from sqlmodel import SQLModel, Field from typing import Optional from datetime import datetime, timezone class User(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) gitea_id: Optional[int] = Field(default=None, unique=True, index=True) username: str = Field(unique=True, index=True) password_hash: Optional[str] = None full_name: Optional[str] = None email: Optional[str] = Field(default=None, unique=True) organization: Optional[str] = None role: str = Field(default="student") is_active: bool = Field(default=True) is_verified: bool = Field(default=False) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class UserSession(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) session_token: str = Field(unique=True, index=True) user_id: int = Field(foreign_key="user.id") ip_address: Optional[str] = None user_agent: Optional[str] = None created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class Event(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) title: str event_type: str = Field(default="lecture") start_time: datetime end_time: datetime location: Optional[str] = None description: Optional[str] = None status: str = Field(default="scheduled") is_public: bool = Field(default=True) google_event_id: Optional[str] = Field(default=None, unique=True, index=True) manually_edited: bool = Field(default=False) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class GlobalStatus(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) status: str = Field(default="free") delay_minutes: int = Field(default=0) message: Optional[str] = None updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class Project(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) title: str description: Optional[str] = None status: str = Field(default="idea") visibility: str = Field(default="public") gitea_url: Optional[str] = None gitea_repo: Optional[str] = None owner_id: int = Field(foreign_key="user.id") tags: Optional[str] = None start_date: Optional[str] = None end_date: Optional[str] = None contact_person: Optional[str] = None created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class ProjectMember(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) project_id: int = Field(foreign_key="project.id") user_id: int = Field(foreign_key="user.id") role: str = Field(default="viewer") can_view_files: bool = Field(default=True) can_edit_files: bool = Field(default=False) can_view_repo: bool = Field(default=True) can_post_feed: bool = Field(default=False) can_edit_own_posts: bool = Field(default=True) can_edit_any_post: bool = Field(default=False) can_manage_members: bool = Field(default=False) joined_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class ProjectTask(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) project_id: int = Field(foreign_key="project.id") title: str description: Optional[str] = None status: str = Field(default="backlog") priority: str = Field(default="medium") assignee_id: Optional[int] = Field(default=None, foreign_key="user.id") created_by: int = Field(foreign_key="user.id") due_date: Optional[str] = None position: int = Field(default=0) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class ProjectPost(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) project_id: int = Field(foreign_key="project.id") author_id: int = Field(foreign_key="user.id") title: str body: Optional[str] = None is_pinned: bool = Field(default=False) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class ProjectPostAttachment(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) post_id: int = Field(foreign_key="projectpost.id") file_id: int = Field(foreign_key="portalfile.id") class ProjectSubscription(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) project_id: int = Field(foreign_key="project.id") user_id: int = Field(foreign_key="user.id") notify_email: bool = Field(default=True) notify_inapp: bool = Field(default=True) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class PortalFile(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) original_name: str stored_name: str size_bytes: int uploaded_by: int = Field(foreign_key="user.id") project_id: Optional[int] = Field(default=None, foreign_key="project.id") uploaded_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) # ── v2.0 ────────────────────────────────────────────────────────── space_id: Optional[int] = Field(default=None, foreign_key="filespace.id") folder_id: Optional[int] = Field(default=None, foreign_key="filefolder.id") description: Optional[str] = None tags: Optional[str] = None version: int = Field(default=1) current_version_id: Optional[int] = Field(default=None) class EventSubscriber(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) email: str = Field(index=True) user_id: Optional[int] = Field(default=None, foreign_key="user.id") unsubscribe_token: str = Field(unique=True, index=True) is_confirmed: bool = Field(default=False) confirm_token: Optional[str] = Field(default=None, unique=True, index=True) notify_status: bool = Field(default=True) notify_events: bool = Field(default=True) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class Workshop(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) title: str description: Optional[str] = None program: Optional[str] = None lead_id: int = Field(foreign_key="user.id") category: str = Field(default="other") difficulty: str = Field(default="beginner") partner: Optional[str] = None status: str = Field(default="draft") visibility: str = Field(default="public") location: Optional[str] = None materials_required: Optional[str] = None outcome: Optional[str] = None has_certificate: bool = Field(default=False) recurrence: Optional[str] = None max_participants: Optional[int] = None cover_url: Optional[str] = None tags: Optional[str] = None created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class WorkshopAssistant(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) workshop_id: int = Field(foreign_key="workshop.id") user_id: int = Field(foreign_key="user.id") created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class WorkshopSession(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) workshop_id: int = Field(foreign_key="workshop.id") start_time: datetime end_time: datetime location_override: Optional[str] = None note: Optional[str] = None class WorkshopRegistration(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) workshop_id: int = Field(foreign_key="workshop.id") user_id: int = Field(foreign_key="user.id") status: str = Field(default="registered") notes: Optional[str] = None registered_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class WorkshopMaterial(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) workshop_id: int = Field(foreign_key="workshop.id") title: str description: Optional[str] = None file_id: Optional[int] = Field(default=None, foreign_key="portalfile.id") external_url: Optional[str] = None visible_to: str = Field(default="registered") created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class WorkshopPost(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) workshop_id: int = Field(foreign_key="workshop.id") author_id: int = Field(foreign_key="user.id") title: str body: Optional[str] = None is_pinned: bool = Field(default=False) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class WorkshopPostAttachment(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) post_id: int = Field(foreign_key="workshoppost.id") file_id: int = Field(foreign_key="portalfile.id") class WorkshopSubscription(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) workshop_id: int = Field(foreign_key="workshop.id") user_id: int = Field(foreign_key="user.id") notify_email: bool = Field(default=True) notify_inapp: bool = Field(default=True) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class WorkshopFeedback(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) workshop_id: int = Field(foreign_key="workshop.id") user_id: int = Field(foreign_key="user.id") rating: int = Field(default=5) comment: Optional[str] = None created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class Notification(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id", index=True) title: str body: Optional[str] = None link_url: Optional[str] = None kind: str = Field(default="info") is_read: bool = Field(default=False, index=True) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class UserOnboarding(SQLModel, table=True): """Прогресс прохождения обучающего тура по платформе.""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id", unique=True, index=True) visited_schedule: bool = Field(default=False) visited_projects: bool = Field(default=False) visited_workshops: bool = Field(default=False) visited_files: bool = Field(default=False) visited_feed: bool = Field(default=False) visited_profile: bool = Field(default=False) completed_at: Optional[datetime] = None created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class FileSpace(SQLModel, table=True): """Пространство файлов: по проекту, по направлению, личное.""" id: Optional[int] = Field(default=None, primary_key=True) name: str slug: str = Field(unique=True, index=True) description: Optional[str] = None owner_id: int = Field(foreign_key="user.id") project_id: Optional[int] = Field(default=None, foreign_key="project.id") visibility: str = Field(default="internal") # public | internal | private created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class FileFolder(SQLModel, table=True): """Папка внутри пространства. parent_id=null — корень пространства.""" id: Optional[int] = Field(default=None, primary_key=True) space_id: int = Field(foreign_key="filespace.id") parent_id: Optional[int] = Field(default=None, foreign_key="filefolder.id") name: str description: Optional[str] = None created_by: int = Field(foreign_key="user.id") created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class FileSpaceACL(SQLModel, table=True): """Права пользователя на пространство.""" id: Optional[int] = Field(default=None, primary_key=True) space_id: int = Field(foreign_key="filespace.id") user_id: int = Field(foreign_key="user.id") role: str = Field(default="viewer") # viewer | editor | manager created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class FileFolderACL(SQLModel, table=True): """Права пользователя на папку (перекрывают права пространства).""" id: Optional[int] = Field(default=None, primary_key=True) folder_id: int = Field(foreign_key="filefolder.id") user_id: int = Field(foreign_key="user.id") can_view: bool = Field(default=True) can_upload: bool = Field(default=False) can_delete: bool = Field(default=False) can_manage: bool = Field(default=False) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class FileVersion(SQLModel, table=True): """Версия файла — каждая имеет свой stored_name.""" id: Optional[int] = Field(default=None, primary_key=True) file_id: int = Field(foreign_key="portalfile.id") version_num: int = Field(default=1) stored_name: str size_bytes: int uploaded_by: int = Field(foreign_key="user.id") uploaded_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) comment: Optional[str] = None