GET /api/v1/selections — Мои подборки
Список подборок текущего владельца (инвестора или агента): название, счётчик объектов недвижимости,
публичный код для публичной ссылки, даты.
Назначение
Владелец открывает страницу «Мои подборки» и видит все свои не удалённые подборки, отсортированные по
дате создания (новые сверху). Дом полей — ../domain/selection.md.
| Поле | Значение |
|---|
| Сервис/контекст | selections |
| API area | public |
| Feature group | selections |
| Статус | draft |
| Документ | docs/06-services/selections/api/public/list-my-selections.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (JWT; гость списка не имеет — подборка привязана к владельцу) |
| Auth policy / Permissions | authenticated user; отдельных permissions нет (self-scope) |
| Scope (RBAC) | self: только собственные подборки |
| Record-level | OwnerUserId == actor.UserId (INV-SEL-002) — обязательный предикат выборки |
| Tenant isolation | не применяется — сущность вне tenant (ADR-0052 п.2) |
| Audit | none (чтение собственных данных) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/selections |
| Success status | 200 |
| Idempotency header | не применяется (безопасный read) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (sub) | Guid | Да | Required; отсутствие → 401 | record-level фильтр |
Page | query | int | Нет (default 1) | >= 1 | — |
PageSize | query | int | Нет (default 20) | 1..100 | — |
Body отсутствует.
Модель ответа ListMySelectionsQueryResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Items | IReadOnlyList<MySelectionListItemModel> | Да (может быть пуст) | проекция Filter | — | Страница подборок. |
TotalCount | int | Да | Count того же предиката | — | Всего не удалённых подборок владельца (≤ 100, INV-SEL-007). |
Page | int | Да | echo запроса | — | Текущая страница. |
PageSize | int | Да | echo запроса | — | Размер страницы. |
MySelectionListItemModel
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | Selection.Id | — | Для мутаций (PUT/DELETE) и ссылок CRM. |
Title | string | Да | Selection.Title | — | Название. |
Description | string? | Нет | Selection.Description | — | Описание. |
PublicCode | string | Да | Selection.PublicCode | — | Код публичной ссылки /collection/{publicCode}. |
Source | string (enum SelectionSource) | Да | Selection.Source | — | Manual / Favorites. |
ItemsCount | int | Да | computed: Count(SelectionItem where SelectionId == Id) в проекции | — | Объектов недвижимости в подборке (0..200). |
CreatedAt | DateTimeOffset | Да | Selection.CreatedAt | — | Создание. |
UpdatedAt | DateTimeOffset | Да | Selection.UpdatedAt | — | Последняя правка. |
OwnerUserId в ответ не включается (равен актору); версия для оптимистической блокировки не нужна —
конфликты разрешаются на мутациях (409 → перечитать).
Алгоритм
- Контекст и доступ. Операцию выполняет авторизованный пользователь; возвращаются только его
собственные подборки (по владельцу-актору, INV-SEL-002).
- Проверка входа и предусловий.
Page >= 1, PageSize ∈ 1..100 — иначе VALIDATION_ERROR.
- Что читаем. Страница подборок владельца, не удалённых (
DeletedAt == null), в порядке
CreatedAt desc (индекс ix_selections_owner_created), с проекцией MySelectionListItemModel
(ItemsCount — счётчик элементов подзапросом, состав не материализуется); плюс общее число
подборок владельца тем же условием.
- Результат. Модель
ListMySelectionsQueryResult (страница + TotalCount + echo Page/PageSize)
→ 200. Freshness: read-your-writes из основной БД (реплик нет, ADR-0056).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
UNAUTHORIZED | 401 | Нет/просрочен access-токен | — | retry после refresh |
VALIDATION_ERROR | 400 | Page/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).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы