API контекста selections — индекс
Дом полей — ../domain/selection.md и ../domain/selection-snapshot.md; карта областей — ../README.md.
Public (мутации — владелец по JWT; чтение по коду — любой актор, включая гостя)
| Операция | Метод и route | Документ |
|---|---|---|
| Мои подборки | GET /api/v1/selections | list-my-selections.md |
| Создать вручную | POST /api/v1/selections | create-selection.md |
| Создать из текущего избранного | POST /api/v1/selections/from-favorites | create-selection-from-favorites.md |
| Открыть по публичному коду | GET /api/v1/selections/{publicCode} | get-selection-by-code.md |
| Название/описание | PUT /api/v1/selections/{id} | update-selection.md |
| Состав и порядок целиком | PUT /api/v1/selections/{id}/items | set-selection-items.md |
| Удалить (soft) | DELETE /api/v1/selections/{id} | delete-selection.md |
Internal (m2m: news, crm)
| Операция | Метод и route | Документ |
|---|---|---|
Снять snapshot для поста (news) | POST /api/v1/internal/selections/{id}/snapshots | create-selection-snapshot.md |
Отвязать snapshot — замена блока целиком / удаление поста (news, INV-SEL-001) | POST /api/v1/internal/snapshots/{id}/detach | detach-selection-snapshot.md |
Контент snapshot-а для рендера поста (news) | GET /api/v1/internal/snapshots/{id} | get-selection-snapshot.md |
Подборка для карточки клиента/сделки (crm) | GET /api/v1/internal/selections/{id} | get-selection.md |
Свои подборки инвестора для карточки клиента (crm) | GET /api/v1/internal/selections/by-owner/{ownerUserId} | list-selections-by-owner.md |
Admin-области нет (модерация публичных подборок отложена — ../README.md). Callbacks — not applicable.
Матрица операций (актор · идемпотентность · публикуемые события)
| Операция | Актор | Success | Идемпотентность | Публикует |
|---|---|---|---|---|
GET /selections | владелец (JWT) | 200 | безопасный read | — |
POST /selections | владелец (JWT) | 201 | нет (повтор → 2-я подборка; лимит INV-SEL-007) | selection-created |
POST /selections/from-favorites | владелец (JWT) | 201 | нет (повтор → независимая копия) | selection-created + N× selection-item-added |
GET /selections/{publicCode} | любой (guest/JWT) | 200 | безопасный read | — |
PUT /selections/{id} | владелец (JWT) | 200 | семантически (полная замена метаданных) | selection-updated (только при изменении) |
PUT /selections/{id}/items | владелец (JWT) | 200 | семантически (replace-all) | selection-updated + N× selection-item-added (при изменении) |
DELETE /selections/{id} | владелец (JWT) | 204 | повтор → 404 (клиент трактует как успех) | selection-deleted |
POST /internal/selections/{id}/snapshots | m2m news | 201/200 | бизнес-ключ (PostId, SourceSelectionId, Active) | — (синхронный ответ) |
POST /internal/snapshots/{id}/detach | m2m news | 200 | переход Active → Detached однократен (INV-SNP-005) | — |
GET /internal/snapshots/{id} | m2m news | 200 | безопасный read | — |
GET /internal/selections/{id} | m2m crm | 200 | безопасный read | — |
GET /internal/selections/by-owner/{ownerUserId} | m2m crm | 200 | безопасный read | — |
Все интеграционные события — key = SelectionId (порядок событий одной подборки сохраняется);
selection-item-added дополнительно несёт монотонный Version. Дом контрактов событий —
../events/; дом инвариантов — ../domain/selection.md,
../domain/selection-snapshot.md.
Сквозные правила записи (какие инварианты проверяет каждая мутация)
- Владение (INV-SEL-002): все мутации подборки — только
OwnerUserId == actor.UserId; чужая/ несуществующая/удалённая →SELECTIONS_NOT_FOUND(существование чужой не раскрывается). - Состав: дедупликация
PropertyObjectId(INV-SEL-003), лимит ≤ 200 (INV-SEL-004), нормализацияSortOrder0..N-1 (INV-SEL-006) — при создании с начальным списком,from-favorites, замене состава. - Лимит владельца (INV-SEL-007): ≤ 100 не удалённых подборок — при создании (
POST,from-favorites). - PublicCode (INV-SEL-005): генерируется при создании, неизменяем; коллизия → регенерация (≤ 3).
- Удаление (INV-SEL-009): soft delete; удалённая невидима публично и не редактируется; snapshot-ы и ссылки CRM остаются разрешимыми.
- Snapshot (INV-SNP-002/004/005/006, INV-SEL-001): снятие только из активной непустой подборки; контент иммутабелен; detach однократен; snapshot не удаляется.
- Конкурентность: правки одной подборки сериализуются оптимистической блокировкой — проигравший
получает
SELECTIONS_CONCURRENCY_CONFLICT(409) и перечитывает.
Общие правила области
- Подборки — вне tenant (ADR-0052 п.2); изоляция мутаций — record-level по
OwnerUserId(INV-SEL-002). - Ошибки — ProblemDetails с кодом
SELECTIONS_*. {publicCode}и{id}в одном сегменте роутинга разводятся route-constraint-ом:{id:guid}— приоритетный маршрут, всё остальное (12 символов base62) —publicCode.- Feature flag
selections.public-open.enabledдействует только на публичное чтение по коду.