collabrix/backend/app/models.py

455 lines
18 KiB
Python

from typing import Optional, List
from sqlmodel import SQLModel, Field, Relationship
from datetime import datetime
from enum import Enum
class SnippetVisibility(str, Enum):
PRIVATE = "private"
DEPARTMENT = "department"
ORGANIZATION = "organization"
class UserRole(str, Enum):
USER = "user"
ADMIN = "admin"
SUPERADMIN = "superadmin"
class Language(SQLModel, table=True):
__tablename__ = "language"
id: Optional[int] = Field(default=None, primary_key=True)
code: str = Field(unique=True, index=True)
name: str
is_default: bool = Field(default=False)
created_at: datetime = Field(default_factory=datetime.utcnow)
translations: List["Translation"] = Relationship(back_populates="language")
class Translation(SQLModel, table=True):
__tablename__ = "translation"
id: Optional[int] = Field(default=None, primary_key=True)
key: str = Field(index=True)
value: str = Field(default="")
language_id: int = Field(foreign_key="language.id")
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
language: Language = Relationship(back_populates="translations")
# Association table for User-Department many-to-many relationship
class UserDepartmentLink(SQLModel, table=True):
__tablename__ = "user_department"
user_id: Optional[int] = Field(default=None, foreign_key="user.id", primary_key=True)
department_id: Optional[int] = Field(default=None, foreign_key="department.id", primary_key=True)
# Association table for Snippet-Department access control
class SnippetDepartmentLink(SQLModel, table=True):
__tablename__ = "snippet_department"
snippet_id: Optional[int] = Field(default=None, foreign_key="snippet.id", primary_key=True)
department_id: Optional[int] = Field(default=None, foreign_key="department.id", primary_key=True)
enabled: bool = Field(default=True) # Can be toggled by admins
created_at: datetime = Field(default_factory=datetime.utcnow)
class User(SQLModel, table=True):
__tablename__ = "user"
id: Optional[int] = Field(default=None, primary_key=True)
username: str = Field(unique=True, index=True)
email: str = Field(unique=True, index=True)
hashed_password: str
full_name: Optional[str] = None
profile_picture: Optional[str] = None
theme: str = Field(default="light") # 'light' or 'dark'
is_active: bool = Field(default=True)
role: UserRole = Field(default=UserRole.USER)
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
departments: List["Department"] = Relationship(back_populates="users", link_model=UserDepartmentLink)
messages: List["Message"] = Relationship(back_populates="sender")
snippets: List["Snippet"] = Relationship(back_populates="owner")
sent_direct_messages: List["DirectMessage"] = Relationship(back_populates="sender", sa_relationship_kwargs={"foreign_keys": "DirectMessage.sender_id"})
received_direct_messages: List["DirectMessage"] = Relationship(back_populates="receiver", sa_relationship_kwargs={"foreign_keys": "DirectMessage.receiver_id"})
uploaded_files: List["FileAttachment"] = Relationship(back_populates="uploader")
file_permissions: List["FilePermission"] = Relationship(back_populates="user")
class Department(SQLModel, table=True):
__tablename__ = "department"
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(unique=True, index=True)
description: Optional[str] = None
snippets_enabled: bool = Field(default=True) # Master switch for snippet access
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
users: List[User] = Relationship(back_populates="departments", link_model=UserDepartmentLink)
channels: List["Channel"] = Relationship(back_populates="department")
allowed_snippets: List["Snippet"] = Relationship(link_model=SnippetDepartmentLink)
class Channel(SQLModel, table=True):
__tablename__ = "channel"
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
description: Optional[str] = None
department_id: int = Field(foreign_key="department.id")
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
department: Department = Relationship(back_populates="channels")
messages: List["Message"] = Relationship(back_populates="channel")
kanban_board: Optional["KanbanBoard"] = Relationship(back_populates="channel")
class Message(SQLModel, table=True):
__tablename__ = "message"
id: Optional[int] = Field(default=None, primary_key=True)
content: str
sender_id: int = Field(foreign_key="user.id")
channel_id: int = Field(foreign_key="channel.id")
snippet_id: Optional[int] = Field(default=None, foreign_key="snippet.id")
reply_to_id: Optional[int] = Field(default=None, foreign_key="message.id") # Reply to another message
is_deleted: bool = Field(default=False)
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
sender: User = Relationship(back_populates="messages")
channel: Channel = Relationship(back_populates="messages")
attachments: List["FileAttachment"] = Relationship(back_populates="message")
snippet: Optional["Snippet"] = Relationship()
class DirectMessage(SQLModel, table=True):
__tablename__ = "direct_message"
id: Optional[int] = Field(default=None, primary_key=True)
content: str
sender_id: int = Field(foreign_key="user.id")
receiver_id: int = Field(foreign_key="user.id")
snippet_id: Optional[int] = Field(default=None, foreign_key="snippet.id")
reply_to_id: Optional[int] = Field(default=None, foreign_key="direct_message.id") # Reply to another DM
created_at: datetime = Field(default_factory=datetime.utcnow)
is_read: bool = Field(default=False)
# Relationships
sender: User = Relationship(back_populates="sent_direct_messages", sa_relationship_kwargs={"foreign_keys": "DirectMessage.sender_id"})
receiver: User = Relationship(back_populates="received_direct_messages", sa_relationship_kwargs={"foreign_keys": "DirectMessage.receiver_id"})
snippet: Optional["Snippet"] = Relationship()
reply_to: Optional["DirectMessage"] = Relationship(back_populates="replies", sa_relationship_kwargs={"foreign_keys": "DirectMessage.reply_to_id", "remote_side": "DirectMessage.id"})
replies: List["DirectMessage"] = Relationship(back_populates="reply_to")
attachments: List["DirectMessageAttachment"] = Relationship(back_populates="direct_message")
class LastSeen(SQLModel, table=True):
__tablename__ = "last_seen"
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(foreign_key="user.id")
channel_id: Optional[int] = Field(default=None)
dm_user_id: Optional[int] = Field(default=None)
last_seen: datetime = Field(default_factory=datetime.utcnow, index=True)
class FileAttachment(SQLModel, table=True):
__tablename__ = "file_attachment"
id: Optional[int] = Field(default=None, primary_key=True)
filename: str
original_filename: str
mime_type: str
file_size: int
file_path: str
message_id: int = Field(foreign_key="message.id")
uploader_id: Optional[int] = Field(default=None, foreign_key="user.id")
webdav_path: Optional[str] = None
upload_permission: str = Field(default="read") # "read" or "write"
is_editable: bool = Field(default=False)
uploaded_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
message: Message = Relationship(back_populates="attachments")
uploader: Optional[User] = Relationship(back_populates="uploaded_files")
permissions: List["FilePermission"] = Relationship(back_populates="file")
class FilePermission(SQLModel, table=True):
__tablename__ = "file_permission"
id: Optional[int] = Field(default=None, primary_key=True)
file_id: int = Field(foreign_key="file_attachment.id")
user_id: int = Field(foreign_key="user.id")
permission: str = Field(default="read") # "read" or "write"
granted_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
file: FileAttachment = Relationship(back_populates="permissions")
user: User = Relationship(back_populates="file_permissions")
class DirectMessageAttachment(SQLModel, table=True):
__tablename__ = "direct_message_attachment"
id: Optional[int] = Field(default=None, primary_key=True)
filename: str
original_filename: str
mime_type: str
file_size: int
file_path: str
direct_message_id: int = Field(foreign_key="direct_message.id")
uploader_id: Optional[int] = Field(default=None, foreign_key="user.id")
webdav_path: Optional[str] = None
upload_permission: str = Field(default="read") # "read" or "write"
is_editable: bool = Field(default=False)
uploaded_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
direct_message: DirectMessage = Relationship(back_populates="attachments")
uploader: Optional[User] = Relationship()
class Snippet(SQLModel, table=True):
__tablename__ = "snippet"
id: Optional[int] = Field(default=None, primary_key=True)
title: str = Field(index=True)
language: str = Field(index=True)
content: str
tags: Optional[str] = None # Comma-separated
visibility: SnippetVisibility = Field(default=SnippetVisibility.PRIVATE)
owner_id: int = Field(foreign_key="user.id")
department_id: Optional[int] = Field(default=None, foreign_key="department.id")
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
owner: User = Relationship(back_populates="snippets")
department: Optional[Department] = Relationship()
allowed_departments: List["Department"] = Relationship(link_model=SnippetDepartmentLink)
# Kanban Board Models
class KanbanBoard(SQLModel, table=True):
__tablename__ = "kanban_board"
id: Optional[int] = Field(default=None, primary_key=True)
channel_id: int = Field(foreign_key="channel.id")
name: str = Field(default="Kanban Board")
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
channel: Channel = Relationship(back_populates="kanban_board")
columns: List["KanbanColumn"] = Relationship(back_populates="board")
custom_fields: List["KanbanCustomField"] = Relationship(back_populates="board")
templates: List["KanbanCardTemplate"] = Relationship(back_populates="board")
class KanbanColumn(SQLModel, table=True):
__tablename__ = "kanban_column"
id: Optional[int] = Field(default=None, primary_key=True)
board_id: int = Field(foreign_key="kanban_board.id")
name: str
position: int = Field(default=0) # For ordering columns
color: Optional[str] = Field(default=None) # Hex color for the column
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
board: KanbanBoard = Relationship(back_populates="columns")
cards: List["KanbanCard"] = Relationship(back_populates="column")
class KanbanCard(SQLModel, table=True):
__tablename__ = "kanban_card"
id: Optional[int] = Field(default=None, primary_key=True)
column_id: int = Field(foreign_key="kanban_column.id")
title: str
description: Optional[str] = Field(default=None)
assignee_id: Optional[int] = Field(default=None, foreign_key="user.id")
position: int = Field(default=0) # For ordering cards within a column
due_date: Optional[datetime] = Field(default=None)
priority: Optional[str] = Field(default="medium") # low, medium, high
labels: Optional[str] = Field(default=None) # JSON string for labels/tags
estimated_time: Optional[int] = Field(default=None) # Estimated time in minutes
actual_time: Optional[int] = Field(default=None) # Actual time spent in minutes
is_archived: bool = Field(default=False) # Soft delete - card is archived but not deleted
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
column: KanbanColumn = Relationship(back_populates="cards")
assignee: Optional[User] = Relationship()
checklists: List["KanbanChecklist"] = Relationship(back_populates="card")
comments: List["KanbanCardComment"] = Relationship(back_populates="card")
attachments: List["KanbanCardAttachment"] = Relationship(back_populates="card")
time_entries: List["KanbanTimeEntry"] = Relationship(back_populates="card")
custom_field_values: List["KanbanCustomFieldValue"] = Relationship(back_populates="card")
class KanbanChecklist(SQLModel, table=True):
__tablename__ = "kanban_checklist"
id: Optional[int] = Field(default=None, primary_key=True)
card_id: int = Field(foreign_key="kanban_card.id")
title: str
position: int = Field(default=0) # For ordering checklists within a card
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
card: KanbanCard = Relationship(back_populates="checklists")
items: List["KanbanChecklistItem"] = Relationship(back_populates="checklist")
class KanbanChecklistItem(SQLModel, table=True):
__tablename__ = "kanban_checklist_item"
id: Optional[int] = Field(default=None, primary_key=True)
checklist_id: int = Field(foreign_key="kanban_checklist.id")
title: str
is_completed: bool = Field(default=False)
position: int = Field(default=0) # For ordering items within a checklist
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
checklist: KanbanChecklist = Relationship(back_populates="items")
# Kanban Card Comments
class KanbanCardComment(SQLModel, table=True):
__tablename__ = "kanban_card_comment"
id: Optional[int] = Field(default=None, primary_key=True)
card_id: int = Field(foreign_key="kanban_card.id")
user_id: int = Field(foreign_key="user.id")
content: str
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
card: KanbanCard = Relationship(back_populates="comments")
user: User = Relationship()
# Kanban Card Attachments
class KanbanCardAttachment(SQLModel, table=True):
__tablename__ = "kanban_card_attachment"
id: Optional[int] = Field(default=None, primary_key=True)
card_id: int = Field(foreign_key="kanban_card.id")
filename: str
original_filename: str
mime_type: str
file_size: int
file_path: str
uploader_id: int = Field(foreign_key="user.id")
uploaded_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
card: KanbanCard = Relationship(back_populates="attachments")
uploader: User = Relationship()
# Kanban Time Tracking
class KanbanTimeEntry(SQLModel, table=True):
__tablename__ = "kanban_time_entry"
id: Optional[int] = Field(default=None, primary_key=True)
card_id: int = Field(foreign_key="kanban_card.id")
user_id: int = Field(foreign_key="user.id")
description: Optional[str] = Field(default=None)
start_time: datetime
end_time: Optional[datetime] = Field(default=None)
duration_minutes: Optional[int] = Field(default=None) # Calculated field
is_running: bool = Field(default=True)
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
card: KanbanCard = Relationship(back_populates="time_entries")
user: User = Relationship()
# Kanban Custom Fields
class KanbanCustomField(SQLModel, table=True):
__tablename__ = "kanban_custom_field"
id: Optional[int] = Field(default=None, primary_key=True)
board_id: int = Field(foreign_key="kanban_board.id")
name: str
field_type: str # 'text', 'number', 'date', 'select', 'multiselect', 'checkbox'
options: Optional[str] = Field(default=None) # JSON string for select options
is_required: bool = Field(default=False)
position: int = Field(default=0)
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
board: KanbanBoard = Relationship(back_populates="custom_fields")
values: List["KanbanCustomFieldValue"] = Relationship(back_populates="field")
class KanbanCustomFieldValue(SQLModel, table=True):
__tablename__ = "kanban_custom_field_value"
id: Optional[int] = Field(default=None, primary_key=True)
field_id: int = Field(foreign_key="kanban_custom_field.id")
card_id: int = Field(foreign_key="kanban_card.id")
value: str # JSON string for the value
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
field: KanbanCustomField = Relationship(back_populates="values")
card: KanbanCard = Relationship(back_populates="custom_field_values")
# Kanban Card Activity Log
class KanbanCardActivityLog(SQLModel, table=True):
__tablename__ = "kanban_card_activity_log"
id: Optional[int] = Field(default=None, primary_key=True)
card_id: int = Field(foreign_key="kanban_card.id")
user_id: int = Field(foreign_key="user.id")
action: str # 'created', 'moved', 'updated', 'commented', etc.
field_name: Optional[str] = Field(default=None) # Which field was changed
old_value: Optional[str] = Field(default=None) # Old value as string
new_value: Optional[str] = Field(default=None) # New value as string
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
card: KanbanCard = Relationship()
user: User = Relationship()
# Kanban Card Templates
class KanbanCardTemplate(SQLModel, table=True):
__tablename__ = "kanban_card_template"
id: Optional[int] = Field(default=None, primary_key=True)
board_id: int = Field(foreign_key="kanban_board.id")
name: str
description: Optional[str] = Field(default=None)
template_data: str # JSON string containing template data
is_default: bool = Field(default=False)
created_at: datetime = Field(default_factory=datetime.utcnow)
# Relationships
board: KanbanBoard = Relationship(back_populates="templates")