## Context Phase 3 added local photo uploads to `static/uploads/`. Phase 6 moves them to MinIO (S3-compatible, self-hosted on .13) so they survive deploys and the admin can manage them. Both apps run on .13 alongside MinIO → no new firewall ports (the Go app serves photos through :3020, already open). ## Goals / Non-Goals **Goals:** - Uploads go to MinIO; public page serves them via the Go app (`/photos/…`). - Admin can upload photos to the same bucket (Filament S3 disk). - Existing uploads migrated; external photo URLs keep working. **Non-Goals:** - Public bucket exposure (photos served through the app, not MinIO directly). - Image resizing/optimisation (future). - Cloud S3 (this is self-hosted; swapping to AWS S3 later = same S3 API). ## Decisions 1. **MinIO on .13** at `/home/sam/Docker/Containers/wherewoof-minio/` (compose: minio/minio server, API 9000, console 9001, volume `wherewoof-minio-data`; creds in a local `.env`, chmod 600). Create bucket `wherewoof` at startup (entrypoint `mc` alias + mb, or a one-off). 2. **Go storage via minio-go/v7**: `UploadPhoto` reads the multipart file → `PutObject` to bucket `wherewoof`, key `tags/{id}.{ext}` → sets `photo_url = /photos/tags/{id}.{ext}`. Serve route `GET /photos/{key...}` → `GetObject` → stream with `Content-Type` + `Cache-Control: public, max-age=86400`. 3. **Config**: `MINIO_ENDPOINT` (host:port), `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET` (default `wherewoof`), env-driven; log-mode/no-op if unset (dev keeps working via the existing local fallback? — no: unset → upload returns a friendly error; local file upload is replaced). Decision: if MINIO_* unset, UploadPhoto responds "storage not configured" (dev DB tests unaffected — they don't upload). 4. **Admin S3 disk**: `composer require league/flysystem-aws-s3-v3`; `config/filesystems.php` adds `minio` disk (driver s3, endpoint http://minio:9000 from the admin container — via docker network or host IP 192.168.20.13:9000, use_ssl false); TagResource photo → `FileUpload::make('photo_url')->disk('minio')` storing to `tags/` and setting the URL via the frontend domain (`https://where-woof.com/photos/…`). 5. **Migration**: one-off script copies `static/uploads/*` objects into MinIO with matching keys (dev data; run once). ## Risks / Trade-offs - [MinIO creds in .env files] → chmod 600, gitignored, consistent with existing pattern. - [Serving through the app adds a hop] → fine (same host, localhost); gives auth/cache control later. - [Admin image preview needs the frontend URL] → `` cross-origin is fine; uses the public frontend domain. - [flysystem package version drift with Filament] → pin as Filament requires (uses its own storage config). ## Migration Plan 1. Provision MinIO on .13 (compose + env + bucket); verify with `mc`/curl. 2. Go: add minio-go, UploadPhoto→S3, /photos/ route, config; build + verify suite (upload → served). 3. Admin: s3 disk + Filament upload; verify via admin UI (browser). 4. One-off migrate `static/uploads` → MinIO. 5. Deploy: frontend env + binary; admin .env + rebuild container. Verify live photo round-trip. ## Open Questions - Whether to keep the local `static/uploads` fallback for dev (decided: no — MinIO only; dev can point MINIO_ENDPOINT at the .13 instance).