Favorites API — индекс операций
Карта HTTP-операций контекста favorites. Дом полей сущностей — domain/favorite.md
и domain/merge-strategy.md.
Общие правила области
- Определение владельца (actor) едино для всех public-операций: авторизованный пользователь —
UserIdиз JWT-claimsub; гость —AnonymousIdиз заголовкаX-Anonymous-Id(выдаётuser-activity, ADR-0054). При наличии обоих приоритет у JWT. Нет ни того ни другого →FAVORITES_OWNER_UNRESOLVED. - Сущности вне tenant (ADR-0052 п.2): обязательный record-level фильтр по
UserId/AnonymousIdв каждой операции (INV-FAV-005). - Display-данные объектов недвижимости (цена, название, фото) этот сервис не отдаёт — фронт добирает их
батч-запросом в
catalogпо спискуPropertyObjectId(будущий докdocs/06-services/catalog/README.md). - Все мутирующие операции публикуют интеграционные события атомарно со сменой состояния.
Note— приватные данные владельца: не логируется, не попадает в Kafka-события и internal-ответы.
Сквозные ошибки области
Единые коды, повторяющиеся в public-операциях (дом условий — таблицы «Ошибки» каждой операции и инварианты domain/favorite.md / domain/merge-strategy.md):
| Код | HTTP | Условие | Где применяется |
|---|---|---|---|
FAVORITES_OWNER_UNRESOLVED | 400 | Нет ни JWT, ни валидного X-Anonymous-Id | все public-операции self-scope |
FAVORITES_NOTE_TOO_LONG | 400 | Note после trim > 4000 символов (INV-FAV-003) | add, update-note |
FAVORITES_NOT_FOUND | 404 | Записи владельца нет / чужая (не различаются, INV-FAV-005) | update-note |
FAVORITES_CONCURRENCY_CONFLICT | 409 | Версия записи изменилась между чтением и фиксацией | update-note |
FAVORITES_SESSION_NOT_LINKED | 403 | Сессия не связана с пользователем в user-activity (INV-FAV-102) | merge-preview, merge |
FAVORITES_MERGE_UNAVAILABLE | 503 | user-activity недоступен (internal API связи сессии) | merge-preview, merge |
DELETE/GET-операции возврата 404 избегают намеренно (существование чужой записи не раскрывается) —
удаление отсутствующей записи и пустой список суть валидные успехи.
Идемпотентность операций (сводка)
| Операция | Ключ идемпотентности | Поведение при повторе |
|---|---|---|
POST /favorites | пара «владелец + PropertyObjectId» (INV-FAV-002) | 200, WasCreated = false, без второго события |
DELETE /favorites/{id} | естественная (удаление отсутствующей записи) | 204, без события |
PUT …/note | целевое значение = текущему | 200, no-op |
POST /favorites/import | пара «владелец + объект» на каждый элемент | 200, ImportedCount = 0, без событий |
POST /favorites/merge | пара (UserId, AnonymousId) (INV-FAV-101) | 200, сохранённые счётчики, WasAlreadyApplied = true |
Окно согласованности слияния и порядок вызовов
Слияние после входа опирается на асинхронную связь сессии в user-activity
(pin.identity.sign-ins.v1 → связывание AnonymousId ↔ UserId). Пока связь не установлена,
merge-preview/merge возвращают FAVORITES_SESSION_NOT_LINKED — фронт повторяет после короткой паузы.
Обязательный порядок клиентского флоу: import → merge-preview → merge (импорт с устройства
должен предшествовать превью — иначе локальные лайки не попадут в слияние). Обычные операции add/remove/
list/note от этого окна не зависят.
Операции
| Область | Метод и маршрут | Операция | Файл |
|---|---|---|---|
| public | GET /api/v1/favorites | Список избранного владельца (user или гостевая сессия) | get-my-favorites.md |
| public | POST /api/v1/favorites | Добавить объект недвижимости в избранное (идемпотентно, опц. заметка) | add-favorite.md |
| public | DELETE /api/v1/favorites/{propertyObjectId} | Удалить объект недвижимости из избранного (идемпотентно) | remove-favorite.md |
| public | PUT /api/v1/favorites/{propertyObjectId}/note | Установить/очистить заметку (≤ 4000) | update-favorite-note.md |
| public | POST /api/v1/favorites/import | Bulk-импорт propertyObjectId[] с устройства | import-favorites.md |
| public | GET /api/v1/favorites/merge-preview | Счётчики/конфликты сторон перед слиянием после входа | get-merge-preview.md |
| public | POST /api/v1/favorites/merge | Применить стратегию слияния (DeviceWins / AccountWins / Merge) | merge-favorites.md |
| internal | GET /internal/favorites?userId= | Чтение избранного пользователя контекстом selections (m2m) | internal/list-favorites.md |
| internal | POST /internal/favorites/status | Пакетный 3-state статус избранного (агент+инвестор × объекты) контекстом crm (m2m) | internal/get-favorite-status.md |
Admin-области нет (модерация заметок не требуется; заметки приватны). Callbacks — не применяется.
Клиентский флоу авторизации (страница избранного)
- Первый заход клиента: если на устройстве есть накопленный локально набор избранного — один вызов
POST /api/v1/favorites/import, при успехе локальный набор очищается. - Обычная работа:
GET /favorites,POST /favorites,DELETE /favorites/{propertyObjectId},PUT /favorites/{propertyObjectId}/note— гость поX-Anonymous-Id, пользователь по JWT. - После входа (JWT получен,
X-Anonymous-Idпродолжает передаваться):GET /favorites/merge-preview→ приRequiresChoice = trueдиалог трёх стратегий (предвыбранMerge) →POST /favorites/merge.
Связанные документы
- ../README.md — границы контекста, зависимости, runtime.
- ../domain/favorite.md — инварианты INV-FAV-001..006.
- ../domain/merge-strategy.md — стратегии слияния, INV-FAV-101..105.