Files
ipal-kit/docs/storage.md
T

131 lines
4.5 KiB
Markdown

# storage — media na Cloudflare R2
Offload mediów (obrazy, pliki) do Cloudflare R2 zamiast lokalnego dysku. R2 jest
S3-kompatybilny; plugin dostarcza `buildR2Storage`, który czyta dane z `.env`
i konfiguruje adapter.
> **Storage to infrastruktura, nie treść.** Dane R2 (klucze, bucket) idą do
> `.env` — jak DATABASE_URI, PAYLOAD_SECRET, GRAPH_*. NIE do panelu (to sekrety
> agencyjne, wiążą się przy starcie, nie zmienia ich redaktor).
## Zależność
```bash
pnpm add @payloadcms/storage-s3
```
## Zmienne .env
Patrz [R2-ENV-przyklad](../R2-ENV-przyklad.md) po pełną instrukcję skąd wziąć wartości.
```bash
R2_BUCKET=nazwa-bucketa
R2_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=<access-key-id>
R2_SECRET_ACCESS_KEY=<secret-access-key>
```
## Wpięcie (payload.config.ts)
```ts
import { buildR2Storage } from '@intecion/ipal-kit'
export default buildConfig({
// ...
plugins: [
ipalKit({ /* ... */ }),
buildR2Storage(['media']), // slugi kolekcji upload do offloadu
],
})
```
`buildR2Storage` przyjmuje listę kolekcji upload (domyślnie `['media']`). Jeśli
masz więcej kolekcji plików: `buildR2Storage(['media', 'documents'])`.
## Zachowanie (fallback)
- **Wszystkie 4 zmienne** → media w R2.
- **Brak zmiennych** → fallback na lokalny dysk (dev działa bez R2, zero konfiguracji).
- **Część zmiennych** → ostrzeżenie w logu + fallback (częściowa konfiguracja =
pewnie pomyłka).
To wzorzec „degrade gracefully" — jak mailAdapter, który wraca do SMTP, gdy brak
Graph. Projekt działa niezależnie od tego, czy R2 jest skonfigurowany.
## Publiczny dostęp (WAŻNE)
R2 domyślnie prywatny. Upload zadziała, ale obrazy się NIE wyświetlą (403), dopóki
nie skonfigurujesz publicznego odczytu:
1. Cloudflare → R2 → bucket → Settings → **Public access** → podłącz custom domain
2. Albo serwuj przez Cloudflare CDN / własną domenę
Bez tego media wgrają się do R2, ale front nie pokaże obrazów. Konfiguracja domeny
jest po stronie Cloudflare, nie kodu.
## Migracja istniejących mediów
Jeśli projekt miał media lokalnie i przełączasz na R2 — nowe uploady idą do R2,
ale STARE zostają na dysku (i znikną przy redeployu bez wolumenu). Przed
przełączeniem na produkcji przenieś istniejące pliki do bucketa (np. `rclone`
albo ręcznie przez R2 dashboard), inaczej stare obrazy znikną.
## Weryfikacja
```bash
# po wpięciu i ustawieniu .env:
pnpm dev
# wgraj obraz w panelu (Media) → sprawdź w Cloudflare R2, czy plik się pojawił
```
## Dev na lokalnym I na R2 (seedowanie podczas developmentu)
Fallback (brak zmiennych → lokalny dysk) oznacza, że **dev działa w obu trybach**:
- **Dev bez R2 w .env** → media na lokalnym dysku. Szybki start, zero konfiguracji.
- **Dev z R2 w .env** → media w R2 już podczas developmentu. Przydatne, gdy
seedujesz treść w devie i chcesz, żeby od razu lądowała w buckecie (np. wspólny
bucket dev, albo test realnego flow przed produkcją).
Przełączasz trybem po prostu obecnością zmiennych R2 w `.env`. Ten sam kod,
`buildR2Storage` sam wykrywa. Nie musisz nic zmieniać w configu między trybami.
> Jeśli seedujesz w devie do R2 — pamiętaj, że to realny bucket. Używaj osobnego
> bucketa dev (nie produkcyjnego), żeby nie mieszać danych testowych z realnymi.
## Normalizacja nazw plików (automatyczna)
Plik `normalizeFilenameHook` czyści nazwy wgrywanych plików — slugifikuje nazwę,
zachowuje rozszerzenie:
```
"Zdjęcie jeden nad morzem.jpg" → "zdjecie-jeden-nad-morzem.jpg"
"Faktura #12 (2024).PDF" → "faktura-12-2024.pdf"
```
Wpięcie w kolekcję Media (projekt):
```ts
import { normalizeFilenameHook } from '@intecion/ipal-kit'
export const Media: CollectionConfig = {
slug: 'media',
upload: { staticDir: 'media' /* ... */ },
hooks: {
beforeOperation: [normalizeFilenameHook], // czyści nazwę przed zapisem
},
fields: [ /* alt itd. */ ],
}
```
Działa z lokalnym dyskiem i z R2/S3 (hook biegnie PRZED warstwą storage, więc
czysta nazwa trafia i do bazy, i do bucketa). Dlaczego to ważne:
- **URL-e mediów są czyste** — `/media/zdjecie-nad-morzem.jpg`, nie
`/media/Zdjęcie%20jeden%20nad%20morzem.jpg` (spacje/diakrytyki w URL = problemy).
- **Przenośność** — nazwa bez polskich znaków/spacji działa wszędzie (CDN, S3, systemy plików).
- **Bez kolizji kodowania** — spacje i `#`, `()` w nazwach plików potrafią psuć
ścieżki i cache.
Sama funkcja `normalizeFilename(name)` też jest wyeksportowana, gdybyś potrzebował
jej poza hookiem.