Files
summercms/.planning/phases/05-data-layer-full-fidelity/05-04-SUMMARY.md
Jakub Zych 690aed2ea6 docs(05-04): complete attachments system_files blob and thumb plan
Tasks completed: 3/3
- Task 1: File, Thumb, bucket, system_files migration
- Task 2: delete lifecycle and static handler
- Task 3: Album/Collection MorphName

SUMMARY: .planning/phases/05-data-layer-full-fidelity/05-04-SUMMARY.md
2026-09-18 20:04:39 +02:00

8.1 KiB
Raw Blame History

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
05-data-layer-full-fidelity 04 database
attach
system_files
gocloud
blob
imaging
thumbnails
morph
phase provides
05-data-layer-full-fidelity Winter-directory registries, Album/Collection models, lagoon.Migrate, HasAfterDelete
framework-owned system_files table migrated before every plugin set
lagoon/attach File model, gocloud.dev/blob bucket, Winter-exact Thumb() naming with lazy imaging
DeleteForOwner two-phase contract (rows in-tx, blobs after commit)
StaticHandler for public attachments (tested, not route-wired)
Album/Collection MorphName PHP class strings
05-05-remaining-models
05-06-tests
06-http
12-api-parity
added patterns
gocloud.dev v0.46.0
github.com/disintegration/imaging v1.6.2
lagoon.Migrate runs attach.Migrations before iterating plugins
Thumb exists-check then resize; memblob in tests, fileblob in config
DeleteForOwner collects blob keys in-tx; DeleteKeys runs after commit
created modified
lagoon/attach/file.go
lagoon/attach/file_test.go
lagoon/attach/migrations.go
lagoon/attach/bucket.go
lagoon/attach/bucket_test.go
lagoon/attach/thumb.go
lagoon/attach/thumb_test.go
lagoon/attach/static.go
lagoon/attach/static_test.go
lagoon/attach/lifecycle_test.go
config/storage.yaml
lagoon/migrations.go
lagoon/migrations_test.go
go.mod
go.sum
plugins/golem15/fonoteka/models/album.go
plugins/golem15/fonoteka/models/collection.go
Blob keys are partition+disk_name (no public/protected prefix); fileblob roots at storage/app/uploads
DeleteForOwner afterCommit is an in-tx key collector; DeleteKeys removes originals and thumb_<id>_ prefixes after commit
Album/Collection MorphName returns the PHP class string and does not import attach (models stay a leaf)
system_files is framework-owned; lagoon.Migrate always runs it first, even with an empty plugin list
StaticHandler rebuilds keys from disk_name via PartitionDirectory; unvalidated path segments never reach NewReader
DATA-08
DATA-09
18min 2026-09-18

Phase 5 Plan 04: Attachments Summary

Framework-owned system_files with gocloud.dev/blob, Winter-exact Thumb() naming and lazy disintegration/imaging resize, post-commit force-delete blob removal, and Album/Collection morph names

Performance

  • Duration: 18 min
  • Started: 2026-09-18T17:46:04Z
  • Completed: 2026-09-18T18:03:39Z
  • Tasks: 3
  • Files modified: 18

Accomplishments

  • lagoon.Migrate(gdb, nil) creates system_files (Winter column set plus metadata and a composite attachment lookup index) before any plugin set
  • ThumbFilename / PartitionDirectory match Winter Storm (thumb_42_200_200_0_0_crop.jpg, abc/123/xyz/); File.Thumb resizes once via disintegration/imaging and reuses the blob on the second call
  • OpenBucket fails loud on empty storage.uploads.bucket_url; fileblob/memblob via gocloud.dev/blob; fonoteka.go/config/storage.yaml mirrors cms.php uploads
  • Soft-delete of an owner keeps system_files rows and blobs; force-delete removes rows inside the transaction and blobs (original + thumb_<id>_) only after commit
  • StaticHandler serves exact prefix/<partition>/<disk_name> keys with Content-Type and 404s traversal/mismatch; not mounted on any route
  • Album and Collection implement attach.Owner with Golem15\Fonoteka\Models\Album and ...\Collection

Task Commits

Each task was committed atomically:

  1. Task 1 RED: attach tests - 2d84ae4 (test) in summercms.go
  2. Task 1 GREEN: File, Thumb, bucket, system_files migration - ba9c992 (feat) in summercms.go
  3. Task 1: storage.uploads config - 81ca63b (feat) in fonoteka.go
  4. Task 2: delete lifecycle and static handler - d8ecb41 (feat) in summercms.go
  5. Task 3: Album/Collection MorphName - a2c3816 (feat) in fonoteka.go

