REST API #2b: Collections CRUD + items (+ retrofit SmartCollectionController) #35

Closed
opened 2026-06-27 13:46:07 +02:00 by timothy · 3 comments
Owner

Part of #2 (REST API tracker). Design: docs/rest-api.md. Depends on #2a foundation.

Slice #2b — Collections.

  • POST /api/collections · GET /api/collections · GET /api/collections/{id} · PUT /api/collections/{id} · DELETE /api/collections/{id}.
  • Items: POST /api/collections/{id}/items · DELETE /api/collections/{id}/items/{mediaItemId}.
  • Map DTOs → existing CreateCollection / UpdateCollection / DeleteCollection / AddItemsToCollection / RemoveItemsFromCollection (all Either<BaseError, _>; name-uniqueness + length already in handlers).

Standardization (test-before/after): retrofit the existing non-idiomatic SmartCollectionController (/api/collections/smart/new|update|delete/{id}) to the idiomatic shape, with characterization tests capturing current behavior first, then change, then prove green.

Tests: handler unit (success + 404/422), controller mapping, create→read→add-item→remove-item→delete EF integration.

Part of #2 (REST API tracker). Design: `docs/rest-api.md`. Depends on #2a foundation. **Slice #2b — Collections.** - `POST /api/collections` · `GET /api/collections` · `GET /api/collections/{id}` · `PUT /api/collections/{id}` · `DELETE /api/collections/{id}`. - Items: `POST /api/collections/{id}/items` · `DELETE /api/collections/{id}/items/{mediaItemId}`. - Map DTOs → existing `CreateCollection` / `UpdateCollection` / `DeleteCollection` / `AddItemsToCollection` / `RemoveItemsFromCollection` (all `Either<BaseError, _>`; name-uniqueness + length already in handlers). **Standardization (test-before/after):** retrofit the existing non-idiomatic `SmartCollectionController` (`/api/collections/smart/new|update|delete/{id}`) to the idiomatic shape, with **characterization tests** capturing current behavior first, then change, then prove green. Tests: handler unit (success + 404/422), controller mapping, create→read→add-item→remove-item→delete EF integration.
Author
Owner

Progress update:

  • Found an existing feature/35-rest-api-collections worktree with partial #35 work.
  • Reviewed it against #2a conventions and #35 acceptance criteria; useful scaffolding existed, but coverage was incomplete and stale smart-collection characterization tests no longer matched the modified controller.
  • Rebuilt the proof around #2a patterns: collection/smart-collection controller tests, security tests, handler not-found tests, and an in-memory SQLite collection lifecycle test.
  • Fixed collection and smart-collection missing-resource handlers to preserve NotFoundError by branching before validation, matching the Channels #2a pattern.
  • Also created server-management#492 for the Codex equivalent of the Claude /done protocol requested during the session.

Next: LSP diagnostics, full build, UTC test run, adversarial review, then commit/rebase/push/PR if clean.

Progress update: - Found an existing `feature/35-rest-api-collections` worktree with partial #35 work. - Reviewed it against #2a conventions and #35 acceptance criteria; useful scaffolding existed, but coverage was incomplete and stale smart-collection characterization tests no longer matched the modified controller. - Rebuilt the proof around #2a patterns: collection/smart-collection controller tests, security tests, handler not-found tests, and an in-memory SQLite collection lifecycle test. - Fixed collection and smart-collection missing-resource handlers to preserve `NotFoundError` by branching before validation, matching the Channels #2a pattern. - Also created server-management#492 for the Codex equivalent of the Claude `/done` protocol requested during the session. Next: LSP diagnostics, full build, UTC test run, adversarial review, then commit/rebase/push/PR if clean.
Author
Owner

Implemented in PR #45: #45

What changed:

  • Added REST CollectionController for POST/GET/GET{id}/PUT/DELETE /api/collections plus POST /api/collections/{id}/items and DELETE /api/collections/{id}/items/{mediaItemId}.
  • Retrofitted SmartCollectionController to idiomatic REST routes under /api/smart-collections.
  • Applied the #2a patterns: class-level ApiKeyAuthorizationFilter, request DTO to command mapping, ApiResults status mapping, and typed NotFoundError preservation from handlers.
  • Added handler/controller/integration coverage using the ErsatzTV.Tests in-memory SQLite harness.
  • Addressed adversarial review findings: invalid collection item add IDs now map to 422; missing item associations on remove now map to 404.
  • Stabilized existing CustomStreamSelectorTests culture assumptions so the required TZ=UTC suite is portable across Monday-first and Sunday-first cultures.

