## 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] → `<img>` 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).
