262 lines
12 KiB
Markdown
262 lines
12 KiB
Markdown
---
|
|
phase: 09-backend-admin-authentication-and-schema-pipeline
|
|
plan: 06
|
|
subsystem: admin
|
|
tags: [cabana, albums, winter-yaml, collection-scope, dropdowns, postgres]
|
|
|
|
requires:
|
|
- phase: 09-backend-admin-authentication-and-schema-pipeline
|
|
provides: compiled form and list schemas, permission gate, and schema-projected CRUD
|
|
provides:
|
|
- Registered Albums admin controller with embedded Winter form and list YAML
|
|
- Format, genre, and style dropdown options without cross-field leakage
|
|
- Exact active-collection binding for album create, update, list, and bulk delete
|
|
affects: [09-backend-admin-authentication-and-schema-pipeline, admin-api, phase-10-spa]
|
|
|
|
actuals:
|
|
tokens: 14974
|
|
tasks: 3
|
|
commits: 2
|
|
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Winter relation keys match exported Go fields case-insensitively; served JSON keeps the YAML spelling"
|
|
- "Album admin scope is the active collection of the one active frontend user with the backend email"
|
|
- "Non-scalar required flags stay on the form schema and are not save-time column rules"
|
|
|
|
key-files:
|
|
created:
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums/config_form.yaml
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums/config_list.yaml
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/models/album/fields.yaml
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/models/album/columns.yaml
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/classes/backend_album_collection.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/admin_albums_test.go
|
|
modified:
|
|
- cabana/http.go
|
|
- cabana/query.go
|
|
- cabana/list_schema.go
|
|
- cabana/crud.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/models/album.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/admin.go
|
|
|
|
key-decisions:
|
|
- "The album foreign key written by D-14 is collection_id of the matched frontend user's active collection; albums have no user_id column"
|
|
- "Genre and style option values are decimal strings because pact.Option.Value is a string"
|
|
- "relation: genre resolves to field Genre while the schema JSON keeps relation genre"
|
|
- "Required relation fields remain in the form schema and are not applied as save validation"
|
|
|
|
patterns-established:
|
|
- "Pattern: albumsAdminController resolves *gorm.DB per call from the booted app, never a process-global collection id"
|
|
- "Pattern: zero, duplicate, inactive, and no-active-collection email matches share one 422 and name no candidate rows"
|
|
|
|
requirements-completed: [ADMIN-01, ADMIN-02, ADMIN-04]
|
|
|
|
coverage:
|
|
- id: D1
|
|
description: Albums form schema serves every Winter field, locale key, and format scalar in source order for pl and en.
|
|
requirement: ADMIN-01
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminForm
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D2
|
|
description: Albums registration resolves the album model and embedded assets and rejects a mismatched model or missing directory.
|
|
requirement: ADMIN-01
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminRegistration
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D3
|
|
description: Denied form, create, and update requests return 403 before album, genre, style, or frontend-user SQL.
|
|
requirement: ADMIN-01
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminPermissionOrder
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D4
|
|
description: Albums list schema keeps columns, toolbar actions, filters, and locale keys in declaration order.
|
|
requirement: ADMIN-02
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminList
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D5
|
|
description: Format, genre, and style dropdowns are ordered and do not leak choices across fields.
|
|
requirement: ADMIN-01
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminDropdowns
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D6
|
|
description: Permitted album lists keep empty, single, equal-value, and adjacent page behavior, and unknown sorts return 422.
|
|
requirement: ADMIN-02
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminListEdges
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D7
|
|
description: One active normalized email persists that frontend user's active collection id.
|
|
requirement: ADMIN-04
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminCollectionMatch
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D8
|
|
description: Duplicate, inactive, and no-active-collection email matches return 422 and persist nothing.
|
|
requirement: ADMIN-04
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminAmbiguousEmail
|
|
status: pass
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminInactiveUser
|
|
status: pass
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminCrossCollection
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D9
|
|
description: Client user_id and collection_id cannot override the resolved collection, and responses do not echo those keys.
|
|
requirement: ADMIN-04
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminProtectedAssociation
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D10
|
|
description: Scoped update, show, and atomic bulk delete keep foreign albums unchanged.
|
|
requirement: ADMIN-04
|
|
verification:
|
|
- kind: integration
|
|
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminCRUD
|
|
status: pass
|
|
human_judgment: false
|
|
|
|
duration: 29min
|
|
completed: 2026-09-24
|
|
status: complete
|
|
plan_head_before: 3e7738ff32012699bccc4e3f53ea347c87b45f8b
|
|
plan_head_after: 0caa86ec0b6d255dd22a55ee5923a40d3a821977
|
|
---
|
|
|
|
# Phase 9 Plan 06: Albums Admin Collection Boundary Summary
|
|
|
|
**Albums admin form, list, and writes bind only the active collection of the one active frontend user matched by the backend account email.**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** 29 min
|
|
- **Started:** 2026-09-24T17:50:44Z
|
|
- **Completed:** 2026-09-24T18:20:13Z
|
|
- **Tasks:** 3
|
|
- **Files modified:** 15
|
|
|
|
## Accomplishments
|
|
- Embedded the Winter Albums form and list YAML and served the localized form at `GET /_admin/api/v1/golem15/fonoteka/albums/schema/form`.
|
|
- Format options keep Album::FORMATS scalars and phrase keys; genre and style options are active rows ordered by name then id.
|
|
- Create and update set `collection_id` from `ResolveBackendAlbumCollectionUser`. Lists, shows, updates, and bulk deletes cannot see another collection.
|
|
|
|
## TDD Gate Compliance
|
|
|
|
Each task has a RED `test(09-06)` commit before its GREEN `feat(09-06)` commit. RED evidence checks returned `RED_EVIDENCE_OK` for `TestAlbumsAdminForm`, `TestAlbumsAdminDropdowns`, and `TestAlbumsAdminCollectionMatch`.
|
|
|
|
## Task Commits
|
|
|
|
1. **Task 1: Port complete Albums form and controller registration** - `60a4b10` (test, fonoteka.go), `c63146a` (feat, summercms.go), `652bfda` (feat, fonoteka.go)
|
|
2. **Task 2: Port Albums list and typed option providers** - `81f1aa4` (test, fonoteka.go), `5de760d` (feat, fonoteka.go)
|
|
3. **Task 3: Enforce exact active-collection user resolution** - `6004c6a` (test, fonoteka.go), `0caa86e` (feat, summercms.go), `15dfefa` (feat, fonoteka.go)
|
|
|
|
The summercms.go ledger `3e7738ff..0caa86e` contains the two framework commits above. Fonoteka commits are in `fonoteka.go` `60a4b10..15dfefa`.
|
|
|
|
## Files Created/Modified
|
|
- `fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go` - Albums controller, permissions, dropdowns, and collection hooks
|
|
- `fonoteka.go/plugins/golem15/fonoteka/classes/backend_album_collection.go` - exact normalized email resolution
|
|
- `fonoteka.go/plugins/golem15/fonoteka/models/album/fields.yaml` - Winter album form, copied unchanged
|
|
- `fonoteka.go/plugins/golem15/fonoteka/models/album/columns.yaml` - Winter album columns, copied unchanged
|
|
- `fonoteka.go/plugins/golem15/fonoteka/models/album.go` - `Genre` association and `getFormatOptions`
|
|
- `cabana/http.go` - `GET .../schema/form` behind the existing permission check
|
|
- `cabana/query.go` and `cabana/list_schema.go` - case-insensitive relation field match
|
|
- `cabana/crud.go` - required validation limited to scalar writable fields
|
|
- `fonoteka.go/plugins/golem15/fonoteka/admin_albums_test.go` - assembled PostgreSQL proof
|
|
|
|
## Decisions Made
|
|
- D-14 stores `collection_id`, the active collection of the matched frontend user. The albums table has no `user_id` column, so none was added.
|
|
- `pact.Option.Value` is a string, so genre and style ids are decimal strings. Format values stay the Winter scalars (`LP`, `EP 7"`, and the rest).
|
|
- Served list JSON keeps `relation: genre`. Joins and preloads use the exported Go field `Genre`.
|
|
- `artists` stays `required: true` on the form schema. Save validation does not require it, because the admin CRUD path cannot bind a relation.
|
|
|
|
## Deviations from Plan
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. [Rule 3 - Blocking] Embedded the list YAML while registering the controller**
|
|
- **Found during:** Task 1
|
|
- **Issue:** Activation compiles `config_list.yaml` for every admin controller. Registering Albums with only the form files failed boot.
|
|
- **Fix:** Copied the Winter list and columns YAML in the same commit as the form.
|
|
- **Files modified:** `controllers/albums/config_list.yaml`, `models/album/columns.yaml`
|
|
- **Verification:** `TestAlbumsAdminRegistration` and `TestAlbumsAdminList`
|
|
- **Committed in:** `652bfda`
|
|
|
|
**2. [Rule 2 - Missing Critical] Served the form schema over HTTP**
|
|
- **Found during:** Task 1
|
|
- **Issue:** Form compilation existed, but no route served it, so permission-denied form requests could not be proven.
|
|
- **Fix:** Added `GET /{vendor}/{plugin}/{controller}/schema/form` using the same `protect` gate as the list schema.
|
|
- **Files modified:** `cabana/http.go`
|
|
- **Verification:** `TestAlbumsAdminForm` and `TestAlbumsAdminPermissionOrder`
|
|
- **Committed in:** `c63146a`
|
|
|
|
**3. [Rule 1 - Bug] Matched Winter relation names to exported Go fields**
|
|
- **Found during:** Task 1
|
|
- **Issue:** `relation: genre` did not match field `Genre`, so the unchanged columns YAML could not activate.
|
|
- **Fix:** Accept an exact field name, otherwise a case-insensitive relation match. JSON still says `genre`.
|
|
- **Files modified:** `cabana/list_schema.go`, `cabana/query.go`, `models/album.go`
|
|
- **Verification:** `go test ./cabana` and `TestAlbumsAdminList`
|
|
- **Committed in:** `c63146a`, `652bfda`
|
|
|
|
**4. [Rule 2 - Missing Critical] Did not save-validate unbound relation required flags**
|
|
- **Found during:** Task 3
|
|
- **Issue:** `artists.required` made every album create 422 before the collection hook ran. The field is not a writable column.
|
|
- **Fix:** Merge `required` into save rules only for scalar writable field types. The schema JSON is unchanged.
|
|
- **Files modified:** `cabana/crud.go`
|
|
- **Verification:** `go test ./cabana` and `TestAlbumsAdminCollectionMatch`
|
|
- **Committed in:** `0caa86e`
|
|
|
|
---
|
|
|
|
**Total deviations:** 4 auto-fixed (1 bug, 2 missing critical, 1 blocking)
|
|
**Impact on plan:** Required for unchanged Winter YAML to boot and for album creates to reach the collection hook. No new album columns and no extra controllers.
|
|
|
|
## Issues Encountered
|
|
|
|
`TestPhase8RedMCPMe` and `TestOAuthToolsMeRouteIsolation` fail because `GET /_admin/api/v1/auth/me` ends in `/me`. That route was already mounted with the admin API before this plan. It was left unchanged.
|
|
|
|
`ADMIN-01`, `ADMIN-02`, and `ADMIN-04` stay pending in `REQUIREMENTS.md`. Later plans in this phase still declare them.
|
|
|
|
## User Setup Required
|
|
|
|
None - no external service configuration required.
|
|
|
|
## Next Phase Readiness
|
|
|
|
Ready for 09-07. Albums is the only new admin controller. Artists, Styles, and Collections controllers were not added. Genre remains the existing list tracer.
|
|
|
|
## Self-Check: PASSED
|
|
|
|
- FOUND: `fonoteka.go` `60a4b10`, `652bfda`, `81f1aa4`, `5de760d`, `6004c6a`, `15dfefa`
|
|
- FOUND: `summercms.go` `c63146a`, `0caa86e`
|
|
- FOUND: albums controller, form YAML, columns YAML, and `backend_album_collection.go`
|
|
|
|
---
|
|
*Phase: 09-backend-admin-authentication-and-schema-pipeline*
|
|
*Completed: 2026-09-24*
|