# Úložiště souborů boosteru

> Nahrávání, výpis, odkazy a mazání souborů booster panelu z vlastního serveru pomocí API klíče.

Booster panel může uchovávat soubory: dokumenty, které nahrají jeho uživatelé, faktury
a exporty vygenerované workflow, obrázky produktů. Endpointy na této stránce umožňují
vašemu serveru pracovat s těmito soubory pomocí API klíče: nahrát vygenerovaný report,
vypsat, co poslali návštěvníci, získat odkaz ke stažení, uklidit.

Endpointy jsou pod `/api/v3/booster/storage/private/`. Samotné panely se ke svým souborům
dostávají samostatnou přístupovou cestou, kterou tato stránka nepopisuje.

## Základní pojmy

### Složky a cesty

Soubory se adresují **cestou**: složka nejvyšší úrovně (**složka**), volitelné podsložky
a název souboru, oddělené `/`, například `invoices/2026/INV-0042.pdf`.

- Název složky smí obsahovat písmena, číslice, `_` a `-`.
- Názvy podsložek a souborů smějí obsahovat libovolné znaky kromě řídicích znaků
  a `/ \ : * ? " < > | # % '`; nesmějí být `.` ani `..` a nesmějí začínat ani končit mezerou.
- Cesta má aspoň dvě části (`<folder>/<name>`), nejvýše 8 úrovní a 512 znaků.
- Složky a podsložky se nevytvářejí zvlášť: existují, dokud obsahují nějaký soubor. Cesta
  je v rámci panelu jedinečná.

### Nastavení úložiště souborů

Vlastník panelu zapíná úložiště souborů a definuje složky v nastavení modulů panelu
v aplikaci. Pro každou složku (nebo `*` pro všechny ostatní) nastavení určuje, kdo smí
soubory nahrávat, číst, vypisovat, měnit a mazat, jaké typy souborů složka přijímá, jak
velký smí soubor být, kolik souborů smí složka obsahovat a zda se soubory po určitém počtu
dní mažou. Složka může poskytovat **trvalé veřejné odkazy** (pro obrázky zobrazené na
veřejné stránce).

### Kdo má přístup k souborům panelu

