77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
"""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')
|