---
title: "RingFileBase (API об’єктного сховища)"
description: "Власний API завантаження для клонів Ring — file() → RingBaseAdapter → MinIO/RGW, похідні {fileId}_v_*, географія push-only"
locale: "uk"
---
# RingFileBase (API об’єктного сховища)

> **Info**
> Використовуйте вкладки **Founder** / **Developer** на бічній панелі документації, щоб відфільтрувати цю сторінку. Поєднуйте її з [Ring CDN](/docs/integrations/ring-cdn.md) для публічної доставки завантажених об’єктів. [Ring File Cabinet](/docs/features/file-cabinet.md) є повноцінним споживачем `file()` (`getCabinetStorageConfig()`, gallery `derivativesProfile`).

**RingFileBase** — це власний контур об’єктного сховища Ring: Express API (`ring-filebase-api`), який приймає автентифіковані multipart-завантаження та записує їх у **S3-сумісне** сховище (MinIO або Ceph RGW). Застосунки Ring ніколи не звертаються до MinIO з браузера — вони викликають `file().upload(...)`, а той вибирає `RingBaseAdapter`, коли провайдер сховища — `ring_filebase`.

| Аспект | RingFileBase (ця сторінка) | Ring CDN |
|---------|--------------------------|----------|
| Роль | Автентифікований API **запису** | Публічний edge **читання** |
| Prod URL | In-cluster `http://ring-filebase-api.ring-filebase.svc.cluster.local` | `https://cdn.` |
| Dev / CI URL | Публічний `https://filebase-api.ring-platform.org` (лише k3s-or або primary) | Той самий CDN (кілька A-edge; master **push** до slave ingest) |
| Клієнт | Server-side `file()` / Server Actions | Браузери, ``, OG-теги |
| Auth | Bearer token (`RINGBASE_API_TOKEN`) | Немає (GET `/files/...`) |

**Джерело істини:** записи завжди йдуть на **k3s-or** (Oregon). Потім API **надсилає** об’єкти до ingest endpoint-ів slave MinIO (Finland / Ukraine). Slave ніколи не забирають дані з master; pull CronJob-и вимкнено.

## SSOT провайдера (спільний)

Порядок визначення в `lib/storage/storage-config.ts` (віддзеркалює `db()`):

1. `NEXT_PUBLIC_STORAGE_PROVIDER` або `STORAGE_PROVIDER`
2. `ring-config.json` → `storage.provider`
3. За замовчуванням: `local_storage` (розробка) / `vercel_blob` (продакшн)

| Значення провайдера | Адаптер | Коли використовувати |
|----------------|---------|-------------|
| `local_storage` | `LocalStorageAdapter` | Локальний `npm run dev`, завантаження на PVC |
| `vercel_blob` | `VercelAdapter` | Клони на Vercel із `BLOB_READ_WRITE_TOKEN` |
| `ring_filebase` | `RingBaseAdapter` | Self-hosted / Ringdom k8s з RingFileBase API |
| `firebase_storage` | У selector повертається local | Наразі не є повноцінним шляхом завантаження |

### For founders

## Чому це важливо для вашого клону

Зображення продуктів, KYC-скани, вкладення чатів і результати ImageConductor потребують надійного місця зберігання. **Vercel Blob** підходить для невеликих OSS-демо; маркетплейсу, який володіє своїми даними, зазвичай потрібен **RingFileBase**, щоб медіа залишалися у вашому кластері, за вашим CDN-хостом і без рахунків Blob за кожен GB.