Plan metadata: pending (this file)

Files Created/Modified

  • lagoon/attach/file.go, file_test.go — File (system_files), Owner, DeleteForOwner/DeleteKeys
  • lagoon/attach/migrations.go — 202609180001_create_system_files
  • lagoon/attach/bucket.go, bucket_test.go — OpenBucket/Publish/PublicPathPrefix
  • lagoon/attach/thumb.go, thumb_test.go — Winter naming + lazy imaging
  • lagoon/attach/static.go, static_test.go — public file handler (httptest only)
  • lagoon/attach/lifecycle_test.go — soft-delete keep / force-delete two-phase
  • lagoon/migrations.go, migrations_test.go — attach set runs first; TestMigrateRunsSystemFilesFirst
  • go.mod, go.sum — gocloud.dev v0.46.0, disintegration/imaging v1.6.2
  • fonoteka.go/config/storage.yaml — uploads.bucket_url / public_path_prefix
  • fonoteka.go/plugins/golem15/fonoteka/models/{album,collection}.go — MorphName()
  • fonoteka.go/plugins/golem15/fonoteka/go.mod, go.sum — transitives from lagoon importing attach

Decisions Made

  • Blob keys are PartitionDirectory(disk_name)+disk_name with no public//protected/ prefix; the fileblob root is storage/app/uploads (cutover can point the URL at .../uploads/public)
  • DeleteForOwner's afterCommit callback only records keys while still inside the GORM transaction; DeleteKeys is the post-commit blob remover (prefix keys ending in _ are listed)
  • Models satisfy attach.Owner by method set only and do not import lagoon/attach, keeping models/ a leaf

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Lifecycle tests cannot live in package attach and import lagoon

  • Found during: Task 2 (TestFileLifecycle)
  • Issue: lagoon already imports lagoon/attach; an internal-package test importing lagoon is a cycle
  • Fix: lifecycle_test.go is package attach_test so it can call lagoon.Migrate/lagoon.Use against a dedicated testcontainers Postgres
  • Files modified: lagoon/attach/lifecycle_test.go
  • Verification: go test ./lagoon/attach/... -run TestFileLifecycle
  • Committed in: d8ecb41 (Task 2)

Total deviations: 1 auto-fixed (1 blocking) Impact on plan: Required for a compiling lifecycle test that proves Migrate(gdb, nil) plus the two-phase delete contract. No scope creep.

Issues Encountered

None beyond the auto-fix above.

Authentication Gates

None.

TDD Gate Compliance

  • RED: 2d84ae4 test(05-04): add failing tests for attach File, Thumb, and bucket
  • GREEN: ba9c992 feat(05-04): implement attach File, Thumb, blob bucket, system_files migration
  • REFACTOR: not needed
  • Task 1 is tdd="true"; Tasks 2–3 are standard type="auto"

Known Stubs

  • StaticHandler is intentionally not registered on any plugin route (D-16 / this phase's HTTP-route boundary; is_public gate is Phase 6/12, T-05-16)
  • HTTP upload endpoint stays Phase 12 (API-02)

User Setup Required

None - no external service configuration required. Operators set storage.uploads.bucket_url (default file://./storage/app/uploads in config/storage.yaml).

Next Phase Readiness

Ready for 05-05 (remaining models). Attachments are usable from Album/Collection via MorphName() and system_files. Plan 05-06 should smoke-test photos/image query helpers on the classes/ layer.

Self-Check: PASSED

  • Key files exist on disk (lagoon/attach/{file,migrations,bucket,thumb,static}.go, fonoteka.go/config/storage.yaml, MorphName on Album/Collection)
  • Commits 2d84ae4, ba9c992, d8ecb41 (summercms.go) and 81ca63b, a2c3816 (fonoteka.go) exist
  • go vet ./lagoon/... and go test ./lagoon/attach/... green in summercms.go (short and full)
  • go vet ./... and go build ./... green in fonoteka.go
  • go.mod pins gocloud.dev v0.46.0 and github.com/disintegration/imaging v1.6.2
  • ThumbFilename(42, 200, 200, 0, 0, "crop", "jpg") is thumb_42_200_200_0_0_crop.jpg; PartitionDirectory("abc123xyz.jpg") is abc/123/xyz/

Phase: 05-data-layer-full-fidelity Completed: 2026-09-18