Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
"""conference invitees and avatars
Реализует раздел «Модель данных» ADR-003
(`docs/architecture/adr/003-conference-invitees.md`):
- новая таблица `conference_invitees` — приглашённые НА КОНФЕРЕНЦИЮ
(зарегистрированный `user_id` ИЛИ внешний `email`, ровно одна identity);
организатор в таблице не хранится (выводится из `conferences.owner_id`);
- `users.avatar_path` — путь к загруженному аватару,
`NULL` — заглушка с инициалами на фронте.
Revision ID: d87681e12784
Revises: 504791847d4f
Create Date: 2026-07-19 12:03:06.294730
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'd87681e12784'
down_revision: Union[str, Sequence[str], None] = '504791847d4f'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
op.create_table(
'conference_invitees',
sa.Column('id', sa.UUID(), server_default=sa.text('gen_random_uuid()'), nullable=False),
sa.Column('conference_id', sa.UUID(), nullable=False),
sa.Column('user_id', sa.UUID(), nullable=True),
sa.Column('email', sa.String(length=255), nullable=True),
sa.Column(
'created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'),
nullable=False,
),
sa.CheckConstraint(
'(user_id IS NOT NULL)::int + (email IS NOT NULL)::int = 1',
name='ck_conference_invitees_single_identity',
),
sa.ForeignKeyConstraint(['conference_id'], ['conferences.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
)
# Частичные уникальные индексы (ADR-003, п.1): дубль по зарегистрированному
# пользователю или по email (регистронезависимо — `lower(email)`, email
# приложение хранит уже в lower-case, индекс — доп. страховка).
op.create_index(
'uq_conference_invitees_user',
'conference_invitees',
['conference_id', 'user_id'],
unique=True,
postgresql_where=sa.text('user_id IS NOT NULL'),
)
op.execute(
"""
CREATE UNIQUE INDEX uq_conference_invitees_email
ON conference_invitees (conference_id, lower(email))
WHERE email IS NOT NULL
"""
)
op.add_column('users', sa.Column('avatar_path', sa.String(length=512), nullable=True))
def downgrade() -> None:
"""Downgrade schema."""
op.drop_column('users', 'avatar_path')
op.execute('DROP INDEX IF EXISTS uq_conference_invitees_email')
op.drop_index('uq_conference_invitees_user', table_name='conference_invitees')
op.drop_table('conference_invitees')