Požadavky se provádějí jménem uživatele, kterému API klíč patří. Tento uživatel musí panel
vlastnit (být jeho tvůrcem nebo členem skupiny, která ho vlastní), nebo mít panel sdílený,
přímo nebo prostřednictvím skupiny. API klíč musí nést odpovídající
[rozsah](authentication.md#rozsahy):

| Rozsah | Endpointy |
|---|---|
| `ayeto.booster.storage` | `list`, `count`, `get`, `url`, `usage` |
| `ayeto.booster.storage.write` | `upload`, `upload-b64`, `update`, `delete`, `delete-many` |

Rozsah pro zápis nezahrnuje rozsah pro čtení; klíč, který čte i zapisuje, potřebuje oba.
Endpointy fungují i tehdy, když je úložiště souborů panelu vypnuté, takže uložené soubory
můžete dál číst a uklízet.

Pro požadavky na této stránce se nastavení složek uplatní takto:

- Operace nastavená pro složku na **none** je zakázaná pro všechny, včetně tohoto API (`403`).
- Každá jiná úroveň povoluje požadavky API, a to nad všemi soubory složky.
- Složka bez nastavení (a bez položky `*`) je přístupná jen pro API a vlastníka.
- Přijímané typy souborů, limit velikosti souboru a limity počtu souborů a objemu dat ve
  složce se na nahrávání přes toto API **uplatní**.

### Soubory

| Pole | Typ | Popis |
|---|---|---|
| `id` | UUID | Identifikátor souboru. |
| `path` | řetězec | Celá cesta, např. `invoices/2026/INV-0042.pdf`. |
| `folder` | řetězec | Složka nejvyšší úrovně. |
| `name` | řetězec | Název souboru (poslední část cesty). |
| `size` | celé číslo | Velikost v bajtech. |
| `content_type` | řetězec | Typ obsahu. Známé formáty (PNG, JPEG, GIF, WebP, AVIF, PDF, běžné audio a video) se rozpoznají z obsahu a mají přednost před deklarovaným typem; rozpoznají se také HTML, SVG a XML. |
| `sha256` | řetězec | SHA-256 obsahu (hex). |
| `metadata` | objekt | Vaše data o souboru (JSON objekt, nejvýše 4 KB, klíče nesmějí začínat na `$` ani obsahovat `.`). |
| `record` | objekt nebo `null` | Záznam [booster databáze](booster-database.md), ke kterému soubor patří: `{collection, key}`. |
| `owner` | UUID nebo `null` | Uživatel panelu, který soubor nahrál; `null` u souborů nahraných přes API. |
| `created`, `updated` | celé číslo | Čas nahrání a poslední změny v milisekundách od epochy. |
| `expires_at` | řetězec nebo `null` | Kdy bude soubor smazán, ve složkách, které uchovávají soubory určitý počet dní. |
| `url` | řetězec nebo `null` | Odkaz, který soubor zobrazí nebo stáhne (viz [Odkazy ke stažení](#odkazy-ke-stazeni)). |
| `download_url` | řetězec nebo `null` | Odkaz, který soubor vždy stáhne. |

### Soubory a záznamy databáze

Soubor může patřit k záznamu [databáze](booster-database.md) panelu: pošlete kolekci
a klíč záznamu spolu s nahráním, nebo je nastavte později přes `update`. Záznam musí
v databázi panelu existovat. Ve výchozím stavu se soubor smaže, když se smaže jeho záznam;
nastavení složky ho může zachovat. Pokud panel potřebuje soubor najít podle záznamu, uložte
`id` (nebo `path`) souboru do hodnoty záznamu.

### Odkazy ke stažení

Odkazy jsou absolutní URL pod `/api/v3/booster/storage/file/` a nepotřebují žádné hlavičky,
takže fungují v `<img src>`, `<a href>` i z jiných serverů.

- Odkaz je **podepsaný** a platí 60 minut. Když ho potřebujete, vyžádejte si nový přes
  [Získání odkazů ke stažení](#ziskani-odkazu-ke-stazeni), místo abyste odkazy ukládali.
- Ve složkách s trvalými veřejnými odkazy `url` nikdy nevyprší. Přestane fungovat, když
  složka přestane poskytovat veřejné odkazy nebo se soubor smaže.
- Obrázky, audio, video a PDF se zobrazí v prohlížeči; ostatní typy (a každé
  `download_url`) se stáhnou. Soubory HTML, SVG a XML se stáhnou vždy.
- Odkazy na obrázky přijímají `?w=` a `?h=` (pixely) pro zmenšenou kopii, např. náhledy.
- Odkazy podporují požadavky `Range` (posouvání v audiu a videu).

### Limity

| Limit | Hodnota |
|---|---|
| Velikost souboru | limit složky, nejvýše 100 MB |
| Data na panel | limit panelu, ve výchozím stavu nejvýše 1 GB |
| Metadata | 4 KB JSON |
| Soubory vrácené jedním `list` | 100 bez stránkování, až 1000 s `limit_from` / `limit_to` |
| `ids` v `url` a `delete-many` | 100 |
| Nahrání v base64 | pro soubory větší než několik MB použijte `upload` (multipart), pro velké [přímé nahrání](#prime-nahrani-velkeho-souboru) |

Soubory se také započítávají do kvóty úložiště vlastníka panelu, nebo u panelů organizace
do kvóty organizace.

## Společné hlavičky požadavků a chyby

| Hlavička | Popis |
|---|---|
| `uni-api-key` | Váš API klíč. Viz [Autentizace](authentication.md). |
| `panel-id` | ID panelu, k jehož souborům přistupujete. |
| `Content-Type` | `application/json`, nebo `multipart/form-data` pro `upload`. |

Chyby jsou JSON ve tvaru `{"detail": "..."}` (viz [Chyby](conventions.md#chyby)).
Tyto mohou přijít z každého endpointu:

| Stav | `detail` | Příčina |
|---|---|---|
| `401` | `API key not provided` / `API key is invalid` | Klíč je prázdný, neznámý, vypnutý nebo mu chybí rozsah endpointu. |
| `403` | `permission denied` | Panel nevlastníte a ani s vámi není sdílený. |
| `403` | `operation '<operation>' is disabled for folder '<folder>'` | Nastavení složky nastavuje tuto operaci na **none**. Operace je `upload`, `read`, `list`, `update` nebo `delete`. |
| `404` | `panel not found` | Panel s tímto ID neexistuje, nebo je panel vypnutý. |
| `404` | `file not found` | Soubor s tímto ID nebo cestou v panelu neexistuje. |
| `422` | chyba validace | Hlavička `panel-id` chybí nebo není UUID, nebo tělo neodpovídá modelu požadavku. |
| `429` | zpráva limitu požadavků | Příliš mnoho požadavků; viz [Limity požadavků](conventions.md#limity-pozadavku). |

## Nahrání souboru

Uloží soubor. Pošlete ho jako `multipart/form-data`.

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/upload` |
| Rozsah | `ayeto.booster.storage.write` |
| Limit požadavků | [default](conventions.md#limity-pozadavku) |

### Požadavek

| Pole formuláře | Typ | Povinné | Popis |
|---|---|---|---|
| `file` | soubor | ano | Obsah. |
| `path` | řetězec | ano | Cílová cesta. Cesta končící na `/` je adresář: připojí se k ní název nahraného souboru. |
| `metadata` | řetězec | ne | JSON objekt s vašimi daty o souboru. |
| `record_collection`, `record_key` | řetězec, UUID | ne | Záznam databáze, ke kterému soubor patří (obě pole, nebo žádné). |
| `overwrite` | boolean | ne | Nahradit soubor na stejné cestě (vyžaduje právo mazat v jeho složce). Výchozí `false`. |
| `rename` | boolean | ne | Když je cesta obsazená, uložit jako `name (1).ext`, `name (2).ext`, … Výchozí `false`. |

### Odpověď

Uložený [soubor](#soubory) s odkazy.

### Chyby

| Stav | `detail` | Příčina |
|---|---|---|
| `409` | `a file '<path>' already exists` | Cesta je obsazená a nebylo posláno `overwrite` ani `rename`. |
| `422` | `path must start with a folder: '<folder>/<name>'` | Cesta má jen jednu část. |
| `422` | `folder name can only contain letters, digits, '_' and '-'` | Neplatný název složky. |
| `422` | `path segments cannot …` | Neplatný název podsložky nebo souboru (viz [Složky a cesty](#slozky-a-cesty)). |
| `422` | `folder '<folder>' does not accept <type> files` | Složka tento typ souboru nepřijímá. |
| `422` | `the file is larger than <n> MB` | Soubor překračuje limit velikosti složky nebo obecný limit. |
| `422` | `the file content is <type>, not <declared>` / `the file content is not <declared>` | Obsah neodpovídá deklarovanému typu obrázku nebo PDF. |
| `422` | `files in this folder must belong to a database record` | Složka vyžaduje záznam a žádný nebyl poslán. |
| `422` | `record '<key>' is not in collection '<collection>'` | Záznam existuje v jiné kolekci. |
| `404` | `Database record not found` | Takový záznam v databázi panelu neexistuje. |
| `403` | `database access is not allowed for this panel` | Byl poslán záznam, ale panel nemá databázi. |
| `422` | `metadata must be a JSON object` / `metadata is larger than 4096 bytes` | Neplatná metadata. |
| `403` | `folder '<folder>' has reached the maximum of <n> files` / `folder '<folder>' is full` | Dosažen limit složky. |
| `403` | `the file storage of this panel is full` | Dosažen limit panelu. |
| `422` | `storage quota exceeded` / `organization storage quota exceeded` | Kvóta úložiště vlastníka (organizace) je plná. |

### Příklady

```bash
curl -X POST "https://ayeto.ai/api/v3/booster/storage/private/upload" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -F "file=@INV-0042.pdf;type=application/pdf" \
  -F "path=invoices/2026/" \
  -F 'metadata={"customer": "ACME", "total": 1250}' \
  -F "record_collection=orders" \
  -F "record_key=8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70"
```

```json
{
  "id": "5b0a1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
  "path": "invoices/2026/INV-0042.pdf",
  "folder": "invoices",
  "name": "INV-0042.pdf",
  "size": 48213,
  "content_type": "application/pdf",
  "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "metadata": {"customer": "ACME", "total": 1250},
  "record": {"collection": "orders", "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70"},
  "owner": null,
  "created": 1791374100000,
  "updated": 1791374100000,
  "expires_at": null,
  "url": "https://ayeto.ai/api/v3/booster/storage/file/3d2c5a8e-…/5b0a1c2d-…/1791377700i.Xk2…/INV-0042.pdf",
  "download_url": "https://ayeto.ai/api/v3/booster/storage/file/3d2c5a8e-…/5b0a1c2d-…/1791377700a.p9Q…/INV-0042.pdf"
}
```

## Nahrání souboru v base64

Totéž jako [Nahrání souboru](#nahrani-souboru) s tělem v JSON, pro malé soubory.

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/upload-b64` |
| Rozsah | `ayeto.booster.storage.write` |

### Požadavek

| Pole | Typ | Povinné | Popis |
|---|---|---|---|
| `path` | řetězec | ano | Cílová cesta; její poslední část je název souboru. |
| `data` | řetězec | ano | Obsah v base64 nebo data URL (`data:text/csv;base64,…`). |
| `content_type` | řetězec | ne | Deklarovaný typ (jinak z data URL nebo z přípony souboru). |
| `metadata`, `record`, `overwrite`, `rename` | | ne | Jako u [Nahrání souboru](#nahrani-souboru); `record` je `{collection, key}`. |

### Odpověď a chyby

Jako u [Nahrání souboru](#nahrani-souboru), navíc `422 data is not valid base64`.

### Příklad

```bash
curl -X POST "https://ayeto.ai/api/v3/booster/storage/private/upload-b64" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{"path": "exports/orders-2026-10.csv", "data": "data:text/csv;base64,aWQsdG90YWwKMSwxMjUw"}'
```

## Přímé nahrání velkého souboru

Velké soubory (desítky až stovky MB) lze nahrát přímo do úložné služby místo přes API, ve
třech krocích: ohlásit soubor, poslat ho na vrácenou URL, dokončit nahrání. Zda je přímé
nahrávání k dispozici, závisí na instalaci AYETA; když není, `upload/init` odpoví
`409 direct upload is not available, use the upload route` a použijete
[Nahrání souboru](#nahrani-souboru).

| | |
|---|---|
| Endpointy | `POST /api/v3/booster/storage/private/upload/init`, `POST /api/v3/booster/storage/private/upload/complete` |
| Rozsah | `ayeto.booster.storage.write` |

1. `upload/init` s `path`, `filename` (když cesta končí `/`), `content_type`, `size` (přesná
   velikost v bajtech) a volitelně `metadata`, `record`, `overwrite`, `rename`. Pravidla složky,
   typ a limity se kontrolují jako při nahrání. Odpověď je `{upload_id, url, fields, expires_at}`.
2. `POST` na `url` jako `multipart/form-data`: každou položku `fields` jako pole formuláře,
   potom soubor jako pole `file` (musí být poslední část). Soubor musí mít přesně ohlášenou
   velikost a typ. Tento požadavek jde do úložné služby, bez vašeho API klíče.
3. `upload/complete` s `{"upload_id": "…"}` před `expires_at` (jedna hodina). Nahraný obsah se
   zkontroluje znovu (velikost, typ podle obsahu) a vrátí se [soubor](#soubory). Nahrání, které
   se nikdy nedokončí, se smaže.

| Stav | `detail` | Příčina |
|---|---|---|
| `409` | `direct upload is not available, use the upload route` | Použijte [Nahrání souboru](#nahrani-souboru). |
| `404` | `upload not found` | Neznámé nebo vypršelé `upload_id`, nebo už dokončené. |
| `422` | `the file was not uploaded` / `the uploaded file has another size than announced` | Krok 2 neuložil ohlášený soubor. |

Ostatní chyby jako u [Nahrání souboru](#nahrani-souboru).

## Výpis souborů

Vrací soubory přímo v adresáři a jeho podsložky. Bez `folder` vrací složky nejvyšší úrovně
panelu.

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/list` |
| Rozsah | `ayeto.booster.storage` |

### Požadavek

| Pole | Typ | Povinné | Popis |
|---|---|---|---|
| `folder` | řetězec | ne | Složka nejvyšší úrovně. Bez ní se vrací jen `folders`. |
| `prefix` | řetězec | ne | Podsložka uvnitř složky, např. `2026` nebo `2026/q1`. |
| `recursive` | boolean | ne | Také soubory v podsložkách v libovolné hloubce. Výchozí `false`. |
| `params` | objekt | ne | `filters`, `sort_key`, `sort_order`, `limit_from`, `limit_to` jako u [booster databáze](booster-database.md#dotazy-na-zaznamy). Pole: `name`, `path`, `size`, `content_type`, `owner`, `created`, `updated`, `record.collection`, `record.key`, `metadata.<path>`. Ve výchozím stavu od nejnovějších. |
| `links` | boolean | ne | Zahrnout `url` a `download_url`. Výchozí `true`. |

### Odpověď

`{"files": [...], "folders": [...]}`: [soubory](#soubory) stránky a názvy podsložek přímo
v adresáři.

### Příklad

```bash
curl -X POST "https://ayeto.ai/api/v3/booster/storage/private/list" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{"folder": "invoices", "prefix": "2026", "params": {"filters": [["metadata.customer", "==", "ACME"]]}}'
```

## Počet souborů

Počet souborů, který by [výpis](#vypis-souboru) se stejnými `folder`, `prefix`, `recursive`
(zde výchozí `true`) a `params.filters` vrátil napříč všemi stránkami. Bez `folder` všechny
soubory panelu.

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/count` |
| Rozsah | `ayeto.booster.storage` |

## Čtení souboru

Jeden soubor s novými odkazy, podle `id` nebo `path`.

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/get` |
| Rozsah | `ayeto.booster.storage` |

```json
{"path": "invoices/2026/INV-0042.pdf"}
```

## Získání odkazů ke stažení

Nové odkazy až pro 100 souborů.

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/url` |
| Rozsah | `ayeto.booster.storage` |

### Požadavek

| Pole | Typ | Povinné | Popis |
|---|---|---|---|
| `ids` | pole UUID | ano | Soubory (1–100). |
| `ttl_minutes` | celé číslo | ne | Kratší platnost než výchozích 60 minut. |
| `download` | boolean | ne | Odkazy, které vždy stáhnou soubor. Výchozí `false`. |

### Odpověď

Pole `{id, url, expires_at}`; `expires_at` (milisekundy) je `null` u trvalých veřejných
odkazů.

## Úprava souboru

Přejmenuje nebo přesune soubor, nahradí jeho metadata nebo jeho záznam. Přesun do jiné
složky vyžaduje právo nahrávat v ní a právo mazat v aktuální složce a soubor musí splňovat
pravidla typu a velikosti nové složky. Obsah se nikdy nemění; k jeho nahrazení nahrajte
soubor s `overwrite`.

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/update` |
| Rozsah | `ayeto.booster.storage.write` |

### Požadavek

| Pole | Typ | Povinné | Popis |
|---|---|---|---|
| `id` | UUID | ano | Soubor. |
| `path` | řetězec | ne | Nová cesta. |
| `metadata` | objekt | ne | Nová metadata (nahradí stará). |
| `record` | objekt | ne | Nový záznam `{collection, key}`. |
| `unlink_record` | boolean | ne | Odebrat vazbu na záznam. |

### Odpověď a chyby

Upravený [soubor](#soubory). Chyby jako u [Nahrání souboru](#nahrani-souboru); `409`, když je
nová cesta obsazená.

## Smazání souborů

| | |
|---|---|
| Endpointy | `POST /api/v3/booster/storage/private/delete` (`{"id": "…"}`), `POST /api/v3/booster/storage/private/delete-many` (`{"ids": [...]}`, až 100) |
| Rozsah | `ayeto.booster.storage.write` |

Vrací `{"accepted": true}`. Odkazy smazaného souboru okamžitě přestanou fungovat.
`delete-many` maže soubory postupně a zastaví se u prvního, který selže.

## Využití úložiště

| | |
|---|---|
| Endpoint | `POST /api/v3/booster/storage/private/usage` (prázdné tělo) |
| Rozsah | `ayeto.booster.storage` |

```json
{"files": 214, "size": 73400320, "limit": 1073741824, "folders": {"invoices": 52428800, "products": 20971520}}
```

`limit` je limit panelu v bajtech (`0` = platí jen kvóta úložiště vlastníka).

## Události

Soubory uložené, změněné (přesun, nová metadata nebo záznam) a smazané přes toto API jsou
událostmi panelu jako kterékoli jiné: spouštějí [workflow](workflows.md) se spouštěčem booster
souboru na panelu a dostávají je webhooky panelu přihlášené k událostem souborů. Událostí je
i smazání po uplynutí doby uchování nebo spolu se záznamem.
