From 37203aee1b955ad77dad7f862b0410d599d8b399 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 22 May 2026 16:49:54 +0300 Subject: [PATCH] =?UTF-8?q?H-3657:=20=D0=B7=D0=B0=D0=B4=D0=BE=D0=BA=D1=83?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20?= =?UTF-8?q?/file/prepare-upload=20=D0=B8=20/file/finish-upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Без этих эндпоинтов excel-import flow для wallet-транзакций неполный — интегратор не понимает откуда взять file_id для POST /admin/wallet-transaction/excel-processing. - Новый тег `file` (Methods for uploading files via signed-URL...). - POST /file/prepare-upload: создаёт File-запись + возвращает signedUrl для PUT-загрузки бинарника напрямую в S3. - POST /file/finish-upload?id=: помечает файл как UPLOADED, после чего file_id можно использовать в downstream-флоу. - Подробная описание двухшагового флоу (prepare → PUT signedUrl → finish) в описании prepare-upload. - Идемпотентность через повторный prepare-upload с тем же id. - Схемы File (полная), FilePrepareUploadBody, FilePrepareUploadResponse. - В WalletExcelFormBody.file_id ссылка на этот upload-флоу — раньше было просто «UUID предварительно загруженного xlsx». Co-Authored-By: Claude Opus 4.7 (1M context) --- v2/swagger.yaml | 189 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 1 deletion(-) diff --git a/v2/swagger.yaml b/v2/swagger.yaml index 2655a4d..29ac15b 100644 --- a/v2/swagger.yaml +++ b/v2/swagger.yaml @@ -71,6 +71,8 @@ tags: description: Methods for polling async file processing (excel import/export, archives). Scoped to current user's files. - name: user-list-cache description: Methods for caching ad-hoc employee selections to pass to mass operations (e.g. wallet credit/debit). + - name: file + description: Methods for uploading files via signed-URL (S3-style two-step flow). Required before any flow that consumes file_id (e.g. wallet excel import). - name: user-api-token description: Methods for managing personal API tokens (PATs) for server-to-server access. Requires "api-tokens-manage" permission. - name: admin-user-api-token @@ -7482,6 +7484,89 @@ paths: schema: $ref: '#/components/schemas/FileProcessing' + /file/prepare-upload: + post: + tags: [file] + summary: Зарегистрировать файл и получить signed URL для загрузки + description: | + Первый шаг двухступенчатой загрузки. Создаёт запись `File` в БД и + возвращает временный signed-URL для PUT-загрузки бинарника напрямую + в объектное хранилище (S3-совместимое). + + ### Полный флоу + + 1. `POST /api/v2/file/prepare-upload` с метаданными файла → получаем + `{File, uploadInfo:{signedUrl}}`. + 2. `PUT ` с телом-бинарником и `Content-Type` файла — + идёт прямо в S3, минуя HRBox. + 3. `POST /api/v2/file/finish-upload?id=` — помечает запись + как загруженную (`file_status_id` → UPLOADED). После этого + `File.id` можно использовать в любом флоу, где требуется + `file_id` (например `POST /admin/wallet-transaction/excel-processing`). + + ### Возможные ошибки + + Если файл не прошёл валидацию (тип, размер) — ответ + `{error:{message, validation:{...}}}`. На уровне антивируса — + `UnsafeFileException` 4xx. + + ### Идемпотентность + + Если передать существующий `id` в body — вернётся прежняя запись + и новый `signedUrl` (если файл ещё не uploaded). Если файл уже + uploaded — `uploadInfo` будет отсутствовать. + operationId: filePrepareUpload + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FilePrepareUploadBody' + responses: + 200: + description: Файл зарегистрирован, ссылка на загрузку готова + content: + application/json: + schema: + $ref: '#/components/schemas/FilePrepareUploadResponse' + + /file/finish-upload: + post: + tags: [file] + summary: Подтвердить завершение загрузки файла + description: | + Второй шаг загрузки. Помечает запись `File` как UPLOADED после того, + как клиент успешно загрузил бинарник по signedUrl из + `prepare-upload`. До вызова этого метода `file_id` нельзя + использовать в downstream-флоу (например, при попытке + `wallet-transaction/excel-processing` с не-UPLOADED файлом форма + отклонит запрос с ошибкой «Ошибка загрузки файла»). + + ### Параметр `id` + + Принимается из query (`?id=`) или из тела (`{id: }`). + operationId: fileFinishUpload + parameters: + - name: id + in: query + required: true + schema: { type: string, format: uuid } + description: UUID файла, полученный из `prepare-upload`. + responses: + 200: + description: Файл помечен как загруженный + content: + application/json: + schema: + type: object + properties: + File: + $ref: '#/components/schemas/File' + 400: + description: Не передан id или невалидный UUID. + 404: + description: Файл с таким id не найден. + /user-list-cache/set-user-list: post: tags: [user-list-cache] @@ -12719,7 +12804,12 @@ components: file_id: type: string format: uuid - description: ID предварительно загруженного xlsx-файла + description: | + UUID предварительно загруженного xlsx-файла. Загружается двумя + шагами через `/file/prepare-upload` → PUT по signedUrl → + `/file/finish-upload`. Файл должен быть в статусе UPLOADED + (`file_status_id = 5`), иначе сервер вернёт ошибку + «Ошибка загрузки файла». mappings: type: array description: | @@ -12920,6 +13010,103 @@ components: - $ref: '#/components/schemas/FileProcessing' nullable: true + File: + type: object + description: Запись файла в HRBox. Полная схема (используется в prepare/finish-upload и nested-объектах). + properties: + id: { type: string, format: uuid } + name: { type: string, description: 'Оригинальное имя файла, например `wallet_import.xlsx`.' } + ext: { type: string, description: 'Расширение без точки.' } + fid: + type: string + description: 'Внутренний идентификатор файла в хранилище (обычно `.`).' + url: + type: string + format: uri + description: 'Публичный URL вида `https:///file/open/`.' + file_type: + type: string + enum: [image, video, document, archive, other] + mime_type: { type: string } + size: + type: integer + description: Размер в байтах. + file_status_id: + type: integer + description: | + Статус загрузки: + * `1` — PREPARED (создан, бинарник не загружен) + * `5` — UPLOADED (готов к использованию) + * другие значения — внутренние состояния обработки + is_public: { type: boolean } + is_common: { type: boolean } + is_system: { type: boolean } + meta: + type: object + nullable: true + description: 'Произвольные метаданные (width/height для картинок и т.п.).' + created_at: { type: string } + user_id: + type: string + format: uuid + description: UUID юзера-загрузчика. + chat_channel_id: + type: string + format: uuid + nullable: true + + FilePrepareUploadBody: + type: object + required: [name, ext, size, file_type] + properties: + name: + type: string + example: "wallet_import.xlsx" + ext: + type: string + example: "xlsx" + size: + type: integer + description: Размер в байтах. + example: 5120 + file_type: + type: string + enum: [image, video, document, archive, other] + example: document + mime_type: + type: string + example: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + is_public: + type: integer + description: '`1` если файл должен быть доступен без авторизации (фото и т.п.), `0` — приватный.' + enum: [0, 1] + default: 0 + meta: + type: object + description: 'Произвольные метаданные (например `{"width":100,"height":100}` для картинок).' + id: + type: string + format: uuid + description: 'Опционально. Если передать существующий `id`, вернётся та же запись (идемпотентность).' + + FilePrepareUploadResponse: + type: object + properties: + File: + $ref: '#/components/schemas/File' + uploadInfo: + type: object + description: | + Информация для PUT-загрузки бинарника. Отсутствует, если файл + уже UPLOADED (повторный prepare-upload с тем же `id`). + properties: + signedUrl: + type: string + format: uri + description: | + Временный URL для PUT-запроса с телом-бинарником. + Не требует HRBox-авторизации — это прямая ссылка на S3. + UserApiTokenStatus: type: integer description: |