### Типові сценарії

  
- **[Локальна розробка](/docs/deployment/environment.md)** — Залиште `storage.provider` / env рівним `local_storage` — файли потраплятимуть у `public/uploads` (або на ваш PVC mount).

  
- **[Продакшн-маркетплейс](/docs/features/store.md)** — Задайте `ring_filebase`, спрямовуйте `RINGBASE_API_URL` на внутрішньокластерний API, публікуйте URL через Ring CDN (див. Related нижче).

  
- **[File Cabinet](/docs/features/file-cabinet.md)** — Завантаження учасників викликають `file()` з MIME-лімітами cabinet/25MB; елементи публічної галереї повторно використовують CDN `/files/{uuid}`.

  
- **[Керований хостинг Ringdom](/docs/development/oss-vs-enterprise.md)** — Оператори розгортають namespace-и `ring-filebase` + `ring-filebase-minio`; вашому клону потрібне лише підключення ConfigMap/Secret.

  
- **[Поєднання з резервним копіюванням](/docs/deployment/backup.md)** — Дампи Postgres не містять об’єктів MinIO — плануйте резервні копії object store окремо від `pg_dump`.

  Хости `api.` часто маршрутизуються на ingress застосунку Ring. Завантаження мають потрапляти до **ring-filebase-api** (внутрішньокластерно) або **https://filebase-api.ring-platform.org** (публічний запис на primary) — ніколи не до розгортання Next.js. Неправильний URL повертає HTML або HTTP 500 замість JSON з успіхом/fileId/url.

### For developers

## Архітектура

```mermaid
flowchart LR
  App["Ring app\nfile().upload"] --> Adapter["RingBaseAdapter"]
  Adapter -->|"POST /api/v1/upload\nBearer token"| API["ring-filebase-api"]
  API --> Primary["Primary MinIO"]
  API --> Outbox["Durable outbox"]
  Outbox -->|"PutObject / DeleteObject"| SlaveFI["Slave ingest FI"]
  Outbox -->|"PutObject / DeleteObject"| SlaveUA["Slave ingest UA"]
  CDN["cdn.host/files/..."] --> Primary
  CDN --> SlaveFI
  CDN --> SlaveUA
```

### Перевірені модулі

| Шлях | Роль |
|------|------|
| `lib/storage/storage-config.ts` | SSOT провайдера + `getStorageConfig()` |
| `lib/file/FileService.ts` | `file()` / `fileService.upload` |
| `lib/file/FileSelector.ts` | Мапа backend-ів; створює `RingBaseAdapter` із env |
| `lib/file/adapters/RingBaseAdapter.ts` | Multipart upload, delete, metadata, `deriveDerivatives` |
| `lib/images/derive-webp.ts` | Необов’язковий WebP sibling; `storage.webpDerivative.provider=ringbase` |
| `lib/uploads/server/upload-core.ts` | Уніфіковане завантаження, яке використовує `POST /api/uploads` |
| `app/api/uploads/route.ts` | Точка входу browser/form upload |
| `infrastructure/ring-file-base/services/ring-filebase-api/` | Джерело API (derivatives + propagate) |
| `infrastructure/ring-file-base/k8s/ring-filebase/10-api-deployment.yaml` | Шаблон розгортання |
| `infrastructure/k3s-or/ring-filebase/60-propagate-slaves-configmap.yaml` | Реєстр slave ingest |

### Змінні середовища

| Змінна | Потрібна для `ring_filebase` | Примітки |
|----------|------------------------------|-------|
| `NEXT_PUBLIC_STORAGE_PROVIDER` | Так | Задайте `ring_filebase` (псевдоніми: `ringbase`, `filebase`) |
| `RINGBASE_API_URL` | Так | Host або host + `/api/v1`; адаптер нормалізує до `…/api/v1` |
| `RINGBASE_API_TOKEN` | Так | Bearer для middleware автентифікації API |
| `RINGBASE_PUBLIC_URL` | Рекомендовано | Публічна база для URL у відповідях (зазвичай origin CDN) |
| `NEXT_PUBLIC_RINGBASE_API_URL` | Необов’язково | Fallback, якщо `RINGBASE_API_URL` не задано |
| `BLOB_READ_WRITE_TOKEN` | Ні | Лише для `vercel_blob` |
| `NEXT_PUBLIC_LOCAL_STORAGE_URL` | Ні | Лише для `local_storage` |

