from __future__ import annotations import uuid from datetime import date, datetime from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.types import JSON from app.core.database import Base from app.models.base import ActorAuditMixin, TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin from app.models.types import GUID class WorkHub(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, ActorAuditMixin): __tablename__ = "work_hubs" name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String(40), default="active", nullable=False, index=True) owner_user_id: Mapped[str | None] = mapped_column(String(100), index=True) class Team(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, ActorAuditMixin): __tablename__ = "teams" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String(40), default="active", nullable=False, index=True) owner_user_id: Mapped[str | None] = mapped_column(String(100), index=True) class TeamMember(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "team_members" __table_args__ = (UniqueConstraint("tenant_id", "team_id", "user_id", name="uq_team_member"),) team_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("teams.id"), nullable=False, index=True) user_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True) role: Mapped[str] = mapped_column(String(40), default="member", nullable=False) status: Mapped[str] = mapped_column(String(40), default="active", nullable=False, index=True) joined_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) class Project(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, ActorAuditMixin): __tablename__ = "projects" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) team_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("teams.id"), index=True) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String(40), default="planning", nullable=False, index=True) priority: Mapped[str] = mapped_column(String(40), default="medium", nullable=False) owner_user_id: Mapped[str | None] = mapped_column(String(100), index=True) start_date: Mapped[date | None] = mapped_column(Date) due_date: Mapped[date | None] = mapped_column(Date, index=True) budget_estimate: Mapped[int | None] = mapped_column(Integer) extra: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) class ProjectMember(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "project_members" __table_args__ = (UniqueConstraint("tenant_id", "project_id", "user_id", name="uq_project_member"),) project_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("projects.id"), nullable=False, index=True) user_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True) role: Mapped[str] = mapped_column(String(40), default="member", nullable=False) status: Mapped[str] = mapped_column(String(40), default="active", nullable=False) class Task(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, ActorAuditMixin): __tablename__ = "tasks" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) project_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("projects.id"), nullable=False, index=True) team_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("teams.id"), index=True) title: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String(40), default="backlog", nullable=False, index=True) priority: Mapped[str] = mapped_column(String(40), default="medium", nullable=False) assignee_user_id: Mapped[str | None] = mapped_column(String(100), index=True) due_date: Mapped[date | None] = mapped_column(Date, index=True) estimated_minutes: Mapped[int | None] = mapped_column(Integer) actual_minutes: Mapped[int | None] = mapped_column(Integer) labels: Mapped[list] = mapped_column(JSON, default=list, nullable=False) sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False) class ContentType(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "content_types" __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_content_type_code"),) work_hub_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("work_hubs.id"), index=True) code: Mapped[str] = mapped_column(String(80), nullable=False, index=True) label: Mapped[str] = mapped_column(String(255), nullable=False) channel: Mapped[str | None] = mapped_column(String(80)) workflow: Mapped[list] = mapped_column(JSON, default=list, nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) extra: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) class ContentItem(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, ActorAuditMixin): __tablename__ = "content_items" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) project_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("projects.id"), index=True) team_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("teams.id"), index=True) title: Mapped[str] = mapped_column(String(255), nullable=False) content_type: Mapped[str] = mapped_column(String(80), nullable=False, index=True) channel: Mapped[str | None] = mapped_column(String(80), index=True) status: Mapped[str] = mapped_column(String(40), default="draft", nullable=False, index=True) priority: Mapped[str] = mapped_column(String(40), default="medium", nullable=False) owner_user_id: Mapped[str | None] = mapped_column(String(100), index=True) creator_user_id: Mapped[str | None] = mapped_column(String(100)) reviewer_user_id: Mapped[str | None] = mapped_column(String(100), index=True) scheduled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True) published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) campaign_ref: Mapped[str | None] = mapped_column(String(255)) caption: Mapped[str | None] = mapped_column(Text) body: Mapped[str | None] = mapped_column(Text) brief: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) extra: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) class ContentApproval(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "content_approvals" content_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("content_items.id"), nullable=False, index=True) work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) approver_user_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True) requester_user_id: Mapped[str | None] = mapped_column(String(100)) approval_type: Mapped[str] = mapped_column(String(40), default="internal", nullable=False) status: Mapped[str] = mapped_column(String(40), default="pending", nullable=False, index=True) comment: Mapped[str | None] = mapped_column(Text) requested_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) responded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) class AssetReference(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "asset_references" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) project_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("projects.id"), index=True) content_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("content_items.id"), index=True) task_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("tasks.id"), index=True) file_ref: Mapped[str] = mapped_column(String(255), nullable=False) title: Mapped[str | None] = mapped_column(String(255)) asset_type: Mapped[str | None] = mapped_column(String(80)) role: Mapped[str | None] = mapped_column(String(80)) sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False) storage_status: Mapped[str] = mapped_column(String(40), default="reference_only", nullable=False) extra: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) class Comment(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "comments" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) subject_type: Mapped[str] = mapped_column(String(40), nullable=False, index=True) subject_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False, index=True) author_user_id: Mapped[str] = mapped_column(String(100), nullable=False) body: Mapped[str] = mapped_column(Text, nullable=False) status: Mapped[str] = mapped_column(String(40), default="active", nullable=False) class Mention(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "mentions" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) comment_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("comments.id"), nullable=False, index=True) mentioned_user_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True) subject_type: Mapped[str] = mapped_column(String(40), nullable=False) subject_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) notification_status: Mapped[str] = mapped_column(String(40), default="persisted", nullable=False) class Activity(Base, UUIDPrimaryKeyMixin, TenantMixin): __tablename__ = "activities" work_hub_id: Mapped[uuid.UUID] = mapped_column(GUID(), ForeignKey("work_hubs.id"), nullable=False, index=True) entity_type: Mapped[str] = mapped_column(String(80), nullable=False, index=True) entity_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False, index=True) action: Mapped[str] = mapped_column(String(80), nullable=False) actor_user_id: Mapped[str | None] = mapped_column(String(100)) payload: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True) class WorkspaceAuditLog(Base, UUIDPrimaryKeyMixin, TenantMixin): __tablename__ = "workspace_audit_logs" entity_type: Mapped[str] = mapped_column(String(100), nullable=False) entity_id: Mapped[uuid.UUID | None] = mapped_column(GUID()) action: Mapped[str] = mapped_column(String(50), nullable=False) actor_user_id: Mapped[str | None] = mapped_column(String(100)) changes: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) class OutboxEvent(Base, UUIDPrimaryKeyMixin, TenantMixin): __tablename__ = "outbox_events" event_type: Mapped[str] = mapped_column(String(150), nullable=False) aggregate_type: Mapped[str] = mapped_column(String(100), nullable=False) aggregate_id: Mapped[str] = mapped_column(String(100), nullable=False) payload: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) status: Mapped[str] = mapped_column(String(30), default="pending", nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) class WorkspaceSetting(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "workspace_settings" work_hub_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), ForeignKey("work_hubs.id"), index=True) key: Mapped[str] = mapped_column(String(150), nullable=False) value: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False) class WorkspacePermission(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "workspace_permissions" code: Mapped[str] = mapped_column(String(150), nullable=False) description: Mapped[str | None] = mapped_column(String(255))