Proje: OkulUp CRM · Hub: OkulUp CRM — Decisions

CRM Tarafında S3 Public/Private Geçişi — Etki Analizi

Karar

OkulUp API’sinde bugün yapılan public/private/ prefix ayrımı (2026-05-14-s3-public-private-hiyerarsi) için CRM’de kod değişikliği yapılmadı. Mevcut CRM kodu yeni bucket yapısı ile %100 uyumlu çalışıyor; sadece manuel smoke test gerekiyor.

Gerekçe — CRM ne tüketiyor?

Tüm src/ ağacında S3-arkalı URL render eden sadece 3 nokta var:

YerAlanAPI SınıflandırmasıRisk
src/features/ParentStudentMatchingTab.jsx:366child.avatar_url (<img>)public (public/avatars/... + legacy avatars/...)❌ Yok — kalıcı URL
src/features/GalleriesTab.jsx:87,147media.thumbnail_url, media.file_url (<img> + <a target=_blank>)public (public/galleries/...)❌ Yok — kalıcı URL
src/pages/AttendancePage.jsx:159 (window.open)response.data.download_url (CSV export)private (private/exports/{userId}/, 15 dk presigned)❌ Yok — URL anında kullanılıyor

CRM, API’nin private tarafında olan mesaj ekleri, tracking foto, ödev teslimi, belge talebi eki, ödeme dekontu alanlarını hiç render etmiyor. Bu modüller CRM’de var ama UI seviyesinde sadece metadata gösteriyor (örn. TrackingTab sadece kategori/durum/not gösterir; photo_path ekran dışında).

Persistence Audit (zustand + localStorage)

  • localStorage sadece şunları saklar: STORAGE_KEYS.token, sidebarOpen, favori action key’leri. URL yok.
  • useDataStore (zustand) persist middleware kullanmıyor — refresh’te tüm data sıfırlanır.
  • src/lib/helpers.js’te in-memory recentGetResponses Map var (TTL GET_CACHE_TTL_MS = 4000). 4 saniye, 15 dakika TTL’in çok altında — sorun yok.
  • IndexedDB / react-query persistor / sessionStorage kullanılmıyor.

Sonuç: 15 dakikalık presigned URL’lerin “ölü URL persist edilmesi” senaryosu CRM’de teknik olarak imkansız.

CORS

Bugünkü kod presigned S3 URL’i hiçbir yerde fetch() veya XMLHttpRequest ile çekmiyor. Kullanılan yöntemler:

  • <img src> — CORS preflight tetiklemez (no-cors mode).
  • <a target="_blank" href> — sadece navigation.
  • window.open(downloadUrl) — sadece navigation.

Dolayısıyla bucket’a CORS policy eklenmesi şu an gerekmiyor. İleride CRM private içeriği fetch(presignedUrl).blob() ile çekmek isterse (örn. inline PDF önizleme, mesaj eki client-side caching), API decision notunda hazır olan CORS şablonuna http://127.0.0.1:4173 (dev) ve prod origin eklenmeli.

CRM’de navigator.clipboard, copyToClipboard, “linki kopyala” tarzı buton hiç yok (grep ile doğrulandı). UX gözden geçirmesi gereken yer yok.

Browser image cache

Public içerik (avatar, gallery) → kalıcı URL → cache normal çalışıyor. Private içerik render edilmediği için “her response yeni URL → cache miss → data harcaması” senaryosu da geçerli değil.

Manuel Test Checklist

Smoke test için kullanıcı şunlara bakmalı:

  1. Avatar render: Admin/manager login → Veli-Öğrenci Eşleştirme ekranı → child.avatar_url olan kayıtlar → <img> 200 dönmeli (hem legacy avatars/... hem yeni public/avatars/... URL’leri için).
  2. Gallery thumbnail: Admin login → Galeri Onayları → grid’de cover görseli + tıklayınca yeni sekmede S3 URL 200 dönmeli.
  3. Attendance export: Yoklama → “CSV İndir” → yeni sekmede S3 presigned URL açılıp dosya inmeli (içerik private/exports/...).
  4. 15 dakika sonra reload: Avatar/galeri public olduğu için 15 dk sonra bile çalışmalı.
  5. Console/network: Dev tools network tab’inde yeni okulup-dev/private/... URL’lerinin response header’ında x-amz-... imza parametreleri olmalı (presigned olduğunu teyit eder).

Backward Compatibility Doğrulaması

API decision notu legacy path’leri (avatars/, events/, galleries/, announcements/, meal-menus/, assignments/) yeni bucket policy ile public-read tutuyor. CRM’deki mevcut DB kayıtlarında bu eski path’ler olan satırlar (örn. data migration öncesi yüklenmiş avatarlar) → API tarafında UserResource::resolveAvatarUrl() her iki prefix’i de tanıdığı için CRM’e yine geçerli URL döner. CRM hiçbir prefix tahmini yapmıyor — sadece response’tan geleni alıp <img src>’ye koyuyor. ✅

İleride Dikkat Edilecekler

Eğer CRM şu özelliklerden birini eklerse o noktada code change şart olur:

  1. Ödeme dekontu görüntülemeprivate/payments/.../receipts/. View açıldığında payment’ı refetch ederek taze URL almak gerek.
  2. Belge talebi eki indirmeprivate/document-requests/.../. Aynı pattern.
  3. Ödev teslim indirme (öğretmen ekranı) — private/submissions/.../. Aynı pattern.
  4. Mesaj ekleri (admin moderation ekranı eklenirse) — private/messages/.../.
  5. Tracking fotoğraf görüntülemeprivate/tracking/.../.

Pattern: “view modal/sayfa açıldığında ilgili resource’u tekrar requestJson ile fetch et” yeterli — useDataStore’daki cached versiyon bayatlamış olabilir. Bucket’a CORS eklemek ancak fetch(presignedUrl) (blob/base64) yapılırsa gerekiyor; bugünkü pattern (<img>, <a>, window.open) için CORS gerekmiyor.

Etkilenen Dosyalar

Hiçbiri. Sadece audit yapıldı, kod değişmedi. Bu not bir “negative finding” kaydı.