Proje: OkulUp API · Hub: OkulUp API — Decisions

S3 (okulup-dev) Konfigürasyonu ve Disk Birleştirme

Karar

  1. Bucket: okulup-dev — dev için. (Başlangıçta dev+staging ortak düşünüldü, prefix önerildi; staging için ayrı bucket açılacağı belli olunca prefix kullanılmadı.)
  2. Prefix yok: AWS_BUCKET_PREFIX env opsiyonu kod tarafında destekleniyor (Flysystem root) ama prod’da boş. İleride başka env aynı bucket’ı paylaşmak isterse prefix tek satır env ile aktif edilebilir.
  3. Tek disk: Tüm kullanıcı yüklemeleri (avatar, etkinlik kapağı, duyuru/ödev/yemek menüsü/belge talebi ekleri, galeri medya, mesaj eki, takip fotosu, ödev teslim, rapor export CSV) tek s3 diskinde toplandı. 'public' ve 'local' disk artık kullanıcı içeriği için kullanılmıyor.
  4. Erişim: Bucket public-read (tüm bucket, Resource: arn:aws:s3:::okulup-dev/*). Resource’larda Storage::disk('s3')->url(...) ile direkt public URL döndürülüyor. Signed URL şu an gereksiz; ileride özel veri sınıfı eklenirse temporaryUrl()’ye geçilir.

Gerekçe

  • okulup-dev bucket’ı dev+staging için ortak olduğundan, env’leri birbirine karıştırmamak için prefix şart. Kod seviyesinde 30+ ->store()/->url() çağrısı var; her birine elle prefix eklemek hata kaynağı — Flysystem 'root' ile merkezi.
  • 4 controller daha önce 'public' (Tracking, Message, Assignment) ve 'local' (ReportExport) disk kullanıyordu; mobile’dan üretilen dosyalar EB instance restart’ta kaybolacaktı. Ayrıca AssignmentSubmissionResource ve AssignmentAttachmentResource zaten Storage::disk('s3')->url(...) döndürüyordu — controller public’e yazdığı için URL’ler bozuktu. Bu refactor o bug’ı da kapattı.
  • Mobile native (Expo) doğrudan S3’e PUT atmıyor (API üzerinden multipart); bucket CORS şu an gerekmiyor. Presigned URL’e geçilirse şart olur.

Alternatifler (red)

  • Kod-içi sabit 'test/' prefix — her controller’da elle eklemek; prod’da silmek gerekirdi.
  • Bucket-policy/IAM tabanlı test/ daraltma — yazma izni kısıtlardı ama ->url() doğru path’i bilmediği için kod yine de prefix’lemek zorundaydı.
  • Per-disk birden fazla S3 config (s3_dev, s3_staging) — env bazlı disk seçimi kod karmaşıklığı.

Etkilenen Dosyalar

  • config/filesystems.phps3.root = env('AWS_BUCKET_PREFIX', '')
  • app/Http/Controllers/Api/TrackingController.php — 2 yer 'public''s3'
  • app/Http/Controllers/Api/MessageController.php — 1 yer
  • app/Http/Controllers/Api/AssignmentController.php — 3 yer
  • app/Http/Controllers/Api/ReportExportController.php'local''s3'
  • app/Services/ExportService.php — 3 yer 'local''s3'
  • app/Http/Resources/TrackingEntryResource.phpasset('storage/...')Storage::disk('s3')->url(...)
  • app/Http/Resources/MessageResource.php'public''s3'
  • Test dosyaları: ReportExportTest, AssignmentAttachmentTest, MessageAttachmentTestStorage::fake('local'|'public')Storage::fake('s3')
  • .env.exampleAWS_BUCKET_PREFIX satırı + yorum

Sunucu .env (dev EB EC2)

FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=<real>
AWS_SECRET_ACCESS_KEY=<real>
AWS_DEFAULT_REGION=eu-central-1
AWS_BUCKET=okulup-dev
AWS_BUCKET_PREFIX=                       # boş, prefix kullanılmıyor
AWS_URL=                                 # boş; Laravel default URL üretiyor
AWS_ENDPOINT=                            # AWS S3 için boş (MinIO için kullanılır)
AWS_USE_PATH_STYLE_ENDPOINT=false

Önemli not: Bucket region eu-central-1. .env’de eksik bırakılırsa SDK us-east-1’e default’lar ve AuthorizationHeaderMalformed hatası verir. Bunu ilk deploy’da 30 dakika harcadık.

IAM (minimum)

Bucket-level public-read policy zaten verildiği için IAM kullanıcısına sadece yazma + silme yetkisi yeterli:

s3:PutObject, s3:GetObject, s3:DeleteObject
Resource: arn:aws:s3:::okulup-dev/*

EC2 EB instance role’üne (aws-elasticbeanstalk-ec2-role) bucket policy yönetim yetkisi yok ve gerekmiyor; bucket policy yönetimi sadece Console’dan admin user ile yapılıyor.

Sanity Check Yöntemi

  1. php artisan tinkerStorage::disk('s3')->put('healthcheck.txt', 'ok') → S3 console’da healthcheck.txt görünüyor mu?
  2. URL’i tarayıcıda aç → AccessDenied geliyorsa Block Public Access (hem bucket hem account-level) ve bucket policy kontrol et.
  3. Mobile’dan avatar upload → response’taki URL tarayıcıda açılıyor mu?

Bucket Public Read Aktivasyonu (Console’dan)

  1. Account-level Block Public Access: S3 servisi → sol panel → “Block Public Access settings for this account” → 4 tikini de kaldır.
  2. Bucket-level Block Public Access: okulup-dev → Permissions → Block public access → 4 tikini kaldır.
  3. Bucket Policy:
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Sid": "PublicRead",
        "Effect": "Allow",
        "Principal": "*",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::okulup-dev/*"
      }]
    }

Account-level BPA’yı atlamak en sık yapılan hata — bucket-level kapatsan bile account-level açıksa hep 403 alırsın.