323 lines
14 KiB
Python
323 lines
14 KiB
Python
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
|