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
This commit is contained in:
172
.planning/phases/05-data-layer-full-fidelity/05-04-SUMMARY.md
Normal file
172
.planning/phases/05-data-layer-full-fidelity/05-04-SUMMARY.md
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
phase: 05-data-layer-full-fidelity
|
||||
plan: 04
|
||||
subsystem: database
|
||||
tags: [attach, system_files, gocloud, blob, imaging, thumbnails, morph]
|
||||
|
||||
requires:
|
||||
- phase: 05-data-layer-full-fidelity
|
||||
provides: Winter-directory registries, Album/Collection models, lagoon.Migrate, HasAfterDelete
|
||||
provides:
|
||||
- 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
|
||||
affects: [05-05-remaining-models, 05-06-tests, 06-http, 12-api-parity]
|
||||
|
||||
tech-stack:
|
||||
added:
|
||||
- gocloud.dev v0.46.0
|
||||
- github.com/disintegration/imaging v1.6.2
|
||||
patterns:
|
||||
- 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
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- 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
|
||||
modified:
|
||||
- lagoon/migrations.go
|
||||
- lagoon/migrations_test.go
|
||||
- go.mod
|
||||
- go.sum
|
||||
- plugins/golem15/fonoteka/models/album.go
|
||||
- plugins/golem15/fonoteka/models/collection.go
|
||||
|
||||
key-decisions:
|
||||
- "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)"
|
||||
|
||||
patterns-established:
|
||||
- "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"
|
||||
|
||||
requirements-completed: [DATA-08, DATA-09]
|
||||
|
||||
duration: 18min
|
||||
completed: 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*
|
||||
Reference in New Issue
Block a user