Приклад `ring-config.json`:

{`{
  "storage": {
    "provider": "ring_filebase",
    "maxFileSize": "10MB"
  }
}`}

Шаблон k8s ConfigMap / Secret (див. `k8s/secrets.example.yaml`):

{`# Prod ConfigMap on k3s-or
RINGBASE_API_URL=http://ring-filebase-api.ring-filebase.svc.cluster.local
RINGBASE_PUBLIC_URL=https://cdn.ring-platform.org
NEXT_PUBLIC_STORAGE_PROVIDER=ring_filebase
# RINGBASE_API_TOKEN from Secret — never commit production tokens

# Local next / CI (.env.local)
RINGBASE_API_URL=https://filebase-api.ring-platform.org
RINGBASE_PUBLIC_URL=https://cdn.ring-platform.org
NEXT_PUBLIC_STORAGE_PROVIDER=ring_filebase`}

### Мапа типів завантаження (`resolveRingBaseUploadType`)

API перевіряє MIME-типи для поля `type`. Адаптер мапить:

| Вміст / доступ | `type`, який надсилається до API |
|------------------|--------------------|
| `access: 'private'` | `document` |
| `image/*` | `image` (повноцінний тип; `product` — застарілий псевдонім) |
| `video/*` або `audio/*` | `media` |
| Усе інше | `other` |

Необов’язкові перевизначення: `ringbaseType`, `derivativesProfile` (`none` | `thumb` | `gallery` | `product` | `news`).

### Схема ключів похідних (SSOT)

| Шаблон | Статус |
|---------|--------|
| `{fileId}_v_{variant}` наприклад `_v_thumb.webp`, `_v_blur.webp`, `_v_sync_thumb.jpg` | **Поточна** — єдина схема, яку записує API |
| `{fileId}_v_video_frame_N.jpg` / `_v_video_frame_N_480.webp` | **Поточна** — асинхронні HEIC/video frames (`_480.webp` під `_v_` навмисно) |
| `{fileId}_thumb`, `{fileId}_thumb_v{N}`, `{fileId}_thumb_v{N}_480w.webp` | **Заборонена** — ніколи не записуйте; очищайте orphan-и через `infrastructure/ring-file-base/scripts/purge-legacy-derivative-keys.sh` |

Для raster-файлів надавайте перевагу upload `type=image` (`product` — застарілий псевдонім → `image`). Poll статусу thumbnail: `GET /api/v1/files/{fileId}/thumbnail?type=image`.

**Підключення застосунку (2026-07-15):** `upload-core` і `/api/uploads` використовують request ladder, коли `storage.provider` дорівнює `ring_filebase` (purpose→profile: news→`news`, product media→`product`, nft/chat images→`gallery`, avatar/logos→`thumb`, docs→`none`). Відповіді містять `fileId` + `derivatives`. Зберігайте їх як `MediaImageAsset` у news (`featuredImageAsset` / `gallery`) і `GalleryItem.derivatives` у store/NFT. Відображайте через `pickImageSrc` / `pickGalleryDisplayUrl`. Застарілу подвійну сумісність Vikka (`vikka-field-normalize` / `NEXT_PUBLIC_NEWS_VIKKA_COMPAT`) **видалено** — лише camelCase JSONB.

Надсилання зображень як `media` не проходить валідацію (A/V + HEIC дозволені лише чинними правилами API).

  Виконайте dry-run на primary, потім на slave FI/UA: `./scripts/purge-legacy-derivative-keys.sh --endpoint https://… --bucket ring-filebase`. Додавайте `--delete` лише після перегляду зразка списку. Команда не зачіпає навмисні `_v_video_frame_*_480.webp` або `_v_thumb.webp`.

### Кроки підключення

### Виберіть провайдера

Задайте env та/або `storage.provider` у `ring-config.json` рівним `ring_filebase`. Після змін env перезапустіть Next.js.

