Add initial schema
Includes: - Trackers - Tickets - Custom ticket fields - Comments - Initial permissions - Audit logging Will eventually also need: - More custom ticket field types - API-only ticket fields - User specific permissions - git.sr.ht (and other gits) integrations - Attachments (blocked pending files.sr.ht)
This commit is contained in:
parent
ac67654f18
commit
f2885f8777
|
@ -1 +1,8 @@
|
|||
from .flagtype import FlagType
|
||||
from .user import User
|
||||
from .ticketaccess import TicketAccess
|
||||
from .tracker import Tracker
|
||||
from .ticketfield import TicketFieldType, TicketField
|
||||
from .ticketfieldvalue import TicketFieldValue
|
||||
from .ticket import Ticket
|
||||
from .ticketauditentry import AuditFieldType, PermissionsTarget, TicketAuditEntry
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import sqlalchemy.types as types
|
||||
|
||||
class FlagType(types.TypeDecorator):
|
||||
"""
|
||||
Encodes/decodes IntFlags on the fly
|
||||
"""
|
||||
|
||||
impl = types.Integer()
|
||||
|
||||
def __init__(self, enum):
|
||||
self.enum = enum
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
return int(value)
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
return self.enum(value)
|
|
@ -0,0 +1,40 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.database import Base
|
||||
from todosrht.types import TicketAccess, FlagType
|
||||
|
||||
class Ticket(Base):
|
||||
"""
|
||||
Represents a ticket filed in the system. The default permissions are
|
||||
inherited from the tracker configuration, but may be edited to i.e.
|
||||
|
||||
- Give an arbitrary edit/view/whatever access
|
||||
- Remove a specific user's permission to edit
|
||||
- Allow the public to comment on an otherwise uncommentable issue
|
||||
- Lock an issue from further discussion from non-contributors
|
||||
- etc
|
||||
"""
|
||||
__tablename__ = 'ticket'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
ticket_id = sa.Column(sa.Integer, index=True)
|
||||
"""The ID specific to this tracker, appears in URLs etc"""
|
||||
name = sa.Column(sa.Unicode(2048), nullable=False)
|
||||
|
||||
tracker_id = sa.Column(sa.Integer, sa.ForeignKey("tracker.id"), nullable=False)
|
||||
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("tickets"))
|
||||
|
||||
submitter_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
submitter = sa.orm.relationship("User", backref=sa.orm.backref("tickets"))
|
||||
|
||||
default_user_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
"""Permissions given to any logged in user"""
|
||||
|
||||
default_submitter_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
"""Permissions granted to the ticket submitter"""
|
||||
|
||||
default_committer_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
"""Permissions granted to people who have authored commits in the linked git repo"""
|
||||
|
||||
default_anonymous_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
"""Permissions granted to anonymous (non-logged in) users"""
|
|
@ -0,0 +1,9 @@
|
|||
from enum import IntFlag
|
||||
|
||||
class TicketAccess(IntFlag):
|
||||
none = 0
|
||||
browse = 1
|
||||
submit = 2
|
||||
comment = 4
|
||||
edit = 8
|
||||
triage = 16
|
|
@ -0,0 +1,95 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils as sau
|
||||
from todosrht.types import FlagType, TicketAccess
|
||||
from srht.database import Base
|
||||
from enum import Enum
|
||||
|
||||
class AuditFieldType(Enum):
|
||||
"""Describes what kind of field was updated in an audit log event"""
|
||||
name = "name"
|
||||
permissions = "permissions"
|
||||
tracker = "tracker"
|
||||
custom_field = "custom_field"
|
||||
custom_event = "custom_event"
|
||||
|
||||
class PermissionsTarget(Enum):
|
||||
"""Describes the target of an update to ticket permissions"""
|
||||
anonymous = "anonymous"
|
||||
logged_in = "logged_in"
|
||||
submitted = "submitter"
|
||||
committer = "committer"
|
||||
user = "user"
|
||||
"""A specific named user"""
|
||||
|
||||
class TicketAuditEntry(Base):
|
||||
"""
|
||||
Records an event that has occured to a ticket. The field_type tells you
|
||||
what kind of field was affected, which is used to disambiguate the affected
|
||||
columns in the database.
|
||||
|
||||
AuditFieldType.name is used when the ticket is renamed. old_name and
|
||||
new_name are valid for these events.
|
||||
|
||||
AuditFieldType.permissions is used permissions are changed. old_permissions
|
||||
and new_permissions are valid for these events, as well as
|
||||
permissions_target, which describes what kind of user was affected by the
|
||||
change. If permissions_target == PermissionsTarget.user, a specific user's
|
||||
permissions were edited and permissions_user is valid.
|
||||
|
||||
AuditFieldType.tracker is used when a ticket is moved between trackers.
|
||||
old_tracker and new_tracker are valid for this event.
|
||||
|
||||
AuditFieldType.custom_field is when a custom field is edited.
|
||||
old_custom_value and new_custom_value are valid for this event.
|
||||
|
||||
AuditFieldType.custom_event is used for events submitted through the API
|
||||
(i.e. build status updates). oauth_client and custom_text are valid for
|
||||
this event.
|
||||
"""
|
||||
__tablename__ = 'ticket_audit_entry'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
|
||||
ticket = sa.orm.relationship("Ticket",
|
||||
backref=sa.orm.backref("audit_log"))
|
||||
field_type = sa.Column(sau.ChoiceType(AuditFieldType), nullable=False)
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
user = sa.orm.relationship("User",
|
||||
foreign_keys=[user_id])
|
||||
"""The user who executed the change"""
|
||||
ticket_field_id = sa.Column(sa.Integer, sa.ForeignKey("ticket_field.id"))
|
||||
ticket_field = sa.orm.relationship("TicketField")
|
||||
#oauth_client_id = sa.Column(sa.Integer, sa.ForeignKey("oauth_client.id"))
|
||||
#oauth_client = sa.orm.relationship("OAuthClient")
|
||||
|
||||
custom_text = sa.Column(sa.Unicode(4096))
|
||||
"""Markdown, typically used for custom events submitted via API"""
|
||||
|
||||
old_name = sa.Column(sa.Unicode(2048))
|
||||
new_name = sa.Column(sa.Unicode(2048))
|
||||
|
||||
old_permissions = sa.Column(FlagType(TicketAccess))
|
||||
new_permissions = sa.Column(FlagType(TicketAccess))
|
||||
permissions_target = sa.Column(sau.ChoiceType(PermissionsTarget))
|
||||
permissions_user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"))
|
||||
permissions_user = sa.orm.relationship("User",
|
||||
foreign_keys=[permissions_user_id])
|
||||
|
||||
old_tracker_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("tracker.id"))
|
||||
old_tracker = sa.orm.relationship("Tracker",
|
||||
foreign_keys=[old_tracker_id])
|
||||
new_tracker_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("tracker.id"))
|
||||
new_tracker = sa.orm.relationship("Tracker",
|
||||
foreign_keys=[new_tracker_id])
|
||||
|
||||
old_custom_value_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("ticket_field_value.id"))
|
||||
old_custom_value = sa.orm.relationship("TicketFieldValue",
|
||||
foreign_keys=[old_custom_value_id])
|
||||
|
||||
new_custom_value_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("ticket_field_value.id"))
|
||||
new_custom_value = sa.orm.relationship("TicketFieldValue",
|
||||
foreign_keys=[new_custom_value_id])
|
|
@ -0,0 +1,19 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.database import Base
|
||||
|
||||
class TicketComment(Base):
|
||||
__tablename__ = "ticket_comment"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
|
||||
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
|
||||
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("fields"))
|
||||
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
user = sa.orm.relationship("User", backref=sa.orm.backref("comments"))
|
||||
|
||||
text = sa.Column(sa.Unicode(16384), nullable=False)
|
||||
"""Markdown"""
|
||||
visible = sa.Column(sa.Boolean, nullable=False, default=True)
|
||||
"""Deleted comments stay in the system, but are removed from the listing"""
|
|
@ -0,0 +1,39 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils as sau
|
||||
from srht.database import Base
|
||||
from todosrht.types import FlagType, TicketAccess
|
||||
from enum import Enum
|
||||
|
||||
class TicketFieldType(Enum):
|
||||
string = "string"
|
||||
"""A single line of text"""
|
||||
multiline = "multiline"
|
||||
"""Multiple lines of text"""
|
||||
markdown = "markdown"
|
||||
"""Markdown text"""
|
||||
# TODO: option, multioption, boolean
|
||||
user_agent = "user_agent"
|
||||
"""Value of User-Agent header when ticket is submitted"""
|
||||
# TODO: decoded user agent fields
|
||||
# TODO: user, user_list, git_tag, git_repo
|
||||
|
||||
class TicketField(Base):
|
||||
"""
|
||||
Represents a field given to the user to fill out. All tickets have a few
|
||||
fields like name and submitter that cannot be changed.
|
||||
"""
|
||||
__tablename__ = 'ticket_field'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
tracker_id = sa.Column(sa.Integer, sa.ForeignKey("tracker.id"), nullable=False)
|
||||
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("fields"))
|
||||
label = sa.Column(sa.Unicode(1024))
|
||||
field_type = sa.Column(sau.ChoiceType(TicketFieldType), nullable=False)
|
||||
order = sa.Column(sa.Integer, nullable=False, default=0)
|
||||
requried = sa.Column(sa.Boolean, nullable=False, default=False)
|
||||
default_value = sa.Column(sa.Unicode(16384))
|
||||
default_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.edit)
|
||||
"""Users with this level of access to the ticket can edit this field"""
|
|
@ -0,0 +1,20 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.database import Base
|
||||
|
||||
class TicketFieldValue(Base):
|
||||
"""Associates a ticket field with its values for a given ticket"""
|
||||
__tablename__ = 'ticket_field_value'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"))
|
||||
"""
|
||||
Note: this can be None if the ticket was edited; we keep the row around for
|
||||
the audit log and keep the association via the audit log table
|
||||
"""
|
||||
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("fields"))
|
||||
ticket_field_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("ticket_field.id"),
|
||||
nullable=False)
|
||||
ticket_field = sa.orm.relationship("TicketField")
|
||||
string_value = sa.Column(sa.Unicode(16384))
|
|
@ -0,0 +1,42 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.database import Base
|
||||
from todosrht.types import TicketAccess, FlagType
|
||||
|
||||
class Tracker(Base):
|
||||
__tablename__ = 'tracker'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
owner_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
owner = sa.orm.relationship("User", backref=sa.orm.backref("owned_trackers"))
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
name = sa.Column(sa.Unicode(1024))
|
||||
"""
|
||||
May include slashes to serve as categories (nesting is supported,
|
||||
builds.sr.ht style)
|
||||
"""
|
||||
|
||||
description = sa.Column(sa.Unicode(8192))
|
||||
"""Markdown"""
|
||||
|
||||
default_user_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse + TicketAccess.submit + TicketAccess.comment)
|
||||
"""Permissions given to any logged in user"""
|
||||
|
||||
default_submitter_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse + TicketAccess.edit + TicketAccess.comment)
|
||||
"""Permissions granted to submitters for their own tickets"""
|
||||
|
||||
default_committer_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse + TicketAccess.submit + TicketAccess.comment)
|
||||
"""Permissions granted to people who have authored commits in the linked git repo"""
|
||||
|
||||
default_anonymous_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse)
|
||||
"""Permissions granted to anonymous (non-logged in) users"""
|
||||
|
||||
def __repr__(self):
|
||||
return '<Tracker {} {}>'.format(self.id, self.name)
|
Loading…
Reference in New Issue