445 lines
29 KiB
Markdown
445 lines
29 KiB
Markdown
# CLAUDE.md - DeepDrftAPI
|
|
|
|
Guidance for working in the DeepDrftAPI project (the dual-database authority and AuthBlocks API host).
|
|
|
|
See the root `CLAUDE.md` for full architecture overview. This file covers what is specific to this project.
|
|
|
|
## One-line purpose
|
|
|
|
Dual-database authority for tracks (SQL metadata + FileDatabase binary), releases (SQL metadata with media-specific satellites), and images (FileDatabase binary); AuthBlocks API host (JWT auth, role/admin seed). Track endpoints expose CRUD with upload+persist, delete+cleanup, paged listing with filters, metadata operations, waveform profiles (512-bucket player-bar seeker + per-track high-res visualizer datum), and release associations. Release endpoints provide paged listing with medium filter, single-release read, and media-specific operations (session hero-image upload; mix waveform is a caller-less legacy delegate — the track-cardinal `GET api/track/{entryKey}/waveform/high-res` is the live fetch path). Image endpoints provide authenticated upload and unauthenticated streaming. ApiKey middleware for authenticated endpoints, JWT + AuthBlocks for auth. CORS, forwarded headers. **FileDatabase implementation lives in `DeepDrftContent`; SQL services in `DeepDrftData`.**
|
|
|
|
## What lives here now (only)
|
|
|
|
- `Program.cs`, `Startup.cs`: HTTP host config, DI wiring, middleware setup, port binding. AuthBlocks startup: `AddAuthBlocks`, `UseAuthBlocksStartupAsync`, `MapAuthBlocks`, authentication/authorization middleware.
|
|
- `Services/UnifiedTrackService.cs`: Host-internal orchestrator. Coordinates vault write + SQL persist for upload (`UploadAsync`), and SQL delete + vault remove for delete (`DeleteAsync`).
|
|
- `Services/UnifiedReleaseService.cs`: Host-internal orchestrator. Coordinates release mutations (mix waveform compute + store, session hero-image upload + link).
|
|
- `Controllers/TrackController.cs`: Track endpoints (see below).
|
|
- `Controllers/ReleaseController.cs`: Release endpoints (see below).
|
|
- `Middleware/ApiKeyAuthenticationMiddleware.cs`, `Middleware/ApiKeyAuthorizeAttribute.cs`: ApiKey validation logic (for track endpoints only).
|
|
- `Models/`: Settings POCOs only (`ApiKeySettings`, `CorsSettings`, `FileDatabaseSettings`). No domain code.
|
|
- `environment/filedatabase.json`: FileDatabase vault path config (loaded via CredentialTools, not in repo).
|
|
- `environment/apikey.json`: API key for track endpoints (loaded via CredentialTools, not in repo, must be created locally or at deployment).
|
|
- `environment/connections.json`: SQL and Auth connection strings (loaded via CredentialTools, not in repo, format: `{ "ConnectionStrings": { "DefaultConnection": "...", "Auth": "..." } }`).
|
|
- `environment/authblocks.json`: AuthBlocks configuration (JWT secret, email sender creds, admin seed creds) (loaded via CredentialTools, not in repo).
|
|
|
|
## What does NOT live here anymore
|
|
|
|
- `FileDatabase/`, `Processors/`, media models (`AudioBinary`, `ImageBinary`, etc.) — all in `DeepDrftContent` (class library).
|
|
- EF Core context and repository — in `DeepDrftData`.
|
|
- **Hosts only own HTTP surface and wiring.** New domain code goes in `*.Services` (shared libraries) or host-internal `Services/` folders (e.g., `UnifiedTrackService` here for dual-database orchestration).
|
|
|
|
## The endpoint surface
|
|
|
|
### GET api/track/{trackId} (unauthenticated)
|
|
|
|
Returns the WAV bytes from the `tracks` vault with HTTP Range support.
|
|
|
|
- **Route parameter `trackId`** (string): the entry id inside the `tracks` vault (i.e. `TrackEntity.EntryKey`).
|
|
- **Range header** (optional): HTTP Range header for byte-range requests (e.g., `Range: bytes=1000-`). Server responds with `206 Partial Content` and streams from the requested offset.
|
|
- Streams the file directly from disk with `enableRangeProcessing: true`, supporting both full-file and partial-range requests without synthesizing WAV headers or buffering.
|
|
- Returns 200 for full-file requests, 206 for Range requests, 404 if track not found, 500 if vault operations fail (with error swallowing — the vault returns `null`).
|
|
|
|
### GET api/track/albums (unauthenticated)
|
|
|
|
Returns a list of all releases with per-release track counts. Public browse data, same auth posture as `GET api/track/page`.
|
|
|
|
- **Response**: `List<ReleaseDto>` where each release carries its title, artist, genre, release date, medium, and track count.
|
|
- Returns 200 with the release list on success. Returns 500 on query error.
|
|
|
|
### GET api/track/genres (unauthenticated)
|
|
|
|
Returns distinct non-null genres with per-genre track counts. Public browse data, same auth posture as `GET api/track/page`.
|
|
|
|
- **Response**: A collection of genre strings with track counts.
|
|
- Returns 200 on success. Returns 500 on query error.
|
|
|
|
### GET api/track/random (unauthenticated)
|
|
|
|
Picks one track at random from the full library and returns its metadata. Public, same auth posture as `GET api/track/page`.
|
|
|
|
- **Response**: A single `TrackDto` selected uniformly at random.
|
|
- Returns 200 on success. Returns 404 if the library is empty (a valid state). Returns 500 on query error.
|
|
|
|
### GET api/track/{trackId}/waveform (unauthenticated)
|
|
|
|
Returns the stored waveform loudness profile for a track as base64-encoded bytes. Public listener data, same auth posture as `GET api/track/{trackId}`.
|
|
|
|
- **Route parameter `trackId`** (string): the entry id (TrackEntity.EntryKey).
|
|
- **Response**: `WaveformProfileDto` with `BucketCount` (number of loudness buckets) and `Data` (base64-encoded byte array).
|
|
- Returns 200 on success. Returns 404 if no profile is stored (existing tracks may predate profiling, or computation failed at upload — the frontend falls back to a flat seekbar). Returns 500 on vault error.
|
|
|
|
### POST api/track/{trackId}/waveform ([ApiKeyAuthorize])
|
|
|
|
Admin backfill: computes and stores a waveform profile for an existing track from its vault audio.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `trackId`** (string): the entry id (TrackEntity.EntryKey).
|
|
- Fetches audio from vault, decodes it, computes a loudness profile, and stores the profile in the `waveform-profiles` vault.
|
|
- Returns 200 on success. Returns 404 if no audio is stored under that key. Returns 500 if WAV decoding or vault write fails.
|
|
|
|
### GET api/track/{trackId}/waveform/high-res (unauthenticated)
|
|
|
|
Track-cardinal high-res datum fetch. Returns the per-track duration-derived high-res waveform datum (~333 samples/sec) from the `track-waveforms` vault. This is the live read path for the `WaveformVisualizer` bridge — the release-level mix waveform endpoint is a caller-less legacy delegate.
|
|
|
|
- **Route parameter `trackId`** (string): the entry id (TrackEntity.EntryKey).
|
|
- **Response**: `WaveformProfileDto` with `BucketCount` and `Data` (base64).
|
|
- Returns 200 on success. Returns 404 if no high-res datum is stored (graceful — not-yet-backfilled tracks fall back to no visualizer data). Returns 500 on vault error.
|
|
|
|
### POST api/track/{trackId}/waveform/high-res ([ApiKeyAuthorize])
|
|
|
|
Server-side trigger: compute and store the per-track high-res datum for any track from its vault audio, keyed by `EntryKey` in the `track-waveforms` vault. Drives the CMS per-row "Generate high-res" action and the CMS batch backfill action. Generalised off Mix-only in Phase 12.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `trackId`** (string): the entry id (TrackEntity.EntryKey).
|
|
- Calls `WaveformProfileService.ComputeAndStoreHighResAsync` via `UnifiedTrackService`.
|
|
- Returns 200 on success. Returns 404 if no audio stored under that key. Returns 500 on compute/storage failure.
|
|
|
|
### GET api/track/meta/by-key/{entryKey} (unauthenticated)
|
|
|
|
Single track metadata by vault entry key (EntryKey). Unauthenticated, reachable through the public proxy.
|
|
|
|
- **Route parameter `entryKey`** (string): the TrackEntity.EntryKey.
|
|
- **Response**: `TrackDto` for the matching track.
|
|
- Returns 200 on success. Returns 404 if not found. Returns 500 on query error.
|
|
|
|
### GET api/track/waveform-status ([ApiKeyAuthorize])
|
|
|
|
Admin backfill view: returns every track with flags indicating whether each waveform type is stored. Used by the CMS track list to flag tracks needing waveform computation.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Response**: `List<WaveformStatusDto>` with `TrackId`, `EntryKey`, `TrackName`, `HasProfile` (bool — 512-bucket player-bar seeker profile in `waveform-profiles` vault), and `HasHighRes` (bool — duration-derived high-res visualizer datum in `track-waveforms` vault).
|
|
- Returns 200 on success. Returns 500 on query error.
|
|
|
|
### POST api/track/duration/backfill ([ApiKeyAuthorize])
|
|
|
|
Admin backfill: for every track whose `DurationSeconds` SQL column is still null, reads the `AudioBinary.Duration` from the vault and writes it to SQL. Idempotent — a re-run only touches still-null rows; rows that already have a value are skipped.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- No request body.
|
|
- Calls `UnifiedTrackService.BackfillDurationsAsync`. Lives on `TrackController` in the literal-route block (before `{trackId}` routes, so the segment is never treated as a trackId).
|
|
- **Response**: `{ updated: int, skipped: int }` — counts of rows written vs. already-populated rows bypassed.
|
|
- Returns 200 on success. Returns 500 if the backfill operation fails.
|
|
|
|
### DELETE api/track/release/{id:long} ([ApiKeyAuthorize])
|
|
|
|
Soft-delete a release row. Used by the albums browser to remove an orphaned release (one with no live tracks).
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `id`** (long): the SQL release ID.
|
|
- Calls `ITrackService.DeleteRelease`.
|
|
- Returns 200 on success. Returns 500 on deletion error.
|
|
|
|
### PUT api/track/{trackId} ([ApiKeyAuthorize])
|
|
|
|
**Authenticated endpoint.** Writes pre-processed audio bytes to the `tracks` vault.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `trackId`** (string): the entry id to store under.
|
|
- **Body**: `AudioBinaryDto` (base64 buffer + size + mime + duration + bitrate). This endpoint receives an already-processed audio DTO, not a raw WAV file.
|
|
- Validates MIME type (rejects unsupported types with `.bin` sentinel). Delegates to `FileDatabase.RegisterResourceAsync`.
|
|
- Rarely used in production (the CLI calls `FileDatabase.RegisterResourceAsync` directly). Exists for potential future web-side intake paths.
|
|
- Returns 200 on success, 401 if ApiKey invalid, 400 if MIME invalid.
|
|
|
|
### POST api/track/upload ([ApiKeyAuthorize])
|
|
|
|
**Authenticated endpoint.** Accepts a raw audio file upload (.wav, .mp3, .flac) + metadata as `multipart/form-data`, processes the file, stores it in the vault, and persists metadata to SQL. Returns the fully persisted `TrackDto` with `Id` populated.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Form fields**:
|
|
- `audioFile` (`IFormFile`, required): the audio bytes. File name must end in `.wav`, `.mp3`, or `.flac`.
|
|
- `trackName` (string, required)
|
|
- `artist` (string, required)
|
|
- `album` (string, optional)
|
|
- `genre` (string, optional)
|
|
- `releaseDate` (string, optional, format `YYYY-MM-DD`)
|
|
- `createdByUserId` (long, required): audit trail — who uploaded this track.
|
|
- `releaseType` (string, optional): enum `ReleaseType` (e.g., `Single`, `Album`, `EP`). Defaults to `Single` if null or unrecognized.
|
|
- `medium` (string, optional): enum `ReleaseMedium` (e.g., `Cut`, `Mix`, `Session`). Defaults to `Cut` if null or unrecognized.
|
|
- `trackNumber` (int?, optional): track position within the release (1-based). Defaults to 1 if ≤ 0 or null.
|
|
- The upload stream is copied to a temp file under `Path.GetTempPath()` with the appropriate extension (`.wav`, `.mp3`, or `.flac`). The audio processor reads from disk and requires the correct extension for format detection. The temp file is always deleted in a `finally` block — success or failure.
|
|
- `[RequestSizeLimit(1 GB)]` + `[RequestFormLimits(MultipartBodyLengthLimit = 1 GB)]` lift the per-request ceiling above the framework default (~28 MB) so production-sized files are accepted. The body is streamed to the temp file, not buffered in memory.
|
|
- Calls `UnifiedTrackService.UploadAsync`, which orchestrates: `TrackContentService.AddTrackAsync` (format-agnostic vault write via router) → `TrackManager` (SQL persist with `createdByUserId`).
|
|
- Returns 200 with the **persisted** `TrackDto` JSON (Id populated) on success. Returns 400 for missing/invalid form fields or unsupported audio format. Returns 409 if the request violates domain cardinality rules (e.g., track number conflict). Returns 500 if processing fails.
|
|
|
|
### DELETE api/track/{id:long} ([ApiKeyAuthorize])
|
|
|
|
**Authenticated endpoint.** Removes a track: SQL row first, then vault entry. `UnifiedTrackService` owns the ordering.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `id`** (long): the SQL track ID (not EntryKey).
|
|
- Calls `UnifiedTrackService.DeleteAsync`, which: looks up SQL row → deletes SQL row → deletes vault entry via EntryKey.
|
|
- Returns 200 on success, 404 if track not found, 500 if deletion fails.
|
|
|
|
### GET api/track/page (unauthenticated)
|
|
|
|
Paged metadata list from SQL with optional filtering. Public browser data, same auth posture as `GET api/track/{id}`.
|
|
|
|
- **Query parameters**:
|
|
- `page` (int, optional, default 1): 1-based page number.
|
|
- `pageSize` (int, optional, default 20): tracks per page.
|
|
- `sortColumn` (string, optional): sort field. Supported: `"TrackName"`, `"Artist"`, `"Album"`, `"Genre"`, `"ReleaseDate"`, `"TrackNumber"`. Defaults to `Id`.
|
|
- `sortDescending` (bool, optional, default false): sort direction.
|
|
- `q` (string, optional): search text filter (matches track name / artist).
|
|
- `album` (string, optional): album title filter.
|
|
- `genre` (string, optional): genre filter.
|
|
- `releaseId` (long?, optional): release ID filter (authoritative join; preferred over album title).
|
|
- Calls `ITrackService.GetPaged` with optional `TrackFilter` (null if all filter params are empty).
|
|
- Returns 200 with `PagedResult<TrackDto>` JSON (`Items`, `TotalCount`, `PageNumber`, `PageSize`). Returns 500 on query error.
|
|
|
|
### GET api/track/meta/{id:long} ([ApiKeyAuthorize])
|
|
|
|
**Authenticated endpoint.** Single track metadata from SQL by ID.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `id`** (long): the SQL track ID.
|
|
- Calls `ITrackService.GetById`, which returns the track as `TrackDto` or null.
|
|
- Returns 200 with `TrackDto` JSON on success. Returns 404 if not found. Returns 500 on query error.
|
|
|
|
### PUT api/track/meta/{id:long} ([ApiKeyAuthorize])
|
|
|
|
**Authenticated endpoint.** Updates track metadata in SQL. EntryKey (the vault link) is immutable.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `id`** (long): the SQL track ID.
|
|
- **Body**: `UpdateTrackMetadataRequest` with fields:
|
|
- `TrackName` (string, required)
|
|
- `Artist` (string, required)
|
|
- `Album` (string?, optional)
|
|
- `Genre` (string?, optional)
|
|
- `ReleaseDate` (DateOnly?, optional)
|
|
- `ImagePath` (string?, tri-state: null = no change, "" = clear, value = set)
|
|
- `ReleaseType` (ReleaseType?, optional): updates the linked release if present; null = no change.
|
|
- `Medium` (ReleaseMedium?, optional): updates the linked release if present; null = no change. When `Medium` is set to non-`Cut`, also resets `ReleaseType` to `Single` (the DB default) to avoid stale studio-format values.
|
|
- `TrackNumber` (int?, optional): track position within the release; validated > 0 when provided.
|
|
- Looks up SQL row by ID, updates the provided fields, and persists via `ITrackService.Update`. Track-cardinal fields (`TrackName`, `TrackNumber`) update the track row; release-cardinal fields (`Artist`, `Album`, `Genre`, `ReleaseDate`, `ImagePath`, `ReleaseType`, `Medium`) update the linked release (if present; loose tracks ignore these).
|
|
- Returns 200 on success. Returns 400 if `TrackNumber` ≤ 0 (when provided). Returns 404 if track not found. Returns 500 on update error.
|
|
|
|
## The image endpoints (two endpoints)
|
|
|
|
### POST api/image/upload ([ApiKeyAuthorize])
|
|
|
|
**Authenticated endpoint.** Accepts an image file upload, stores it in the `images` vault, and returns the entry key.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Form field `image`** (`IFormFile`, required): the image bytes (PNG, JPEG, or other format supported by `ImageProcessor`). Maximum file size 50 MB.
|
|
- Calls `FileDatabase.RegisterResourceAsync("images", entryKey, imageBinary)` where `imageBinary` is produced by `ImageProcessor` (computes aspect ratio from headers, defaults 1.0 for unsupported formats).
|
|
- Returns 200 with JSON `{ entryKey }` on success. Returns 400 for missing file. Returns 500 if processing or vault operations fail.
|
|
|
|
### GET api/image/{entryKey} (unauthenticated)
|
|
|
|
Returns image bytes from the `images` vault.
|
|
|
|
- **Route parameter `entryKey`** (string): the entry id inside the `images` vault.
|
|
- Streams the image file directly from disk without buffering.
|
|
- Returns 404 if image not found. Returns 500 if vault operations fail (with error swallowing — the vault returns `null`).
|
|
|
|
## The release endpoints
|
|
|
|
### GET api/release (unauthenticated)
|
|
|
|
Paged release list, optionally filtered to one medium. Public browse data, same auth posture as `GET api/track/page`.
|
|
|
|
- **Query parameters**:
|
|
- `medium` (string, optional): enum `ReleaseMedium` (e.g., `Cut`, `Mix`, `Session`). If provided, only releases of that medium are returned; the matching medium's metadata satellite is populated, others are null.
|
|
- `page` (int, optional, default 1): 1-based page number.
|
|
- `pageSize` (int, optional, default 20): releases per page.
|
|
- `sortColumn` (string, optional): sort field (typically `"Title"`).
|
|
- `sortDescending` (bool, optional, default false): sort direction.
|
|
- Returns 200 with `PagedResult<ReleaseDto>` on success. Returns 400 if `medium` is unrecognized. Returns 500 on query error.
|
|
|
|
### GET api/release/{entryKey} (unauthenticated)
|
|
|
|
Single release with both metadata navs (nulls for non-matching media). Public, same auth posture as `GET api/release`. Addresses releases by their opaque public `EntryKey` (GUID string), never the int PK (Phase 11 §3e).
|
|
|
|
- **Route parameter `entryKey`** (string): the release's `EntryKey` (the public handle).
|
|
- **Response**: `ReleaseDto` with `Id`, `EntryKey`, `Title`, `Artist`, `Genre`, `ReleaseDate`, `Medium`, `ImagePath`, and media-specific metadata satellites (`MixMetadata` for Cut/Mix, `SessionMetadata` for Session; others null).
|
|
- Returns 200 on success. Returns 404 if not found. Returns 500 on query error.
|
|
|
|
### GET api/release/{entryKey}/mix/waveform (unauthenticated — caller-less legacy delegate)
|
|
|
|
Legacy endpoint: formerly served the high-res waveform datum for a Mix release from the `mix-waveforms` vault. **No longer called by the client** — the live fetch path is now the track-cardinal `GET api/track/{trackId}/waveform/high-res` (Phase 12). The endpoint is retained in the API but has no active callers. `UnifiedReleaseService.TriggerMixWaveformAsync` now delegates to `WaveformProfileService.ComputeAndStoreHighResAsync` (the same shared seam used by the upload path and the generalized CMS generate action).
|
|
|
|
- **Route parameter `entryKey`** (string): the release's `EntryKey`.
|
|
- **Response**: `WaveformProfileDto` with `BucketCount` and `Data` (base64).
|
|
- Returns 200 on success. Returns 404 if the release is not a Mix, carries no waveform key, or no datum is stored. Returns 500 on query/vault error.
|
|
|
|
### POST api/release/{id:long}/mix/waveform ([ApiKeyAuthorize])
|
|
|
|
Server-side trigger: fetch the Mix's track audio from the vault, compute the duration-derived high-res datum, store it in the `track-waveforms` vault under the track's `EntryKey`, and link it via `MixMetadata.WaveformEntryKey`. Delegates to `WaveformProfileService.ComputeAndStoreHighResAsync` — the same shared seam used by the upload path and the generalized CMS generate action. No request body.
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `id`** (long): the SQL release ID.
|
|
- Calls `UnifiedReleaseService.TriggerMixWaveformAsync`.
|
|
- Returns 200 on success. Returns 404 if the release is missing, is not a Mix, has no track, or the track audio is not stored. Returns 500 on compute/storage failure.
|
|
|
|
### POST api/release/{id:long}/session/hero-image ([ApiKeyAuthorize])
|
|
|
|
Stores a hero image in the `images` vault and links it via `SessionMetadata.HeroImageEntryKey`. The release must be a Session medium (enforced in the service).
|
|
|
|
- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`.
|
|
- **Route parameter `id`** (long): the SQL release ID.
|
|
- **Form field `image`** (`IFormFile`, required): the image bytes (PNG, JPEG, or other format supported by `ImageProcessor`). Maximum file size 50 MB.
|
|
- Validates MIME type (rejects unsupported types with `.bin` sentinel). Calls `UnifiedReleaseService.SetHeroImageAsync`.
|
|
- Returns 200 on success. Returns 400 for missing file or unsupported MIME type. Returns 404 if release not found. Returns 500 on processing or vault failure.
|
|
|
|
## The stats endpoints
|
|
|
|
### GET api/stats/home (unauthenticated)
|
|
|
|
Aggregate figures behind the public home hero stat row (`NowPlayingStats`). A single read returns everything the three cards need so the client makes one round-trip. Public, same auth posture as `GET api/track/page`.
|
|
|
|
- **Response**: `HomeStatsDto` with:
|
|
- `CutTrackCount` (int): total non-deleted tracks on Cut-medium releases.
|
|
- `CutReleaseTypeCounts` (`List<CutReleaseTypeCount>`): per-`ReleaseType` Cut release counts; zero-count types are absent (zero-suppressed server-side).
|
|
- `MixReleaseCount` (int): total non-deleted Mix-medium releases.
|
|
- `MixRuntimeSeconds` (double): sum of `DurationSeconds` across all non-deleted tracks on Mix releases (null durations count as 0).
|
|
- Aggregated in `TrackRepository.GetHomeStatsAsync`, surfaced via `ITrackService`/`TrackManager`. Controller is `StatsController` — a thin HTTP boundary; no domain logic lives there.
|
|
- Returns 200 on success. Returns 500 on query error.
|
|
|
|
## ApiKey middleware behaviour
|
|
|
|
`ApiKeyAuthenticationMiddleware` runs on every request but only enforces on endpoints with `[ApiKeyAuthorize]` metadata.
|
|
|
|
- Reads header `ApiKey`.
|
|
- Compares against `ApiKeySettings.ApiKey` from `environment/apikey.json`.
|
|
- Returns 401 with body `"API Key was not provided"` or `"Unauthorized client"` if validation fails.
|
|
- Endpoints without `[ApiKeyAuthorize]` skip the check entirely (e.g., `GET api/track/{id}` is unauthenticated).
|
|
|
|
## CORS configuration
|
|
|
|
`CorsSettings.AllowedOrigins` is **required** — the app throws on startup if missing. Policy is named `ContentApiPolicy`:
|
|
|
|
- `AllowCredentials()`
|
|
- `AllowAnyMethod()`
|
|
- `AllowAnyHeader()`
|
|
|
|
Configured in `Startup.ConfigureDomainServices()`, applied to all endpoints via `UseCors()`.
|
|
|
|
## Forwarded headers
|
|
|
|
**Enabled only in `Production` mode** (via `if (app.Environment.IsProduction())`). This differs from `DeepDrftWeb`, which enables them always. Be aware when debugging proxy issues.
|
|
|
|
`UseForwardedHeaders()` processes `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host` so the app knows its real client IP and scheme when sitting behind nginx.
|
|
|
|
## Startup wiring (Startup.ConfigureDomainServices + Program.cs)
|
|
|
|
**In `Startup.ConfigureDomainServices`** (FileDatabase + binary services):
|
|
|
|
1. Load `environment/filedatabase.json` via `CredentialTools.ResolvePathOrThrow("filedatabase", ...)` and bind `FileDatabaseSettings`.
|
|
2. Await `FileDatabase.FromAsync(VaultPath)` to load or create the database.
|
|
3. Register `FileDatabase` as singleton.
|
|
4. Ensure the `tracks` vault exists (type `MediaVaultType.Audio`, created on first boot if missing).
|
|
5. Ensure the `images` vault exists (type `MediaVaultType.Image`, created on first boot if missing) via `InitializeImageVault`.
|
|
5a. Ensure the `track-waveforms` vault exists (type `MediaVaultType.Media`, created on first boot if missing) — holds per-track high-res visualizer datum keyed by `TrackEntity.EntryKey`.
|
|
6. Register singletons: `AudioProcessor`, `ImageProcessor`, `TrackService` (the `DeepDrftContent` version for vault operations), `WaveformProfileService`.
|
|
|
|
**In `Program.cs`** (SQL + AuthBlocks + wiring):
|
|
|
|
6. Load `environment/connections.json` via `CredentialTools.ResolvePathOrThrow("connections", ...)` — contains both `DefaultConnection` (SQL metadata) and `Auth` (AuthBlocks Identity database).
|
|
7. **AuthBlocks startup:** Call `builder.Services.AddAuthBlocks(options => { ... })` with `Auth` connection string and settings from `AuthBlocks:*` config keys. Load `environment/authblocks.json` for JWT secret, email sender creds, and admin seed creds. This registers JWT bearer auth scheme and EF Identity.
|
|
8. Register `DbContext<DeepDrftContext>` (scoped) with `DefaultConnection` from config.
|
|
9. Register scoped: `TrackRepository`, `TrackManager`, `ITrackService` (factory resolves to `TrackManager`), `UnifiedTrackService`.
|
|
10. **After `app.Build()`:** Call `await app.Services.UseAuthBlocksStartupAsync()` to apply migrations and seed roles + admin user to the Auth database.
|
|
11. Configure forwarded headers (production-only) for reverse proxy support.
|
|
12. Load `environment/apikey.json` and register API key middleware.
|
|
13. Configure CORS policy (`ContentApiPolicy`): AllowAnyMethod, AllowAnyHeader, AllowCredentials, specific origins from config (includes DeepDrftManager origin for cross-origin auth calls).
|
|
14. **Map AuthBlocks endpoints:** Call `app.MapAuthBlocks()` to mount `/api/auth/*` and `/api/users/*` endpoints. Ensure `app.UseAuthentication()` and `app.UseAuthorization()` are in the middleware pipeline (required for JWT bearer auth).
|
|
15. Verify ApiKey middleware ordering: it must not interfere with JWT middleware. ApiKey gates only `[ApiKeyAuthorize]`-decorated track endpoints; JWT gates AuthBlocks endpoints.
|
|
|
|
The singleton `FileDatabase` is thread-safe for reads. Writes are atomic at the vault level (index updates are serialised). The `IndexWatcher` reloads the vault index if an external process (e.g., CLI) writes to it, so a long-running web host stays consistent. SQL services are scoped (DbContext not thread-safe).
|
|
|
|
## OpenAPI
|
|
|
|
Mapped in `Development` only. Swagger UI at `/swagger` for testing endpoints locally.
|
|
|
|
## Configuration files
|
|
|
|
- `appsettings.json`: Logging, hosting, CORS, and AuthBlocks config. **Does not contain secrets.**
|
|
- `Logging`: standard ASP.NET structure.
|
|
- `CorsSettings.AllowedOrigins`: array of origin URLs allowed to call the API (required; throws on startup if missing).
|
|
- `AuthBlocks:Jwt:Issuer`, `AuthBlocks:Jwt:Audience`: JWT validation settings (loaded from `environment/authblocks.json`).
|
|
- `environment/filedatabase.json` (required, loaded via CredentialTools, not in repo):
|
|
```json
|
|
{
|
|
"FileDatabaseSettings": {
|
|
"VaultPath": "../Database/Vaults"
|
|
}
|
|
}
|
|
```
|
|
- `environment/apikey.json` (required at runtime, loaded via CredentialTools, not in repo):
|
|
```json
|
|
{
|
|
"ApiKeySettings": {
|
|
"ApiKey": "your-secret-key"
|
|
}
|
|
}
|
|
```
|
|
- `environment/connections.json` (required, loaded via CredentialTools, not in repo):
|
|
```json
|
|
{
|
|
"ConnectionStrings": {
|
|
"DefaultConnection": "Host=localhost;Database=deepdrft;Username=postgres;Password=...",
|
|
"Auth": "Host=localhost;Database=deepdrft_auth;Username=postgres;Password=..."
|
|
}
|
|
}
|
|
```
|
|
- `environment/authblocks.json` (required, loaded via CredentialTools, not in repo):
|
|
```json
|
|
{
|
|
"AuthBlocks": {
|
|
"Jwt": {
|
|
"Secret": "your-signing-secret-min-32-chars",
|
|
"Issuer": "https://deepdrft.api",
|
|
"Audience": "https://deepdrft.com"
|
|
},
|
|
"Email": {
|
|
"Host": "smtp.provider.com",
|
|
"Token": "your-smtp-password"
|
|
},
|
|
"Admin": {
|
|
"UserName": "admin",
|
|
"Email": "admin@deepdrft.com",
|
|
"Password": "initial-admin-password"
|
|
},
|
|
"SupportEmail": "support@deepdrft.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Development commands
|
|
|
|
```bash
|
|
# Run the API host (default https://localhost:5002)
|
|
dotnet run --project DeepDrftAPI
|
|
|
|
# Watch during development
|
|
dotnet watch run --project DeepDrftAPI
|
|
|
|
# Build
|
|
dotnet build DeepDrftAPI
|
|
|
|
# Test track endpoints (requires API key in environment/apikey.json)
|
|
curl -H "ApiKey: your-secret-key" -X GET https://localhost:5002/api/track/page \
|
|
-H "Accept: application/json"
|
|
|
|
curl https://localhost:5002/api/track/test-entry-key
|
|
|
|
# Test auth endpoints (AuthBlocks API)
|
|
curl -X POST https://localhost:5002/api/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"admin@deepdrft.com","password":"initial-admin-password"}'
|
|
```
|
|
|
|
## Important patterns
|
|
|
|
- **Result types**: Controllers return `ActionResult<T>`. Service calls return `Result` or `ResultContainer<T>` from NetBlocks. The controller checks `Success` and returns 200/4xx/5xx accordingly.
|
|
- **Error swallowing**: FileDatabase operations return `null` or `false` on failure. The controller surfaces this as 500. Never throw — check return values.
|
|
- **Async/await**: All operations are async.
|
|
- **Vault operations**: Always use the injected `FileDatabase` singleton. Never construct a new one — it has the `IndexWatcher` and is the source of truth.
|
|
|
|
## The FileDatabase import
|
|
|
|
See `DeepDrftContent/CLAUDE.md` for the FileDatabase API and semantics. This host only provides the HTTP surface over it and the AuthBlocks authority.
|
|
|
|
When working with this project, focus on the HTTP surface (controllers, middleware, CORS, forwarded headers, AuthBlocks wiring) and the dual-database orchestration via `UnifiedTrackService`. New domain logic goes in `DeepDrftContent` (FileDatabase) or `DeepDrftData` (SQL). Keep this host focused on HTTP boundaries and wiring.
|