Files
vidconf/docs/architecture/adr/003-conference-invitees.md

71 lines
6.4 KiB
Markdown
Raw 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.
# ADR-003. Модель приглашённых участников конференции (conference_invitees)
Статус: принято.
## Контекст
Продукту нужен состав приглашённых участников конференции: зарегистрированные
пользователи (user_id) и внешние по произвольному email. Организатор обязан
всегда быть в составе и быть неудаляемым. Уже существует таблица
`conference_participants` — это ФАКТИЧЕСКИЕ участники сеанса (кто реально был,
окна присутствия, единица атрибуции фраз, ADR-002); смешивать сущности нельзя.
## Решение
1. Новая таблица `conference_invitees` — приглашённые НА КОНФЕРЕНЦИЮ
(не на сеанс):
- `id UUID PK`, `conference_id FK conferences ON DELETE CASCADE NOT NULL`;
- `user_id FK users ON DELETE CASCADE NULL` — зарегистрированный;
- `email VARCHAR(255) NULL` — внешний (хранится в lower-case);
- `CHECK ((user_id IS NOT NULL)::int + (email IS NOT NULL)::int = 1)`
ровно одна identity (тот же приём, что в `conference_participants`);
- частичные UNIQUE: `(conference_id, user_id) WHERE user_id IS NOT NULL`
и `(conference_id, email) WHERE email IS NOT NULL` — без дублей;
- `created_at`.
2. Организатор в таблице НЕ хранится: он выводится из `conferences.owner_id`
и всегда добавляется в состав на уровне API/рассылки. Инвариант
«организатор всегда в составе и неудаляем» обеспечен конструктивно —
удалить его из состава невозможно в принципе, рассинхронизация при смене
владельца исключена. Попытка добавить владельца в invitees (по user_id или
его email) молча дедуплицируется на записи.
3. Состав задаётся списком целиком (PUT-семантика поля `participants` в
create/update конференции): backend вычисляет diff, отсутствие поля —
«не менять». Права на изменение состава = права на изменение конференции.
4. Связь с фактическими участниками сеанса — аналитическая, по join без FK:
зарегистрированный — `conference_participants.user_id = invitees.user_id`;
внешний — `lower(guest_access.email) = invitees.email` (если приглашённый
вошёл гостем и указал тот же email). FK не вводим: гость может войти
с другим email или не войти вовсе — жёсткая связь ложна по природе данных.
5. Рассылка приглашений (.ics METHOD:REQUEST): получатели =
организатор + invitees (email пользователя или внешний email); для
закреплённых по-прежнему добавляются участники прошлых сеансов
(`workers/tasks/invitations.py`, дедуп по lower(email)).
6. **Видимость приглашённого в списках.** Приглашённый видит конференцию в
`GET /conferences/my` и `GET /conferences/calendar` наравне с владельцем —
строка попадает в выборку, если `owner_id == user.id` ИЛИ существует
`conference_invitees` этой конференции с `user_id == user.id` ИЛИ с
`lower(email) == lower(email пользователя)` (внешнее приглашение на адрес,
под которым человек впоследствии зарегистрировался). Критерии показа
(закреплённая — безусловно; разовая — `status=scheduled` и `scheduled_at`
в будущем) не меняются, только круг «чей» конференция. `GET /conferences/{id}`
аналогично открыт приглашённому (иначе ховер-карточка/детальная страница
получали бы 403); `is_owner` в ответе для приглашённого — `false`,
`organizer_name` — имя фактического владельца. Права на PATCH/DELETE это
расширение НЕ затрагивает — по-прежнему только владелец/администратор.
## Последствия
- (+) Чистое разделение «приглашён» / «фактически был»; пайплайн атрибуции
фраз (ADR-002) не затронут.
- (+) Инвариант организатора не требует триггеров и проверок целостности.
- () Внешний приглашённый не связывается с гостевым входом надёжно (только
эвристика по email) — принято как ограничение модели.
- () Списки состава в ответах API требуют дозагрузки (`selectinload`) —
следить за N+1 в `/my` и `/calendar`; показ приглашённому (п. 6) добавляет
туда же `EXISTS`-подзапрос по `conference_invitees` и точечный запрос имени
реального владельца на каждую НЕ свою строку списка — список короткий
(закреплённые + предстоящие), нагрузка признана приемлемой.
- Календарь и «Мои конференции» — выборка «владелец ИЛИ приглашённый» (п. 6);
до 2026-07-20 показывались только конференции владельца — приглашённый
видел состав лишь через уведомление/.ics, не через списки приложения.