docs: specialist_invite contract (in-app + email). Fixes EventHub/EventHubSpec#7
This commit is contained in:
+72
-9
@@ -106,17 +106,74 @@ pending владельца, **не** меняет `calendar.type`. Legacy `downg
|
|||||||
|
|
||||||
#### Специалисты
|
#### Специалисты
|
||||||
|
|
||||||
- Специалист = существующий `user`, привязанный к commercial-календарю.
|
- Специалист = существующий `user`, привязанный к commercial-календарю после **принятия приглашения**.
|
||||||
- API (владелец календаря):
|
- **Не путать** с `calendar_share` (права read/write/admin — фаза 2).
|
||||||
- `GET/POST /v1/calendars/:id/specialists`
|
|
||||||
- `PUT/DELETE /v1/calendars/:id/specialists/:user_id`
|
##### Приглашение (`specialist_invite`)
|
||||||
|
|
||||||
|
Владелец commercial **не** вводит сырой `user_id` в продуктовом UI. Добавление — через invite:
|
||||||
|
|
||||||
|
| Канал | Как |
|
||||||
|
|-------|-----|
|
||||||
|
| In-app | typeahead (lookup) → invite по `user_id` → уведомление invitee → accept/decline |
|
||||||
|
| Email | invite по `email` (юзер есть или ещё нет) → письмо со ссылкой → после логина/регистрации тот же accept |
|
||||||
|
|
||||||
|
Модель `specialist_invite` (логическая; таблица/поля — на усмотрение Back при миграции):
|
||||||
|
|
||||||
|
- `id`, `calendar_id`, `inviter_id` (owner)
|
||||||
|
- `invitee_user_id` (если известен) и/или `invitee_email`
|
||||||
|
- `name` / `specialization` (опционально, подставляются в `calendar_specialist` при accept)
|
||||||
|
- `status`: `pending` | `accepted` | `declined` | `expired` | `cancelled`
|
||||||
|
- `token` (для email deep-link), `created_at`, `expires_at` (рекомендуемо: 7 суток)
|
||||||
|
- Уникальность pending: одна активная pending-пара на (`calendar_id` + user) или (`calendar_id` + email)
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- Создавать invite может только владелец commercial; при `booking_open=false` (restricted) —
|
||||||
|
создание invite **запрещено** (`403` / `402` по политике подписки календаря).
|
||||||
|
- Invitee: только `pending` → `accepted` | `declined`; owner может `cancelled` для своего pending.
|
||||||
|
- **Accept** → создаётся / активируется `calendar_specialist` (`status=active`); invite → `accepted`.
|
||||||
|
- Повторный invite тому же active specialist → `409` / no-op по политике Back.
|
||||||
|
- Email для незарегистрированного: после регистрации/verify с тем же email — pending invite
|
||||||
|
привязывается к `user_id` (или accept по token).
|
||||||
|
- Каналы при создании pending: запись `notification` invitee (если user известен) **и** email
|
||||||
|
(если задан email; если только user_id — email на адрес профиля, если есть).
|
||||||
|
|
||||||
|
##### Lookup для typeahead
|
||||||
|
|
||||||
|
- `GET /v1/users/lookup?q=` (auth, rate-limit): поиск по **точному email** и/или prefix `nickname`
|
||||||
|
среди `active` (+ verified) пользователей.
|
||||||
|
- Ответ — минимум PII: `{ id, nickname }` и **маскированный** email (или email только при
|
||||||
|
точном совпадении запроса с полным email). Не админский список пользователей.
|
||||||
|
|
||||||
|
##### API специалистов / invite
|
||||||
|
|
||||||
|
Владелец:
|
||||||
|
|
||||||
|
- `GET /v1/calendars/:id/specialists` — active/inactive специалисты
|
||||||
|
- `GET /v1/calendars/:id/specialist-invites` — исходящие invite (фильтр status)
|
||||||
|
- `POST /v1/calendars/:id/specialist-invites` — тело: `{ user_id }` **или** `{ email }`,
|
||||||
|
опционально `name`, `specialization`
|
||||||
|
- `DELETE /v1/calendars/:id/specialist-invites/:invite_id` — отмена pending (`cancelled`)
|
||||||
|
- `PUT/DELETE /v1/calendars/:id/specialists/:user_id` — deactivate / remove уже принятого
|
||||||
|
|
||||||
|
Invitee:
|
||||||
|
|
||||||
|
- `GET /v1/user/specialist-invites` — входящие (`pending` и история по желанию)
|
||||||
|
- `POST /v1/specialist-invites/:id/accept` | `…/decline`
|
||||||
|
- `POST /v1/specialist-invites/accept` с `{ token }` — accept по email-ссылке (гость → логин)
|
||||||
|
|
||||||
|
Legacy: прямой `POST /v1/calendars/:id/specialists` **не используется клиентским UI**;
|
||||||
|
допускается только как внутренний/тестовый путь или удаляется после миграции на invite.
|
||||||
|
Продуктовый путь: invite → accept → specialist.
|
||||||
|
|
||||||
- `event.specialist_id` опционален; если задан — только `active` specialist этого календаря
|
- `event.specialist_id` опционален; если задан — только `active` specialist этого календаря
|
||||||
(иначе `400`).
|
(иначе `400`).
|
||||||
- **Confirm/decline:** владелец — любые booking календаря; `active` specialist — только booking
|
- **Confirm/decline booking:** владелец — любые booking календаря; `active` specialist — только
|
||||||
на событиях, где `event.specialist_id` = его `user_id`.
|
booking на событиях, где `event.specialist_id` = его `user_id`.
|
||||||
|
|
||||||
#### Фаза 2 (вне текущего контракта реализации)
|
#### Фаза 2 (вне текущего контракта реализации)
|
||||||
- `calendar_share` (`read` | `write` | `admin`): не путать с follow и платной subscription.
|
- `calendar_share` (`read` | `write` | `admin`): не путать с follow, specialist_invite и платной subscription.
|
||||||
- Реальный эквайринг, waitlist, оплата услуги клиентом (B2C).
|
- Реальный эквайринг, waitlist, оплата услуги клиентом (B2C).
|
||||||
|
|
||||||
### 2.1.3. Share / приглашения (фаза 2)
|
### 2.1.3. Share / приглашения (фаза 2)
|
||||||
@@ -403,8 +460,14 @@ src/
|
|||||||
- `DELETE /v1/calendars/:id` — удалить календарь.
|
- `DELETE /v1/calendars/:id` — удалить календарь.
|
||||||
- `POST /v1/calendars/:id/follow` — отслеживать чужой календарь.
|
- `POST /v1/calendars/:id/follow` — отслеживать чужой календарь.
|
||||||
- `DELETE /v1/calendars/:id/follow` — снять follow.
|
- `DELETE /v1/calendars/:id/follow` — снять follow.
|
||||||
- `GET/POST /v1/calendars/:id/specialists` — список / добавить специалиста (владелец).
|
- `GET /v1/users/lookup?q=` — typeahead пользователей для invite (минимальный PII, rate-limit).
|
||||||
- `PUT/DELETE /v1/calendars/:id/specialists/:user_id` — обновить / убрать специалиста.
|
- `GET /v1/calendars/:id/specialists` — список специалистов (владелец).
|
||||||
|
- `PUT/DELETE /v1/calendars/:id/specialists/:user_id` — deactivate / убрать специалиста (владелец).
|
||||||
|
- `GET/POST /v1/calendars/:id/specialist-invites` — исходящие invite / создать (владелец).
|
||||||
|
- `DELETE /v1/calendars/:id/specialist-invites/:invite_id` — отменить pending (владелец).
|
||||||
|
- `GET /v1/user/specialist-invites` — входящие приглашения.
|
||||||
|
- `POST /v1/specialist-invites/:id/accept` | `…/decline` — ответ invitee.
|
||||||
|
- `POST /v1/specialist-invites/accept` `{ token }` — accept по email deep-link.
|
||||||
- `GET /v1/calendars/:calendar_id/events` — события календаря.
|
- `GET /v1/calendars/:calendar_id/events` — события календаря.
|
||||||
- `POST /v1/calendars/:calendar_id/events` — создать событие.
|
- `POST /v1/calendars/:calendar_id/events` — создать событие.
|
||||||
- `GET /v1/events/:id` — событие.
|
- `GET /v1/events/:id` — событие.
|
||||||
|
|||||||
+10
-5
@@ -125,8 +125,13 @@ Redirects: `/discover` → `/search`; `/calendars/:id` → `/c/:id`; `/calendars
|
|||||||
- HTML month view владельца: `GET /v1/calendars/:calendar_id/view?month=YYYY-MM` — при переключении виджета на **прошедший месяц** (owner + вид «Месяц») автоматически подменяет клиентский grid серверным HTML (live/архив, задача #15). Текущий и будущие месяцы — React-виджет по `GET …/events`.
|
- HTML month view владельца: `GET /v1/calendars/:calendar_id/view?month=YYYY-MM` — при переключении виджета на **прошедший месяц** (owner + вид «Месяц») автоматически подменяет клиентский grid серверным HTML (live/архив, задача #15). Текущий и будущие месяцы — React-виджет по `GET …/events`.
|
||||||
- Создание / апгрейд `commercial` без подписки → `402` → `/subscription`; после оплаты —
|
- Создание / апгрейд `commercial` без подписки → `402` → `/subscription`; после оплаты —
|
||||||
возврат к созданию/редактированию
|
возврат к созданию/редактированию
|
||||||
- Владелец commercial: управление специалистами (`GET/POST/PUT/DELETE …/specialists`);
|
- Владелец commercial: специалисты **через invite** (не сырой `user_id`):
|
||||||
выбор `specialist_id` в форме события; баннер «подписка истекла / истекает»
|
typeahead `GET /v1/users/lookup` и/или email → `POST …/specialist-invites`;
|
||||||
|
список active + исходящие pending; deactivate/remove принятых.
|
||||||
|
Выбор `specialist_id` в форме события (только `active`).
|
||||||
|
Баннер «подписка истекла / истекает» при `booking_open=false`.
|
||||||
|
- Invitee: inbox входящих `GET /v1/user/specialist-invites` (More / уведомления) —
|
||||||
|
принять / отклонить; deep-link из email → accept по `token` после логина.
|
||||||
|
|
||||||
### 5.5. События
|
### 5.5. События
|
||||||
- Список: `GET /v1/calendars/:calendar_id/events`
|
- Список: `GET /v1/calendars/:calendar_id/events`
|
||||||
@@ -206,12 +211,12 @@ E2E: `e2e/TESTIDS.md`. Моки (`npm run test:e2e`, `project=mock`) — тол
|
|||||||
|
|
||||||
## 7. Ограничения MVP (Future)
|
## 7. Ограничения MVP (Future)
|
||||||
|
|
||||||
Не в текущей волне commercial (§2.1.2 BackSpec уже в scope UI):
|
Не в текущей волне (кроме specialist_invite — см. §5.4 / BackSpec §2.1.2):
|
||||||
- приглашения / шаринг календаря (`calendar_share`) — фаза 2
|
- шаринг календаря с правами (`calendar_share`) — фаза 2 (не путать со specialist_invite)
|
||||||
- серверный logout / revoke session
|
- серверный logout / revoke session
|
||||||
- загрузка файлов (вложения)
|
- загрузка файлов (вложения)
|
||||||
- явный `client_type=mobile` (фаза 3 бэка)
|
- явный `client_type=mobile` (фаза 3 бэка)
|
||||||
- push-уведомления (таблица `notification` без полноценной доставки)
|
- полноценный push (сейчас in-app `notification` + email для specialist_invite)
|
||||||
- waitlist / оплата услуги клиентом (B2C)
|
- waitlist / оплата услуги клиентом (B2C)
|
||||||
|
|
||||||
Лайки/дизлайки отзывов: `PUT/DELETE /v1/reviews/:id/vote`, поле `my_vote` в ответах отзывов (EventHubBack#47).
|
Лайки/дизлайки отзывов: `PUT/DELETE /v1/reviews/:id/vote`, поле `my_vote` в ответах отзывов (EventHubBack#47).
|
||||||
|
|||||||
Reference in New Issue
Block a user