Verification:

  • dotnet restore ErsatzTV.sln --disable-parallel -v normal
  • dotnet build ErsatzTV.sln -v normal
  • TZ=UTC dotnet test ErsatzTV.sln -v normal

CI is running on PR #45; I will pause for review before merge.

Implemented in PR #45: https://gitea.tblindustries.be/timothy/ersatztv/pulls/45 What changed: - Added REST `CollectionController` for `POST/GET/GET{id}/PUT/DELETE /api/collections` plus `POST /api/collections/{id}/items` and `DELETE /api/collections/{id}/items/{mediaItemId}`. - Retrofitted `SmartCollectionController` to idiomatic REST routes under `/api/smart-collections`. - Applied the #2a patterns: class-level `ApiKeyAuthorizationFilter`, request DTO to command mapping, `ApiResults` status mapping, and typed `NotFoundError` preservation from handlers. - Added handler/controller/integration coverage using the `ErsatzTV.Tests` in-memory SQLite harness. - Addressed adversarial review findings: invalid collection item add IDs now map to 422; missing item associations on remove now map to 404. - Stabilized existing `CustomStreamSelectorTests` culture assumptions so the required `TZ=UTC` suite is portable across Monday-first and Sunday-first cultures. Verification: - `dotnet restore ErsatzTV.sln --disable-parallel -v normal` - `dotnet build ErsatzTV.sln -v normal` - `TZ=UTC dotnet test ErsatzTV.sln -v normal` CI is running on PR #45; I will pause for review before merge.
Author
Owner

Done

What was done: Implemented REST API #2b for collections: collection CRUD, collection item add/remove, and idiomatic REST smart-collection routes under /api/smart-collections. Reused the #2a patterns for ApiResults, NotFoundError, class-level ApiKeyAuthorizationFilter, and request DTO to command mapping. Added handler, controller/security, and in-memory SQLite lifecycle tests.

Root cause: n/a; feature slice. During verification, existing CustomStreamSelectorTests had unpinned Sunday-first culture assumptions, so those tests were stabilized and Monday-first coverage was added.

Files changed: API controllers/request DTOs, collection/smart-collection handlers, ApiResults test coverage, collection controller/security/integration tests, CustomStreamSelectorTests, and generated OpenAPI at ErsatzTV/wwwroot/openapi/v1.json.

Verification: Local dotnet build ErsatzTV.sln -v normal passed with 0 warnings/errors. Local TZ=UTC dotnet test ErsatzTV.sln -v normal passed. PR CI run 172 on 82f3a1e633a1706c069332f51dcb1c659b27d748 passed Build & test and EF migration integrity; image job skipped as expected for PR.

Deferred: Non-blocking follow-up candidate: OpenAPI currently documents 404/422 bodies as ProblemDetails while the #2a ApiResults convention returns plain error values. This is inherited from the existing pattern and not blocking this slice.

Follow-up issues: server-management#492 was created for the Codex /done-equivalent protocol work requested during this session.

Docs updated: Static OpenAPI /docs artifact regenerated and corrected for collection/smart-collection routes and response status codes. No operational docs impact.

## Done **What was done**: Implemented REST API #2b for collections: collection CRUD, collection item add/remove, and idiomatic REST smart-collection routes under `/api/smart-collections`. Reused the #2a patterns for `ApiResults`, `NotFoundError`, class-level `ApiKeyAuthorizationFilter`, and request DTO to command mapping. Added handler, controller/security, and in-memory SQLite lifecycle tests. **Root cause**: n/a; feature slice. During verification, existing `CustomStreamSelectorTests` had unpinned Sunday-first culture assumptions, so those tests were stabilized and Monday-first coverage was added. **Files changed**: API controllers/request DTOs, collection/smart-collection handlers, `ApiResults` test coverage, collection controller/security/integration tests, `CustomStreamSelectorTests`, and generated OpenAPI at `ErsatzTV/wwwroot/openapi/v1.json`. **Verification**: Local `dotnet build ErsatzTV.sln -v normal` passed with 0 warnings/errors. Local `TZ=UTC dotnet test ErsatzTV.sln -v normal` passed. PR CI run 172 on `82f3a1e633a1706c069332f51dcb1c659b27d748` passed Build & test and EF migration integrity; image job skipped as expected for PR. **Deferred**: Non-blocking follow-up candidate: OpenAPI currently documents 404/422 bodies as `ProblemDetails` while the #2a `ApiResults` convention returns plain error values. This is inherited from the existing pattern and not blocking this slice. **Follow-up issues**: server-management#492 was created for the Codex `/done`-equivalent protocol work requested during this session. **Docs updated**: Static OpenAPI `/docs` artifact regenerated and corrected for collection/smart-collection routes and response status codes. No operational docs impact.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#35