GET /api/v1/profile/persons/{personId}/documents — Список документов персоны (listPersonDocuments)
Назначение
Вернуть документы персоны: паспорта РФ/РБ/КЗ/СНГ/иностранные, ИНН, кастомные документы. Значения полей
хранятся шифрованными целиком в PayloadCipher (person-document.md); для
владельца typed payload расшифровывается в памяти (документы ≤ 20, формы редактирования требуют
значений). Файлы сканов не возвращаются — только метаданные ScanList; скачивание — presigned-ссылка
(get-document-scan-link.md). Вызывает инвестор-владелец персоны.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | profile |
| API area | public |
| Feature group | documents |
| Статус | draft |
| Документ | docs/06-services/profile/api/public/list-person-documents.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Bearer JWT), UserType = Investor |
| Auth policy / Permissions | authenticated; permission profile.documents.view-self |
| Scope (RBAC) | self |
| Record-level | PersonEntity.OwnerUserId = UserId; чужой personId → 404 |
| Tenant isolation | не применяется (ADR-0052) |
| Audit | read-sensitive: self-расшифровка payload не аудируется отдельно; значения в логи не пишутся |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/profile/persons/{personId}/documents |
| Success status | 200 |
| Idempotency header | не применяется |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
personId | route | Guid | Да | NotEmpty; персона владельца | чужая → 404 |
includeArchived | query | bool | Нет (default false) | true/false | — |
Модель ответа PersonDocumentListModel (container)
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
items | List<PersonDocumentModel> | Да (может быть пустым) | child-коллекция PersonEntity | — | Порядок: Type (паспорта → ИНН → кастомные), затем CreatedAt asc. |
totalCount | int | Да | computed | — | Всего в выборке. |
activeCount | int | Да | computed | — | Для UI-лимита (20, INV-DOC-8). |
Модель PersonDocumentModel
Дом полей и typed-моделей payload — person-document.md; здесь проекция.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
id | Guid | Да | PersonDocumentEntity.Id | — | — |
personId | Guid | Да | PersonDocumentEntity.PersonId | — | — |
type | PersonDocumentType | Да | PersonDocumentEntity.Type | — | RfPassport/RbPassport/KzPassport/CisPassport/ForeignPassport/Inn/Custom. |
status | PersonDocumentStatus | Да | PersonDocumentEntity.Status | — | Active/Archived. |
countryId | short | Да | PersonDocumentEntity.CountryId | — | Ссылка на справочник стран handbook. |
countryIsoCode2 | string | Да | PersonDocumentEntity.CountryIsoCode2 | — | char(2) — денормализованный код страны, снятый при записи документа. |
title | string | Да | PersonDocumentEntity.Title | — | «Паспорт РФ •• 1234» / пользовательский для Custom. |
numberMask | string | Да | PersonDocumentEntity.NumberMask | это и есть маска | Для списков; полный номер — в payload. |
issueDate | DateOnly? | Нет | PersonDocumentEntity.IssueDate | — | Открытая копия. |
expiresAt | DateOnly? | Нет | PersonDocumentEntity.ExpiresAt | — | Для подсветки «истекает». |
payloadContractVersion | int | Да | PersonDocumentEntity.PayloadContractVersion | — | Версия typed-модели. |
payload | typed-модель по type | Да | расшифровка PayloadCipher → десериализация | только self; в логах — никогда | RfPassportModel/RbPassportModel/KzPassportModel/CisPassportModel/ForeignPassportModel/InnModel/CustomDocumentModel — поля см. дом. |
scans | List<DocumentScanModel> | Да (может быть пустым) | ScanList.Items | — | fileId (Guid), fileName (string ≤256), contentType (whitelist), sizeBytes (long), uploadedAt (DateTimeOffset) — дом DocumentScan. |
createdAt / updatedAt | DateTimeOffset | Да | сущность | — | — |
version | long | Да | PersonDocumentEntity.Version | — | Для optimistic-обновлений. |
Алгоритм
- Контекст и доступ. Операцию выполняет инвестор (
UserIdиз токена); tenant не применяется; требуется правоprofile.documents.view-self. - Проверка входа.
personIdзадан. Нарушение →ValidationError. - Проверка предусловий и выборка. Профиль владельца существует; иначе
PROFILE_NOT_FOUND. Тип пользователя —Investor(иначеPROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE, INV-PRF-4). ПерсонаpersonIdсуществует и принадлежит владельцу; иначеPROFILE_PERSON_NOT_FOUND. Из документов персоны отбираются активные, а приincludeArchived = true— и архивные. - Маппинг с расшифровкой. Для каждого документа: расшифровать
PayloadCipherи десериализовать строго в typed-модель поType(INV-DOC-1); приPayloadContractVersion< актуальной — lazy-миграция модели в памяти (additive-правило дома). НеизвестныйType— fail-fast ошибка данных (не бизнес-ошибка). Недоступность ключей — fail-closedPROFILE_CRYPTO_UNAVAILABLE. Спроецировать по таблицеPersonDocumentModel; сортировкаType, затемCreatedAtasc. - Результат. Модель
PersonDocumentListModel; полный payload — только владельцу (record-level). HTTP200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Внешние вызовы | провайдер ключей шифрования для расшифровки payload документов; недоступность → PROFILE_CRYPTO_UNAVAILABLE |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
| — (auth) | 401 | Нет/просрочен JWT | без тела | re-login |
ValidationError | 400 | Пустой personId | перечень невалидных полей | no retry |
PROFILE_NOT_FOUND | 404 | Профиль не создан | «Профиль ещё создаётся» | retry |
PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE | 403 | не Investor | «Доступно только инвесторам» | no retry |
PROFILE_PERSON_NOT_FOUND | 404 | Нет персоны / чужая | «Персона не найдена» | no retry |
PROFILE_CRYPTO_UNAVAILABLE | 503 | Ключи недоступны | «Сервис временно недоступен» | retry |
Совместимость и наблюдаемость
- Payload-модели меняются только additive (рост
PayloadContractVersion, дом person-document.md); новыеtype— additive для списка (клиент скрывает неизвестные типы). - Logs:
UserId,PersonId,totalCount,DocumentId+Type(без значений payload); metrics:profile_api_operations_total{operation="listPersonDocuments",result},profile_crypto_errors_total; traces — OTel.
Acceptance Criteria
- Given персона владельца с 3 активными и 1 архивным документами, When
GETincludeArchived=false, Then200;items— 3 активных, порядокType(паспорта → ИНН → кастомные) затемCreatedAtasc;payloadкаждого расшифрован в typed-модель поType; сканы — только метаданные (без содержимого/ссылок);totalCount/activeCountвычислены. - Given
includeArchived = true, WhenGET, Then в выборку включены и архивные документы. - Given у персоны нет документов, When
GET, Then200с пустымitems. - Given
PayloadContractVersion< актуальной, WhenGET, Then lazy-миграция модели в памяти (additive), без ошибки. - Given неизвестный
Typeв хранилище, WhenGET, Then fail-fast ошибка данных (не бизнес-ошибка). - Given ключи шифрования недоступны, When
GET, ThenPROFILE_CRYPTO_UNAVAILABLE(503) без частичного ответа. - Given
personIdчужой, WhenGET, ThenPROFILE_PERSON_NOT_FOUND(404). - Given
UserType != Investor/ профиль не создан, WhenGET, ThenPROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE(403) /PROFILE_NOT_FOUND(404) соответственно. - Given любой успешный ответ, Then полный
payloadвиден только владельцу (record-level) и никогда не логируется.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (7): DELETE /api/v1/profile/persons/{personId}/documents/{documentId}/scans/{fileId} — Удаление скана документа (removeDocumentScan), GET /api/v1/profile/persons/{personId} — Получение персоны (getPerson), POST /api/v1/profile/persons/{personId}/documents — Добавление документа персоны (addPersonDocument), POST /api/v1/profile/persons/{personId}/documents/{documentId}/archive — Архивация документа персоны (archivePersonDocument), POST /api/v1/profile/persons/{personId}/documents/{documentId}/scans — Прикрепление скана документа (attachDocumentScan), Profile API — реестр операций областей public / internal / admin, PUT /api/v1/profile/persons/{personId}/documents/{documentId} — Обновление документа персоны (updatePersonDocument)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: person-document.md (typed-модели payload — дом).
- Операции: add-person-document.md, update-person-document.md, attach-document-scan.md, get-document-scan-link.md.