### Розгорніть API + MinIO (кластер)

Застосуйте маніфести RingFileBase з `infrastructure/ring-file-base/k8s/ring-filebase/` (API Deployment/Service, namespace MinIO, CDN stack). Переконайтеся, що API може дістатися `minio-service.ring-filebase-minio.svc.cluster.local:9000`, а цільовий bucket існує.

### Випустіть Bearer token

Автентифікація API (`services/ring-filebase-api/src/middleware/auth.ts`) очікує JWT, чиї **декодовані** claims містять:

- `iss` = `kubernetes/serviceaccount`
- `kubernetes.io/serviceaccount/namespace`
- `kubernetes.io/serviceaccount/service-account.name`

Сьогодні middleware **декодує** claims і не викликає TokenReview та не перевіряє `exp`. Надавайте перевагу довгоживучому токену з такими claims у Secrets — **не** вставляйте одногодинний projected ServiceAccount JWT у `RINGBASE_API_TOKEN`, забуваючи про його строк дії.

### Smoke-тест із pod застосунку

{`curl -sS http://ring-filebase-api.ring-filebase.svc.cluster.local/api/v1/health

curl -sS -X POST http://ring-filebase-api.ring-filebase.svc.cluster.local/api/v1/upload \
  -H "Authorization: Bearer $RINGBASE_API_TOKEN" \
  -F "file=@./sample.png;type=image/png" \
  -F "type=image" \
  -F "derivatives=gallery"`}

Очікуйте JSON із `success: true`, `fileId`, `url` і необов’язковими `derivatives` під вашим CDN-хостом. Протягом кількох секунд HEAD того самого ключа на slave ingest має повернути 200 (master push).

### Завантаження на рівні застосунку

{`import { file } from '@/lib/file/FileService'

const result = await file().upload('avatar.png', buffer, {
  contentType: 'image/png',
  access: 'public',
})
if (!result.success) throw new Error(result.error)
// result.url → https://cdn./files/...`}

Browser forms мають використовувати `POST /api/uploads` (уніфіковане upload core), а не викликати RingFileBase напряму.

### Типові помилки

| Симптом | Ймовірна причина |
|---------|----------------|
| HTML / 500 від URL завантаження | `RINGBASE_API_URL` вказує на Next.js (`api.` ingress) |
| `401 Invalid ServiceAccount token` | Відсутня legacy SA claim shape у токені |
| `Invalid file type … got: image/png` | `type=media` замість `image` / `other` |
| Завантаження успішне, CDN на slave короткочасно повертає 404 | Рідкісна race до push; edge мають зберігати `proxy_cache_valid 404 0` |
| Завантаження успішне, CDN повертає 403 | У bucket MinIO немає анонімного `GetObject` (або неправильно налаштовано CDN Host header) |
| Node `stream.isDisturbed` для Blob | Надавайте перевагу `local_storage` або `ring_filebase`; Vercel adapter завантажує `Uint8Array` |
| Propagate завис у `failed` | `POST /api/v1/replication/redrive` на primary (максимум 3 повтори, потім зупинка) |

## Пов’язана документація

- [integrations/ring-cdn](/docs/integrations/ring-cdn.md) — Наступний крок: публічний edge /files/..., мапа host→bucket і географічна доставка завантажених об’єктів.

- [features/file-cabinet](/docs/features/file-cabinet.md) — Той самий процес: завантаження учасника /file-cabinet через file() + getCabinetStorageConfig; галерея використовує derivativesProfile gallery; subscriber+ /profile/shared — лише ACL (без власних завантажень).

- [deployment/environment](/docs/deployment/environment.md) — Залежність: блок env сховища (RINGBASE_*) і передумови generative conductor.

- [deployment/backup](/docs/deployment/backup.md) — Див. також: object store — окремий ресурс резервного копіювання від Postgres.

- [integrations](/docs/integrations.md) — Див. також: повний хаб каталогу інтеграцій.
