Files
vidconf/backend/models/conference.py

90 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Модель 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()
)