Первоначальная версия VidConf
This commit is contained in:
89
backend/models/conference.py
Normal file
89
backend/models/conference.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Модель Conference — конференция как пользовательская сущность (ADR-001).
|
||||
|
||||
Конференция — постоянная сущность с номером и ссылкой; каждый её запуск
|
||||
порождает отдельную запись `ConferenceSession` (см. `models/session.py`),
|
||||
которая и является единицей AI-пайплайна пост-обработки.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
CheckConstraint,
|
||||
DateTime,
|
||||
Enum,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from models.base import Base
|
||||
|
||||
# Жизненный цикл конференции (ADR-001, п.2): `draft` не нужен — создание
|
||||
# атомарно из формы; `is_pinned` — ортогональный флаг, а не статус.
|
||||
ConferenceStatus = Enum(
|
||||
"scheduled",
|
||||
"active",
|
||||
"ended",
|
||||
name="conference_status",
|
||||
)
|
||||
|
||||
|
||||
class Conference(Base):
|
||||
"""Конференция — номер, постоянная ссылка, владелец, признаки закрепления/закрытости."""
|
||||
|
||||
__tablename__ = "conferences"
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"is_closed = false OR password_hash IS NOT NULL",
|
||||
name="ck_conferences_closed_requires_password",
|
||||
),
|
||||
CheckConstraint(
|
||||
"recurrence IS NULL OR is_pinned = true",
|
||||
name="ck_conferences_recurrence_requires_pinned",
|
||||
),
|
||||
CheckConstraint(
|
||||
"summary_recipients IN ('all', 'owner')",
|
||||
name="ck_conferences_summary_recipients",
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")
|
||||
)
|
||||
# 9 десятичных цифр, первая 1..9 (services/conference_ids.py::generate_number).
|
||||
number: Mapped[str] = mapped_column(String(9), unique=True, nullable=False)
|
||||
# secrets.token_urlsafe(8) — 11 base64url-символов (services/conference_ids.py::generate_slug).
|
||||
slug: Mapped[str] = mapped_column(String(22), unique=True, nullable=False)
|
||||
title: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
owner_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
status: Mapped[str] = mapped_column(
|
||||
ConferenceStatus, nullable=False, server_default="scheduled"
|
||||
)
|
||||
is_pinned: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
is_closed: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
password_hash: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
scheduled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
duration_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
# Pydantic-модель RecurrenceRule (services/recurrence.py), сериализованная в JSONB.
|
||||
recurrence: Mapped[dict[str, Any] | None] = mapped_column(JSONB, nullable=True)
|
||||
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
# Переопределение рассылки саммари конкретной конференции;
|
||||
# NULL = использовать дефолт инстанса (`instance_settings['summary_recipients']`).
|
||||
summary_recipients: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
||||
# Счётчик изменений расписания для VEVENT `SEQUENCE` (.ics):
|
||||
# растёт при правке `scheduled_at`/`duration_minutes`/`recurrence`/`title`,
|
||||
# чтобы календарные клиенты обновляли уже принятое приглашение по UID.
|
||||
ics_sequence: Mapped[int] = mapped_column(Integer, nullable=False, server_default="0")
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
Reference in New Issue
Block a user