"""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')