8.0 KiB
title, description, status, last_updated
| title | description | status | last_updated |
|---|---|---|---|
| Faz 3 Yerel Storage ve Instance Branding | Güvenli local filesystem, dosya metadata, authorized file route'ları, backup checksum ve server-rendered branding sözleşmesi. | complete | 2026-07-16 |
Faz 3 Yerel Storage ve Instance Branding
Faz 3, avatar, branding ve project asset içeriklerinin Supabase Storage yerine instance'ın persistent data volume'ünde saklanabileceği güvenli backend temelini kurar. Mevcut feature sayfalarının bu servislere taşınması ilgili dikey sayfa fazlarında yapılacaktır; file ve branding çekirdeğinin kendisi Supabase, browser veya UI bağımlılığı taşımaz.
Veri modeli
0004_fancy_baron_zemo.sql iki tablo ekler:
files: owner/uploader, avatar subject veya project ilişkisi, tür, visibility, relative storage path, MIME, byte size ve SHA-256 metadata'sı;instance_branding: tek instance kaydı, uygulama isimleri, logo/icon file referansları, primary/accent renkleri, color mode, radius ve portal metinleri.
files tablosu tür-kaynak-visibility kombinasyonlarını SQLite CHECK constraint'iyle sınırlar. Mutlak path, .., sıfır/negatif byte size ve geçersiz checksum metadata seviyesinde de reddedilir. File foreign key'leri restrict kullanır; bir user veya project silinmesi physical dosyayı atlayarak orphan üretemez. Branding file referansları dosya silindiğinde set null olur.
Filesystem sözleşmesi
Dosyalar yalnızca aşağıdaki servis tarafından üretilen relative path'lerde tutulur:
uploads/
avatars/<generated-id>.<detected-extension>
branding/<generated-id>.<detected-extension>
project-assets/<generated-id>.<detected-extension>
Actor, project ID veya original filename path üretiminde kullanılmaz. resolveStoragePath absolute path, backslash, boş segment, . ve .. segmentlerini reddeder; çözülmüş path'in uploads root altında kaldığını ikinci kez doğrular.
Upload sırası:
- Role/resource authorization ve policy doğrulanır.
- İçerik instance
tmp/alanınawxve0600ile yazılır. - Hedef path'e overwrite etmeyen hard-link ile atomik publish edilir.
- File metadata ve avatar
user.imagedeğişikliğiBEGIN IMMEDIATEtransaction'da yazılır. - Transaction başarısızsa yalnızca o işlemde oluşturulan physical dosya kaldırılır.
Delete işleminde dosya önce aynı data volume içindeki trash path'e taşınır, metadata transaction'ı tamamlanır, sonra trash kaldırılır. DB işlemi başarısızsa dosya eski yerine alınır. Avatar silinirse halen ilgili dosyayı gösteren user.image alanı da aynı transaction'da temizlenir.
Download sırasında metadata authorization yeniden uygulanır. File descriptor O_NOFOLLOW ile açılır; symbolic link izlenmez ve physical size metadata ile eşleşmelidir. Response nosniff, doğru Content-Type, byte length, ETag ve kontrollü cache header'ları taşır.
Dosya politikası
- Maksimum dosya boyutu: 5 MiB.
- Allowlist: JPEG, PNG, WebP ve avatar/project/logo için GIF.
- Branding icon/favicon için yalnızca PNG kabul edilir; manifest MIME sözleşmesi sabit ve güvenli kalır.
- MIME yalnızca browser beyanından alınmaz; JPEG/PNG/WebP/GIF magic byte imzası içerikten doğrulanır.
- SVG ilk sürümde kabul edilmez. Böylece SVG script/external reference sanitizasyon bağımlılığı eklenmez.
- Original filename yalnızca download adı olarak normalize edilir; storage path'e girmez.
Authorization matrisi
| Kaynak | Upload | Authenticated read | Public read | Delete |
|---|---|---|---|---|
| Avatar | Aktif owner veya client, yalnızca kendisi | Owner veya avatar subject | Yok | Owner veya avatar subject |
| Branding logo/icon | Yalnızca owner | Owner | Yalnızca aktif branding kaydınca referanslanan dosya | Owner |
| Private project asset | Yalnızca project owner | Owner | Yok | Owner |
| Portal project asset | Yalnızca project owner | Owner veya projeye bağlı client | Yok | Owner |
Unauthorized resource varlığı sızdırmamak için cross-owner/cross-client read çoğunlukla NOT_FOUND döner. Branding public route'u public_branding visibility tek başına yeterli saymaz; dosyanın aktif instance_branding kaydındaki light logo, dark logo veya icon alanlarından birinde referanslanması gerekir.
Route sınırları:
POST /api/files: authenticated multipart upload;GET /api/files/:id: authenticated authorized download;DELETE /api/files/:id: authorized delete;GET /api/branding/assets/:id: kontrollü public branding asset;GET /api/branding: public, secret içermeyen instance markası;PATCH /api/branding: owner-only branding mutation.
Branding ve ilk render
Primary/accent değerleri yalnızca altı haneli hex olarak kabul edilir ve normalize edilir. Her renk için siyah/beyaz foreground arasından WCAG contrast oranı yüksek olan seçilir; smoke test en az 4.5:1 oranını doğrular. Hover/pressed, ring ve radius tokenları server-side üretilir.
Root layout her request'te branding'i SQLite'tan okur ve semantic CSS custom property'lerini doğrudan <html style> üzerinde üretir. data-color-mode ve dark class ilk HTML'de bulunur; system dark tercihi CSS media query ile uygulanır. Bu nedenle token veya color mode için hydration sonrası browser düzeltmesi ve ilk render parlaması gerekmez.
Metadata title, Apple web app adı, theme color ve favicon da branding'den üretilir. organizationName görünür workspace adını, applicationName meta title'ı, shortName PWA/mobil kısa adını taşır. manifest.webmanifest bu alanları, primary rengi ve favicon referansını dinamik kullanır. Light/dark logo alanlarından biri boşsa diğeri fallback olur; ayarlar arayüzü iki tema için ayrı upload ister. File silmek foreign key set null ile varsayılan asset durumuna döner. Dashboard ve client portal aynı root layout tokenlarını kullanır.
Backup ve restore
Backup mevcut davranışını koruyarak SQLite dosyasını ve uploads/ ağacını aynı backup klasörüne kopyalar; manifest her dosya için size ve SHA-256 içerir. Restore artık kopyalamadan önce:
- manifest formatını ve her path'in backup root içinde kalmasını;
- symlink bulunmadığını;
- size ve SHA-256 eşleşmesini;
- manifest dışında doğrulanmamış ek dosya bulunmadığını
kontrol eder. Bozuk veya sonradan değiştirilmiş bir upload içeren backup reddedilir.
Doğrulama kapsamı
npm run phase3:storage-smoke, migration uygulanmış gerçek SQLite ve gerçek geçici filesystem üzerinde şunları doğrular:
- avatar, logo/icon ve private/portal project asset upload/read/delete;
- client avatar subject, project-client visibility ve cross-owner negatifleri;
- MIME allowlist, magic byte, 5 MiB limit ve SVG reddi;
- absolute/traversal/backslash path reddi ve symlink takip etmeme;
- DB CHECK constraint'leri ve physical/metadata delete tutarlılığı;
- owner-only branding mutation, file-kind eşleşmesi, logo fallback ve file silme;
- primary/accent normalization ve foreground contrast.
Uçtan uca auth smoke gerçek Next.js Route Handler'ları üzerinden anonymous error envelope, authenticated multipart upload, public referenced logo, client portal/private ayrımı, client avatar upload/delete, server-rendered token/metadata ve dinamik manifest'i test eder. Runtime backup smoke upload restore'unu ve bozuk checksum reddini doğrular.
| Kontrol | Sonuç |
|---|---|
npm run typecheck |
Başarılı |
| Değişen Faz 3 TypeScript dosyalarında targeted ESLint | 0 error, 0 warning |
npm run phase3:storage-smoke |
Başarılı |
node scripts/phase1-auth-smoke.mjs |
Başarılı; file/branding HTTP ve SSR dahil |
node scripts/phase1-smoke.mjs |
Başarılı; uploads backup/restore ve bozuk checksum reddi dahil |
pnpm db:generate |
Schema drift yok |
npm run build |
Başarılı; file/branding route'ları ve dinamik manifest üretildi |
Repo geneli lint, önceki fazlardan kayıtlı UI/AI baseline dosyalarında 31 error ve 18 warning ile açık kalır. Faz 3 dosyalarının targeted lint kontrolü temizdir.