Spaces:
Running
Running
File size: 5,249 Bytes
09801ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | """
Collaboration Models — Multi-tenant Workspaces, team member management, shared links, and comments.
"""
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Boolean, Text, Index, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.models.user import User
class Workspace(UUIDPrimaryKeyMixin, TimestampMixin, Base):
"""Team workspace for sharing projects, datasets, and dashboards."""
__tablename__ = "workspaces"
__table_args__ = (
Index("ix_workspaces_owner_id", "owner_id"),
)
owner_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
)
name: Mapped[str] = mapped_column(String(255), nullable=False)
slug: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
is_private: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
# Relationships
members: Mapped[list["WorkspaceMember"]] = relationship(
back_populates="workspace", cascade="all, delete-orphan", lazy="selectin"
)
def __repr__(self) -> str:
return f"<Workspace name={self.name} slug={self.slug}>"
class WorkspaceMember(UUIDPrimaryKeyMixin, Base):
"""Members inside a workspace with assigned team roles."""
__tablename__ = "workspace_members"
__table_args__ = (
Index("ix_workspace_members_workspace_id", "workspace_id"),
Index("ix_workspace_members_user_id", "user_id"),
)
workspace_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("workspaces.id", ondelete="CASCADE"),
nullable=False,
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
)
role: Mapped[str] = mapped_column(String(50), default="member", nullable=False) # owner, admin, member, viewer
joined_at: Mapped[datetime] = mapped_column(server_default="now()", nullable=False)
# Relationships
workspace: Mapped["Workspace"] = relationship(back_populates="members")
def __repr__(self) -> str:
return f"<WorkspaceMember workspace={self.workspace_id} user={self.user_id}>"
class SharedLink(UUIDPrimaryKeyMixin, TimestampMixin, Base):
"""Public/private share links for dashboards, reports, and models."""
__tablename__ = "shared_links"
__table_args__ = (
Index("ix_shared_links_token", "share_token", unique=True),
)
creator_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
)
resource_type: Mapped[str] = mapped_column(String(50), nullable=False) # dashboard, report, dataset, model
resource_id: Mapped[str] = mapped_column(String(255), nullable=False)
share_token: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
password_hash: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
access_count: Mapped[int] = mapped_column(default=0, nullable=False)
expires_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
def __repr__(self) -> str:
return f"<SharedLink token={self.share_token} type={self.resource_type}>"
class ChatChannel(UUIDPrimaryKeyMixin, TimestampMixin, Base):
"""Channels for collaboration."""
__tablename__ = "chat_channels"
name: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
class ChannelMessage(UUIDPrimaryKeyMixin, TimestampMixin, Base):
"""Messages inside a collaboration channel."""
__tablename__ = "channel_messages"
# Production collaboration tables use UUID identifiers.
channel_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
user_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
parent_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), nullable=True)
content: Mapped[str] = mapped_column(Text, nullable=False)
is_ai: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
is_pinned: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# Relationships
user: Mapped[Optional["User"]] = relationship("User")
class MessageReaction(UUIDPrimaryKeyMixin, TimestampMixin, Base):
"""Emoji reactions on channel messages."""
__tablename__ = "message_reactions"
message_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
emoji: Mapped[str] = mapped_column(String(50), nullable=False)
# Relationships
user: Mapped["User"] = relationship("User")
|