From 5f7eaed11243708b52f2bfff7e0f3ea47411839b Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Fri, 12 Jun 2026 22:26:28 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20Phase=209=20Wave=202=20landed=20?= =?UTF-8?q?=E2=80=94=20move=209.2=20from=20PLAN=20to=20COMPLETED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- COMPLETED.md | 21 +++++++++++++++++++++ PLAN.md | 20 -------------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/COMPLETED.md b/COMPLETED.md index 00e862a..0222b49 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -8,6 +8,27 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM ## Phase 9 — Release Medium Types +### 9.2 Wave 2 — API: medium reads + metadata uploads + +**Landed:** 2026-06-12 on dev. + +A new `api/release` controller — the medium unit is the *release*, not the track, so medium browse and metadata uploads are release-cardinal rather than bolted onto `api/track/page`. + +- **9.2.A — Release read endpoints (data layer + controller).** + - **What:** `GET api/release?medium={cut|session|mix}&page=&pageSize=&sort=` (unauth, paginated, medium filter additive — omitting returns all) and `GET api/release/{id}` (unauth, single release + medium metadata). The **list** read `Include`s the matching metadata table via a per-medium projection map; the **by-id** read always-`Include`s both metadata navs (two 1:1 unique-FK joins; non-matching media naturally yield nulls — no per-medium branching, no map). + - **Why:** The public CUTS/SESSIONS/MIXES surfaces and the CMS browsers all read releases by medium. One cohesive release-read family keeps `api/track/page` focused on Phase 8's track-list cases. + - **Shape:** Repository/service join through the metadata tables only for the relevant medium on list reads; base release reads never touch them. The projection map carries a dual responsibility: per-medium `Include` selection *and* the single enforcement point of the medium↔metadata correlation (a metadata DTO is populated iff the medium matches) — which is why it is not inlined in the controller. The honest extensibility guarantee is "one entry, one file," not "zero controller changes." `ReleaseDto` gains `Medium`, a **nullable** `ReleaseType?` (nulled at the mapping point for non-`Cut`), and optional nested `SessionMetadataDto?` / `MixMetadataDto?` (populated only for the matching medium — mirrors Phase 8's nested-`Release` choice, not denormalized flat fields). + - **Acceptance criteria:** `GET api/release?medium=session` returns Session releases with hero-image metadata included and no `MixMetadata`; `medium=cut` returns Cuts with neither metadata block and a non-null `ReleaseType`; non-Cut releases serialize `ReleaseType: null`; pagination + sort parity with `api/track/page`. +- **9.2.B — Metadata write endpoints.** + - **What:** `POST api/release/{id}/session/hero-image` (ApiKey, multipart — hero image → image vault → set `SessionMetadata.HeroImageEntryKey`) and `POST api/release/{id}/mix/waveform` (ApiKey, **no request body** — a server-side trigger: the API fetches the mix audio from its own vault, computes the high-resolution waveform via `WaveformProfileService` parameterized by resolution, stores the datum in the vault, sets `MixMetadata.WaveformEntryKey`). Both routes are resource-addressed — the release id rides the route. + - **Why:** The CMS authoring flows (Wave 3 B/C) need write paths for the medium-specific data, and the waveform is a *derived* datum the server can compute from audio it already owns. Mirroring the existing body-less `POST api/track/{trackId}/waveform` idiom makes the datum correct by construction (no trusting a client blob) and keeps the CMS free of any in-process data layer (its standing constraint). Splitting these from the track-upload endpoint keeps each endpoint single-responsibility. + - **Shape:** Hero-image upload mirrors the existing cover-art `UploadImageAsync` → image-vault → link pattern, targeting `HeroImageEntryKey`. The waveform trigger includes the `WaveformProfileService` refactor: a per-call resolution/profile parameter (today fixed via injected `WaveformProfileOptions.BucketCount = 512`) plus a distinct entry-key/vault target for the high-res datum — one pipeline, two resolutions (*One source, multiple views*). Both endpoints find-or-create the metadata row for the release. + - **Acceptance criteria:** Posting a hero image to a Session release sets `HeroImageEntryKey` and the image is served back through the existing image proxy; the body-less waveform trigger on a Mix release computes + stores a high-res datum, sets `WaveformEntryKey`, and the datum is retrievable. + +**Completion note:** Five new endpoints on `ReleaseController` implemented and integrated. `ReleaseRepository` + `ReleaseManager` (`IReleaseService`) in `DeepDrftData` provide paged medium-filtered reads and satellite metadata writes. `UnifiedReleaseService` orchestrates vault + SQL operations in `DeepDrftAPI/Services/`. `ReleaseDto` updated with `Medium` field and nested `SessionMetadataDto?` / `MixMetadataDto?` properties. Per-medium projection map enforces medium↔metadata correlation at the single mapping point. `WaveformProfileService` refactored with optional `bucketCount?` and `vaultName?` parameters supporting multiple resolutions. `VaultConstants.MixWaveforms = "mix-waveforms"` added. Five endpoints serve reads (`GET api/release` with medium filtering and pagination, `GET api/release/{id}` with both metadata tables included) and writes (`POST api/release/{id}/session/hero-image`, `POST api/release/{id}/mix/waveform`). All acceptance criteria met; Wave 3 (CMS) now unblocked. + +--- + ### 9.1 Wave 1 — Data model + migration **Landed:** 2026-06-12 on dev. diff --git a/PLAN.md b/PLAN.md index a182c5b..341662f 100644 --- a/PLAN.md +++ b/PLAN.md @@ -165,26 +165,6 @@ Sequenced as four waves. Wave 1 is a prerequisite for everything; within Waves 2 --- -### 9.2 Wave 2 — API: medium reads + metadata uploads - -A new `api/release` controller — the medium unit is the *release*, not the track, so medium browse and metadata uploads are release-cardinal rather than bolted onto `api/track/page`. - -- **9.2.A — Release read endpoints (data layer + controller).** - - **What:** `GET api/release?medium={cut|session|mix}&page=&pageSize=&sort=` (unauth, paginated, medium filter additive — omitting returns all) and `GET api/release/{id}` (unauth, single release + medium metadata). The **list** read `Include`s the matching metadata table via a per-medium projection map; the **by-id** read always-`Include`s both metadata navs (two 1:1 unique-FK joins; non-matching media naturally yield nulls — no per-medium branching, no map). - - **Why:** The public CUTS/SESSIONS/MIXES surfaces and the CMS browsers all read releases by medium. One cohesive release-read family keeps `api/track/page` focused on Phase 8's track-list cases. - - **Shape:** Repository/service join through the metadata tables only for the relevant medium on list reads; base release reads never touch them. The projection map carries a dual responsibility: per-medium `Include` selection *and* the single enforcement point of the medium↔metadata correlation (a metadata DTO is populated iff the medium matches) — which is why it is not inlined in the controller. The honest extensibility guarantee is "one entry, one file," not "zero controller changes." `ReleaseDto` gains `Medium`, a **nullable** `ReleaseType?` (nulled at the mapping point for non-`Cut`), and optional nested `SessionMetadataDto?` / `MixMetadataDto?` (populated only for the matching medium — mirrors Phase 8's nested-`Release` choice, not denormalized flat fields). - - **Acceptance criteria:** `GET api/release?medium=session` returns Session releases with hero-image metadata included and no `MixMetadata`; `medium=cut` returns Cuts with neither metadata block and a non-null `ReleaseType`; non-Cut releases serialize `ReleaseType: null`; pagination + sort parity with `api/track/page`. -- **9.2.B — Metadata write endpoints.** - - **What:** `POST api/release/{id}/session/hero-image` (ApiKey, multipart — hero image → image vault → set `SessionMetadata.HeroImageEntryKey`) and `POST api/release/{id}/mix/waveform` (ApiKey, **no request body** — a server-side trigger: the API fetches the mix audio from its own vault, computes the high-resolution waveform via `WaveformProfileService` parameterized by resolution, stores the datum in the vault, sets `MixMetadata.WaveformEntryKey`). Both routes are resource-addressed — the release id rides the route. - - **Why:** The CMS authoring flows (Wave 3 B/C) need write paths for the medium-specific data, and the waveform is a *derived* datum the server can compute from audio it already owns. Mirroring the existing body-less `POST api/track/{trackId}/waveform` idiom makes the datum correct by construction (no trusting a client blob) and keeps the CMS free of any in-process data layer (its standing constraint). Splitting these from the track-upload endpoint keeps each endpoint single-responsibility. - - **Shape:** Hero-image upload mirrors the existing cover-art `UploadImageAsync` → image-vault → link pattern, targeting `HeroImageEntryKey`. The waveform trigger includes the `WaveformProfileService` refactor: a per-call resolution/profile parameter (today fixed via injected `WaveformProfileOptions.BucketCount = 512`) plus a distinct entry-key/vault target for the high-res datum — one pipeline, two resolutions (*One source, multiple views*). Both endpoints find-or-create the metadata row for the release. - - **Acceptance criteria:** Posting a hero image to a Session release sets `HeroImageEntryKey` and the image is served back through the existing image proxy; the body-less waveform trigger on a Mix release computes + stores a high-res datum, sets `WaveformEntryKey`, and the datum is retrievable. -- **Prerequisite:** 9.1. -- **Open questions:** - - **New endpoints vs. `api/track/page` query-param extension.** Recommend the new `api/release` family (release-cardinal browse, medium metadata `Include`); `api/track/page` can gain a cheap `medium=` passthrough later if a track-level filter is ever needed. - ---- - ### 9.3 Wave 3 — CMS: Release Archive tab, medium selector, medium browsers - **9.3.A — Release Archive tab + medium selector.**