---
phase: 05-files
plan: "01"
subsystem: files-foundation
tags: [s3, aws-sdk-go-v2, sqlc, migration, minio, compose]
dependency_graph:
  requires: []
  provides:
    - backend/migrations/0005_files.sql
    - backend/internal/db/queries/files.sql
    - backend/internal/files/store.go (FileStorer interface + Store)
    - backend/internal/files/store_test.go
    - backend/internal/web/handlers_files_test.go (RED scaffold)
    - backend/compose.yaml (MinIO services)
  affects:
    - backend/go.mod (four aws-sdk-go-v2 modules added)
    - backend/go.sum
    - backend/internal/db/sqlc/ (regenerated: TabloFile model + files.sql.go)
tech_stack:
  added:
    - github.com/aws/aws-sdk-go-v2 v1.41.7
    - github.com/aws/aws-sdk-go-v2/config v1.32.17
    - github.com/aws/aws-sdk-go-v2/credentials v1.19.16
    - github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0
  patterns:
    - sniff-and-stream upload (io.ReadFull + io.MultiReader)
    - byteCountReader for reliable size_bytes tracking
    - UsePathStyle for MinIO compatibility
    - compose init container with restart: "no"
key_files:
  created:
    - backend/migrations/0005_files.sql
    - backend/internal/db/queries/files.sql
    - backend/internal/files/store.go
    - backend/internal/files/store_test.go
    - backend/internal/web/handlers_files_test.go
  modified:
    - backend/go.mod
    - backend/go.sum
    - backend/compose.yaml
decisions:
  - "FileStorer interface in internal/files package (not web) — test injection without circular imports"
  - "byteCountReader wraps io.MultiReader body before PutObject — tracks actual bytes written (Pitfall 8)"
  - "UsePathStyle: true in NewStore — required for MinIO; R2 supports both styles"
  - "io.ErrUnexpectedEOF treated as non-fatal in Upload — handles files < 512 bytes (Pitfall 3)"
  - "minio-init restart: no — init container must not restart after successful bucket creation (Pitfall 7)"
  - "stubbedFileStorer declared in handlers_files_test.go — Plan 02/03 reuses it without new file"
  - "sqlc-generated files not committed — .gitignore excludes internal/db/sqlc/*.go; sqlc generate reproduces them"
metrics:
  duration: ~15min
  completed: "2026-05-15"
  tasks: 2
  files: 7
---

# Phase 05 Plan 01: Files Foundation Summary

Wave 0 foundation for Phase 5. Four aws-sdk-go-v2 modules added to go.mod, tablo_files migration written with ON DELETE CASCADE, sqlc queries generated (InsertTabloFile, ListFilesByTablo, GetTabloFileByID, DeleteTabloFile), internal/files package implemented with FileStorer interface and Store struct (Upload with sniff-and-stream + byteCountReader, Delete, PresignDownload), RED test scaffold created for FILE-01..06, and MinIO service wired into compose.yaml with mc init container.

## Tasks Completed

| # | Name | Commit | Files |
|---|------|--------|-------|
| 1 | aws-sdk-go-v2 modules + migration + sqlc queries + files.Store | e0d7274 | go.mod, go.sum, 0005_files.sql, files.sql, store.go |
| 2 | RED test scaffold + MinIO in compose.yaml | 3327a42 | handlers_files_test.go, store_test.go, compose.yaml |

## Verification Results

- `go build ./...` exits 0
- `go test ./internal/files/... -v`: TestStoreImplementsFileStorer PASS, TestNewStore_SkipIfNoEndpoint SKIP
- `go test ./internal/web/ -run TestFile -v`: all 5 TestFile* tests SKIP (zero FAIL)
- `backend/migrations/0005_files.sql` contains CREATE TABLE tablo_files, ON DELETE CASCADE, -- +goose Up, -- +goose Down
- `backend/internal/db/sqlc/files.sql.go` contains InsertTabloFile, ListFilesByTablo, GetTabloFileByID, DeleteTabloFile (gitignored, regeneratable)
- `backend/internal/db/sqlc/models.go` contains TabloFile struct (gitignored, regeneratable)
- `backend/compose.yaml` contains minio/minio, minio/mc, minio_data:, restart: "no"

## Deviations from Plan

None — plan executed exactly as written.

## Known Stubs

| File | Description | Resolved by |
|------|-------------|-------------|
| backend/internal/web/handlers_files_test.go | Six TestFile* stubs with t.Skip — intentional RED scaffold | Plan 02/03 |
| backend/internal/web/handlers_files_test.go | stubbedFileStorer no-op — intentional test double | Plan 02/03 |

These stubs are intentional per the Wave 0 plan. They do not prevent the plan's goal (compiling foundation + sqlc + MinIO in compose) from being achieved.

## Self-Check: PASSED

Files verified:
- FOUND: backend/migrations/0005_files.sql
- FOUND: backend/internal/db/queries/files.sql
- FOUND: backend/internal/files/store.go
- FOUND: backend/internal/files/store_test.go
- FOUND: backend/internal/web/handlers_files_test.go
- FOUND: backend/compose.yaml (minio services present)

Commits verified:
- FOUND: e0d7274 (feat(05-01): add aws-sdk-go-v2 modules...)
- FOUND: 3327a42 (test(05-01): add RED test scaffold...)
