Работа с файлами

Condo API предоставляет безопасный способ работы с файлами через REST эндпоинты /api/files, основанный на подписи (signature)

API работы с файлами

API загрузки файлов использует специальные REST эндпоинты (/api/files) и требует client_id, который ранее был зарегистрирован на файловом сервере. Этот подход обеспечивает лучшую безопасность, ограничение частоты запросов, а также возможности для переиспользования ранее загруженных файлов.

Начало работы

Для использования данного API вам необходим fileClientId. fileClientId — это уникальный идентификатор вашего приложения, предоставляемый вашим партнером.
На клиентской стороне вам нужен только fileClientId (обычно доступен из next/config или переменных окружения). Вам не нужно знать о секрете или валидации подписи — это обрабатывается Condo API.
Если ваше приложение имеет собственный бэкенд и вам необходимо верифицировать подписи, полученные от Condo API, вы должны разместить переменную окружения FILE_SECRET на вашем бэкенде. FILE_SECRET используется для шифрования и дешифрования подписей метаданных файла.

Загрузка файлов

Используйте пакет @open-condo/files для загрузки файлов:
typescript
import { upload, buildMeta } from '@open-condo/files' import { getClientSideSenderInfo } from '@open-condo/miniapp-utils/helpers/sender' import getConfig from 'next/config' const { publicRuntimeConfig: { fileClientId } } = getConfig() async function uploadFile(file: File, userId: string, organizationId?: string) { const senderInfo = getClientSideSenderInfo() const uploadResult = await upload({ files: [file], meta: buildMeta({ userId, // ID пользователя, который будет привязывать файл к модели fileClientId, // client_id, который станет владельцем данного файла modelNames: ['Document'], // Модели, к которым можно прикрепить этот файл fingerprint: senderInfo.fingerprint, organizationId, // Организация пользователя, к которой будет привязан загруженный файл }), }) // uploadResult.files[0].signature содержит JWT подпись // Используйте эту подпись в вашей GraphQL мутации return uploadResult.files[0].signature }

Структура upload meta

Каждый запрос загрузки отправляет JSON-поле meta (его формирует buildMeta), в котором описаны контекст и ограничения для файла:
json
{ "dv": 1, "sender": { "dv": 1, "fingerprint": "<client-fingerprint>" }, "user": { "id": "<current-user-id>" }, "fileClientId": "<your-client-id>", "modelNames": ["Document", "..."], "organization": { "id": "<organization-id>" } }
  • dv — версия структуры данных (сейчас всегда 1)
  • sender — идентификатор клиента, инициировавшего запрос
  • user — пользователь, которому принадлежит файл
  • fileClientId — выданный вам идентификатор приложения
  • modelNames — список GraphQL моделей, которым разрешено использовать файл
  • organization — идентификатор организации, в рамках которой загружается файл
Если вы передаёте необязательное поле attach, оно должно выглядеть так:
json
{ "dv": 1, "sender": { "dv": 1, "fingerprint": "<client-fingerprint>" }, "modelName": "Document", "itemId": "<model-record-id>" }
Таким образом API может сразу прикрепить загруженный файл к указанной записи и вернуть publicSignature — зашифрованные метаданные, которые можно повторно использовать для обращения к файлу.

Как работает подпись

Каждому загруженному файлу соответствуют JWT-токены (signature и publicSignature), подписанные вашим FILE_SECRET алгоритмом HS256. Время жизни каждого токена — 5 минут (exp = iat + 300 секунд).
Пример полезной нагрузки для signature:
json
{ "id": "<storage-id>", "recordId": "<FileRecord.id>", "filename": "<generated-name>", "originalFilename": "<original name>", "mimetype": "<mime>", "encoding": "<encoding>", "meta": { "dv": 1, "sender": { "dv": 1, "fingerprint": "<string>" }, "user": { "id": "<uuid>" }, "fileClientId": "<client_id>", "modelNames": ["Document"], "sourceFileClientId": "<string|null>" }, "iat": 1700000000, "exp": 1700000300 }
publicSignature содержит тот же публичный набор метаданных, что и ответ эндпоинта /api/files/attach, поэтому его можно безопасно хранить в вашей базе данных.
Если вам нужно валидировать подпись на своём бэкенде:
  1. Сохраните FILE_SECRET в переменных окружения сервера
  2. Выполните jwt.verify(signature, process.env.FILE_SECRET, { algorithms: ['HS256'] })
  3. Проверьте расшифрованные данные (например, modelNames, user.id или fileClientId)
Никогда не передавайте FILE_SECRET в браузер: фронтенд использует только fileClientId и токены, полученные от Condo API.

Прикрепление файлов к моделям

После загрузки используйте signature в вашей GraphQL мутации:
graphql
mutation CreateDocument($data: DocumentCreateInput!) { obj: createDocument(data: $data) { id file { filename mimetype publicUrl } } }
Входные данные мутации должны включать:
json
{ "data": { "file": { "signature": "<jwt-signature-from-upload>", "originalFilename": "document.pdf" }, "name": "Мой документ", "organization": { "connect": { "id": "..." } } } }

Встроенное прикрепление

Вы можете прикрепить файл к модели во время загрузки в одном запросе:
typescript
import { upload, buildMeta } from '@open-condo/files' const uploadResult = await upload({ files: [file], meta: buildMeta({ userId, fileClientId, modelNames: ['Document'], fingerprint: senderInfo.fingerprint, organizationId, }), attach: { dv: 1, sender: { dv: 1, fingerprint: senderInfo.fingerprint }, modelName: 'Document', itemId: 'existing-document-id', }, }) // uploadResult.files[0].publicSignature можно использовать напрямую // Нет необходимости вызывать /api/files/attach отдельно

Совместное использование файлов

Поделитесь загруженным файлом с другим пользователем или приложением:
typescript
import { share } from '@open-condo/files' const shareResult = await share({ payload: { dv: 1, sender: { dv: 1, fingerprint: senderInfo.fingerprint }, id: 'existing-file-id', user: { id: 'target-user-id' }, fileClientId: 'target-app-id', modelNames: ['Document'], }, }) // shareResult.file.signature содержит новую подпись

Прикрепление существующих файлов

Прикрепите ранее загруженный файл к модели:
typescript
import { attach } from '@open-condo/files' const attachResult = await attach({ payload: { dv: 1, sender: { dv: 1, fingerprint: senderInfo.fingerprint }, modelName: 'Document', itemId: 'document-id', fileClientId, signature: 'upload-or-share-signature', }, }) // attachResult.file.signature содержит публичную подпись

Скачивание файлов

Файлы можно скачать двумя способами:
  1. Используя publicUrl из GraphQL ответа — при запросе модели с полем файла вы получаете publicUrl, который можно использовать напрямую:
graphql
query GetDocument($id: ID!) { obj: document(where: { id: $id }) { id file { filename mimetype publicUrl } } }
  1. Используя API эндпоинт файлов — скачайте файл напрямую, используя его ID и подпись:
GET /api/files/<file_id>?sign=<file_signature>
Подпись можно получить из ответа загрузки или из метаданных файла.

API эндпоинты

Новый flow использует следующие REST эндпоинты:
  • POST /api/files/upload — загрузка одного или нескольких файлов
  • POST /api/files/share — совместное использование существующего файла
  • POST /api/files/attach — прикрепление файла к модели
  • GET /api/files/<file_id>?sign=<signature> — скачивание файла