Перейти к основному содержимому

GET /api/v1/selections — Мои подборки

Список подборок текущего владельца (инвестора или агента): название, счётчик объектов недвижимости, публичный код для публичной ссылки, даты.

Назначение

Владелец открывает страницу «Мои подборки» и видит все свои не удалённые подборки, отсортированные по дате создания (новые сверху). Дом полей — ../domain/selection.md.

Metadata

ПолеЗначение
Сервис/контекстselections
API areapublic
Feature groupselections
Статусdraft
Документdocs/06-services/selections/api/public/list-my-selections.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (JWT; гость списка не имеет — подборка привязана к владельцу)
Auth policy / Permissionsauthenticated user; отдельных permissions нет (self-scope)
Scope (RBAC)self: только собственные подборки
Record-levelOwnerUserId == actor.UserId (INV-SEL-002) — обязательный предикат выборки
Tenant isolationне применяется — сущность вне tenant (ADR-0052 п.2)
Auditnone (чтение собственных данных)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/selections
Success status200
Idempotency headerне применяется (безопасный read)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequired; отсутствие → 401record-level фильтр
PagequeryintНет (default 1)>= 1
PageSizequeryintНет (default 20)1..100

Body отсутствует.

Модель ответа ListMySelectionsQueryResult

ПолеТипОбязательностьИсточникMaskingОписание
ItemsIReadOnlyList<MySelectionListItemModel>Да (может быть пуст)проекция FilterСтраница подборок.
TotalCountintДаCount того же предикатаВсего не удалённых подборок владельца (≤ 100, INV-SEL-007).
PageintДаecho запросаТекущая страница.
PageSizeintДаecho запросаРазмер страницы.

MySelectionListItemModel

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаSelection.IdДля мутаций (PUT/DELETE) и ссылок CRM.
TitlestringДаSelection.TitleНазвание.
Descriptionstring?НетSelection.DescriptionОписание.
PublicCodestringДаSelection.PublicCodeКод публичной ссылки /collection/{publicCode}.
Sourcestring (enum SelectionSource)ДаSelection.SourceManual / Favorites.
ItemsCountintДаcomputed: Count(SelectionItem where SelectionId == Id) в проекцииОбъектов недвижимости в подборке (0..200).
CreatedAtDateTimeOffsetДаSelection.CreatedAtСоздание.
UpdatedAtDateTimeOffsetДаSelection.UpdatedAtПоследняя правка.

OwnerUserId в ответ не включается (равен актору); версия для оптимистической блокировки не нужна — конфликты разрешаются на мутациях (409 → перечитать).

Алгоритм

  1. Контекст и доступ. Операцию выполняет авторизованный пользователь; возвращаются только его собственные подборки (по владельцу-актору, INV-SEL-002).
  2. Проверка входа и предусловий. Page >= 1, PageSize ∈ 1..100 — иначе VALIDATION_ERROR.
  3. Что читаем. Страница подборок владельца, не удалённых (DeletedAt == null), в порядке CreatedAt desc (индекс ix_selections_owner_created), с проекцией MySelectionListItemModel (ItemsCount — счётчик элементов подзапросом, состав не материализуется); плюс общее число подборок владельца тем же условием.
  4. Результат. Модель ListMySelectionsQueryResult (страница + TotalCount + echo Page/PageSize) → 200. Freshness: read-your-writes из основной БД (реплик нет, ADR-0056).

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
UNAUTHORIZED401Нет/просрочен access-токенretry после refresh
VALIDATION_ERROR400Page/PageSize вне диапазона«Некорректные параметры страницы»no retry

Предусловия и постусловия

  • Предусловия: актор авторизован (валидный JWT).
  • Постусловия: состояние не меняется (безопасное чтение); возвращается страница только собственных не удалённых подборок актора (INV-SEL-002/009), отсортированная CreatedAt desc; TotalCount считает подборки по тому же предикату (≤ 100, INV-SEL-007); данные актуальны (read-your-writes из основной БД, реплик нет — ADR-0056).

Acceptance Criteria

  • Given владелец с 3 не удалёнными подборками, When GET без параметров, Then 200, Items — 3 подборки по убыванию CreatedAt, Page = 1, PageSize = 20, TotalCount = 3; у каждого элемента есть Id, PublicCode, ItemsCount, Source.
  • Given владелец с 25 подборками, When GET?page=2&pageSize=20, Then 200, Items — оставшиеся 5, TotalCount = 25.
  • Given владелец только что создал подборку, When GET, Then новая подборка присутствует сразу (read-your-writes), даже если событие selection-created ещё не доехало до истории действий.
  • Given у владельца есть soft-deleted подборка, When GET, Then она не входит ни в Items, ни в TotalCount (INV-SEL-009).
  • Given владелец без подборок, When GET, Then 200, Items = [], TotalCount = 0.
  • Given pageSize = 0 или page = 0, When GET, Then 400 VALIDATION_ERROR.
  • Given чужие подборки в системе, When GET, Then возвращаются только подборки актора — чужие не видны (record-level по OwnerUserId, INV-SEL-002).
  • Given отсутствующий/просроченный токен, When GET, Then 401 UNAUTHORIZED.

Совместимость и наблюдаемость

  • Новые optional поля ответа — additive; удаление/переименование/смена типа — breaking (migration + rollout).
  • Logs: UserId, Page, счётчик результата (без Title/Description); metrics: selections_list_total{result}; traces: span чтения списка. Dashboard — selections-overview; runbook — не применяется (read-only).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы