From 2574ed9af8fd58f6a46f4183d159e535f1db9d5f Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Mon, 25 May 2026 09:33:28 -0400 Subject: [PATCH] docs: update CLAUDE.md files for Content SQL authority and Manager HTTP-only --- CLAUDE.md | 11 ++-- DeepDrftContent/CLAUDE.md | 123 ++++++++++++++++++++++++++------------ DeepDrftData/CLAUDE.md | 81 ++++++++++++++----------- 3 files changed, 136 insertions(+), 79 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7fb9bba..655666b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,10 +10,10 @@ DeepDrftHome is a **net10.0** solution consisting of ten projects implementing a - **DeepDrftPublic**: ASP.NET Core host. Blazor Web App with Server + WASM render modes. Owns the SQL-backed `api/track/page` endpoint, MudBlazor theme prerender, and TypeScript→JS audio interop. Public-facing site for listeners. - **DeepDrftPublic.Client**: Blazor WebAssembly assembly. All interactive UI (pages, player stack, dark-mode plumbing, HTTP clients for both backends). Consumed by the public site. -- **DeepDrftManager**: ASP.NET Core host. Blazor Web App with server-rendered `InteractiveServer` render mode. Hosts all CMS Razor components and pages under `Components/Pages/Cms/`, `Components/Pages/Tracks/`, `Components/Layout/CmsLayout.razor`, and `Components/Shared/` (all inlined from the former `DeepDrftCms` RCL). Gated by AuthBlocks login and hierarchical `Admin` role authorization. CMS mutations (track upload proxy, vault delete) are handled by `ICmsTrackService` / `CmsTrackService` injected directly into Blazor components; no in-process HTTP controllers. +- **DeepDrftManager**: ASP.NET Core host. Blazor Web App with server-rendered `InteractiveServer` render mode. Hosts all CMS Razor components and pages under `Components/Pages/Cms/`, `Components/Pages/Tracks/`, `Components/Layout/CmsLayout.razor`, and `Components/Shared/` (all inlined from the former `DeepDrftCms` RCL). Gated by AuthBlocks login and hierarchical `Admin` role authorization. All track operations (upload, metadata read/write, delete) are HTTP proxies via `ICmsTrackService` / `CmsTrackService` injected directly into Blazor components; no in-process data layer. - **DeepDrftShared.Client**: Razor Class Library. Shared Blazor components consumed by both `DeepDrftPublic` and `DeepDrftManager` for consistency across public and admin surfaces. -- **DeepDrftData**: Class library. EF Core domain logic: `DeepDrftContext`, `TrackConfiguration`, `Migrations`, `TrackRepository`, `TrackService`. Shared between hosts. -- **DeepDrftContent**: ASP.NET Core host. Binary content API (`GET api/track/{id}` unauthenticated, `PUT api/track/{id}` ApiKey-protected). Returns audio bytes with optional WAV-aware offset streaming. +- **DeepDrftData**: Class library. EF Core domain logic: `DeepDrftContext`, `TrackConfiguration`, `Migrations`, `TrackRepository`, `TrackService`, `TrackManager`. Consumed by `DeepDrftContent`, `DeepDrftPublic`, and `DeepDrftCli`. +- **DeepDrftContent**: ASP.NET Core host. Dual-database authority (SQL metadata + FileDatabase binary). Seven endpoints: `GET api/track/{id}` unauthenticated streaming; `PUT api/track/{id}` vault write (ApiKey); `POST api/track/upload` upload + SQL persist (ApiKey); `DELETE api/track/{id:long}` SQL delete + vault remove (ApiKey); `GET api/track/page` paged metadata list (ApiKey); `GET api/track/meta/{id:long}` single metadata (ApiKey); `PUT api/track/meta/{id:long}` metadata update (ApiKey). - **DeepDrftContent.Services**: Class library. The FileDatabase implementation in full (Models, Services, Utils, Abstractions, Constants), `WavOffsetService`, `AudioProcessor`, content-side `TrackService`. Consumed by hosts and tests. - **DeepDrftModels**: Shared contracts. `TrackEntity`, `TrackDto`, `PagingParameters`, `PagedResult`. Every project references this. - **DeepDrftCli**: Console app. Two modes: classic CLI (`add` / `list` / `help`) and Terminal.Gui (`gui`). Direct access to both databases (local admin tool, not a network client). @@ -57,7 +57,7 @@ The split between host projects (`DeepDrftPublic`, `DeepDrftManager`, `DeepDrftC `TrackEntity` holds *only* metadata. The link to binary content is `EntryKey` (string) — the entry id inside the `tracks` vault in FileDatabase. Dual-database add flow: 1. `DeepDrftContent.Services.TrackService.AddTrackFromWavAsync` processes WAV, generates entry GUID, stores audio in vault, returns unpersisted `TrackEntity`. -2. `DeepDrftData.TrackService.Create` saves to SQLite and returns the persisted entity with `Id`. +2. `DeepDrftContent.Services.UnifiedTrackService.UploadAsync` persists the entity to SQL via `DeepDrftData.TrackManager` and returns the persisted entity with `Id`. If step 1 succeeds and step 2 fails, audio is orphaned in the vault (no rollback today). @@ -129,8 +129,7 @@ All projects load secrets via `CredentialTools.ResolvePathOrThrow()` from gitign - `DeepDrftPublic/appsettings.json`: Logging and URL config. Secrets loaded from `environment/connections.json` (SQL connection string). - `DeepDrftManager/appsettings.json`: Logging and URL config. Secrets loaded from `environment/apikey.json` (DeepDrftContent API key), `environment/connections.json` (SQL and Auth connection strings), `environment/authblocks.json` (AuthBlocks settings). -- `DeepDrftContent/environment/filedatabase.json`: FileDatabase vault path. Loaded via CredentialTools. -- `DeepDrftContent/environment/apikey.json`: API key. Loaded via CredentialTools (not in repo). +- `DeepDrftContent/appsettings.json`: Logging and hosting config. Secrets loaded from `environment/filedatabase.json` (FileDatabase vault path), `environment/apikey.json` (API key), `environment/connections.json` (SQL connection string). - `DeepDrftCli/environment/connections.json`: CLI config (`ConnectionString`, `VaultPath`). Loaded via CredentialTools. ## Folder-Level Guidance diff --git a/DeepDrftContent/CLAUDE.md b/DeepDrftContent/CLAUDE.md index 1e418d7..04779f0 100644 --- a/DeepDrftContent/CLAUDE.md +++ b/DeepDrftContent/CLAUDE.md @@ -6,60 +6,51 @@ See the root `CLAUDE.md` for full architecture overview. This file covers what i ## One-line purpose -The binary content API host. ApiKey middleware, CORS, forwarded headers. Returns audio bytes (with optional WAV-aware offset) and accepts authenticated PUTs. **FileDatabase implementation lives in `DeepDrftContent.Services`, not here.** +The dual-database authority for tracks: SQL metadata and FileDatabase binary. Seven endpoints expose track CRUD with upload+persist, delete+cleanup, paged listing, and metadata operations. ApiKey middleware, CORS, forwarded headers. **FileDatabase implementation lives in `DeepDrftContent.Services`; SQL services in `DeepDrftData`.** ## What lives here now (only) - `Program.cs`, `Startup.cs`: HTTP host config, DI wiring, middleware setup, port binding. -- `Controllers/TrackController.cs`: Four endpoints (see below). +- `Services/UnifiedTrackService.cs`: Host-internal orchestrator. Coordinates vault write + SQL persist for upload (`UploadAsync`), and SQL delete + vault remove for delete (`DeleteAsync`). +- `Controllers/TrackController.cs`: Seven endpoints (see below). - `Middleware/ApiKeyAuthenticationMiddleware.cs`, `Middleware/ApiKeyAuthorizeAttribute.cs`: ApiKey validation logic. - `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 (loaded via CredentialTools, not in repo, must be created locally or at deployment). +- `environment/connections.json`: SQL connection string (loaded via CredentialTools, not in repo, format: `{ "ConnectionStrings": { "DefaultConnection": "..." } }`). ## What does NOT live here anymore -- Any `FileDatabase/`, `Services/`, or `Processors/` code — all moved to `DeepDrftContent.Services`. -- Media models (`AudioBinary`, `ImageBinary`, etc.) — in `DeepDrftContent.Services`. -- `WavOffsetService` — in `DeepDrftContent.Services`. -- Don't add new domain code to this project. +- `FileDatabase/`, `Processors/`, media models (`AudioBinary`, `ImageBinary`, etc.), `WavOffsetService` — all in `DeepDrftContent.Services`. +- 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 (four endpoints) +## The endpoint surface (seven endpoints) ### GET api/track/{trackId}?offset=0 (unauthenticated) -Returns the WAV bytes from the `tracks` vault. +Returns the WAV bytes from the `tracks` vault with optional offset support. +- **Route parameter `trackId`** (string): the entry id inside the `tracks` vault (i.e. `TrackEntity.EntryKey`). - **Query parameter `offset`** (optional, default 0): byte position to start streaming from. -- If `offset == 0`: streams the entire file from the beginning. +- If `offset == 0`: streams the entire file directly from disk without buffering (so 100 MB WAVs do not force 100 MB LOH allocations per request). - If `offset > 0`: `WavOffsetService.CreateOffsetStream` block-aligns the offset and synthesises a fresh 44-byte WAV header so the response is a valid standalone WAV starting from that byte position. This is load-bearing for seek-beyond-buffer — the player asks for a new stream at the offset it wants to seek to, gets back a valid WAV that starts there, and tears down/re-initialises the decoder. - Returns 404 if track not found. Returns 500 if vault operations fail (with error swallowing — the vault returns `null`). ### PUT api/track/{trackId} ([ApiKeyAuthorize]) -**Authenticated endpoint.** Writes audio bytes to the `tracks` vault. +**Authenticated endpoint.** Writes pre-processed audio bytes to the `tracks` vault. - **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`. -- **Body**: `AudioBinaryDto` (base64 buffer + size + mime + duration + bitrate). -- `TrackService.AddTrackFromWavAsync` (via DI) is NOT called here — the endpoint just validates and writes to the vault. The caller (CLI) is responsible for then saving the returned `TrackEntity` to SQL. -- Actually: the endpoint is rarely used in production (the CLI calls `FileDatabase.RegisterResourceAsync` directly). But the endpoint exists for potential web-side uploads in future. -- Returns 200 on success, 401 if ApiKey invalid, 400 if body invalid. - -### DELETE api/track/{entryKey} ([ApiKeyAuthorize]) - -**Authenticated endpoint.** Removes an entry from the `tracks` vault (drops the index entry and deletes the backing file). - -- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`. -- **Route parameter `entryKey`**: the entry id inside the `tracks` vault (i.e. `TrackEntity.EntryKey`). -- Delegates to `FileDatabase.RemoveResourceAsync` which returns a tri-state outcome: - - `null` → vault missing or unexpected error → 500. - - `false` → entry not present → 404. - - `true` → entry removed → 200. -- Added in CMS Wave 3 (W1.5) so the CMS delete endpoint on `DeepDrftWeb` (`DELETE api/cms/track/{id}`) can clean up the vault after the SQL row is gone. Wave 3 product approval covers this — the "do not add a third endpoint without product approval" rule from prior waves is satisfied. +- **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 WAV upload + metadata as `multipart/form-data`, processes the WAV via `DeepDrftContent.Services.TrackService.AddTrackFromWavAsync`, and returns an unpersisted `TrackEntity` (no `Id` assigned). The caller (the CMS controller on `DeepDrftWeb`) is responsible for then saving that entity to SQL. +**Authenticated endpoint.** Accepts a raw WAV upload + metadata as `multipart/form-data`, processes the WAV, stores it in the vault, and persists metadata to SQL. Returns the fully persisted `TrackEntity` with `Id` populated. - **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`. - **Form fields**: @@ -69,12 +60,52 @@ Returns the WAV bytes from the `tracks` vault. - `album` (string, optional) - `genre` (string, optional) - `releaseDate` (string, optional, format `YYYY-MM-DD`) + - `createdByUserId` (long, required): audit trail — who uploaded this track. - The upload stream is copied to a `.wav`-suffixed temp file under `Path.GetTempPath()` (the audio processor requires that extension and reads from disk). 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 WAVs are accepted. The body is streamed to the temp file, not buffered in memory. -- Returns 200 with the unpersisted `TrackEntity` JSON on success. Returns 400 for missing/invalid form fields (no WAV, missing required strings, wrong extension, bad date). Returns 500 if `AddTrackFromWavAsync` returns null or throws. -- **Wave 3 product approval covers this addition** (CMS-PLAN §5, Option B): `DeepDrftWeb` proxies CMS uploads here so it never touches the vault disk path. Do not extend the endpoint surface further without a fresh approval. +- Calls `UnifiedTrackService.UploadAsync`, which orchestrates: `TrackService.AddTrackFromWavAsync` (vault write) → `TrackManager` (SQL persist with `createdByUserId`). +- Returns 200 with the **persisted** `TrackEntity` JSON (Id populated) on success. Returns 400 for missing/invalid form fields. Returns 500 if processing fails. -The endpoint surface is now intentionally **four** endpoints. Do not add a fifth without product approval. +### 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 ([ApiKeyAuthorize]) + +**Authenticated endpoint.** Paged metadata list from SQL. Used by CMS track browser. + +- **Header `ApiKey`**: required. Validated by `ApiKeyAuthenticationMiddleware`. +- **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"`. Defaults to `Id`. + - `sortDescending` (bool, optional, default false): sort direction. +- Calls `ITrackService.GetPaged` (via DI), which is actually `TrackManager` from `DeepDrftData`. +- Returns 200 with `PagedResult` 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 or null. +- Returns 200 with `TrackEntity` 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`, `Artist`, `Album?`, `Genre?`, `ReleaseDate?`. +- Looks up SQL row by ID, updates the provided fields (nulls in the request clear optional fields), and persists via `ITrackService.Update`. +- Returns 200 on success. Returns 404 if track not found. Returns 500 on update error. ## ApiKey middleware behaviour @@ -101,18 +132,26 @@ Configured in `Startup.ConfigureDomainServices()`, applied to all endpoints via `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) +## 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. Register singleton `WavOffsetService`. -6. Register named `IHttpClientFactory` client (for future use, e.g., fetching from external APIs). -7. Add MudBlazor (if shared components need it in future). -8. Add CORS policy. +5. Register singletons: `WavOffsetService`, `AudioProcessor`, `TrackService` (the `DeepDrftContent.Data` version for vault operations). -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. +**In `Program.cs`** (SQL + wiring): + +6. Load `environment/connections.json` via `CredentialTools.ResolvePathOrThrow("connections", ...)`. +7. Register `DbContext` (scoped) with connection string from config. +8. Register scoped: `TrackRepository`, `TrackManager`, `ITrackService` (factory resolves to `TrackManager`), `UnifiedTrackService`. +9. Configure forwarded headers (production-only) for reverse proxy support. +10. Load `environment/apikey.json` and register API key middleware. +11. Configure CORS policy (`ContentApiPolicy`): AllowAnyMethod, AllowAnyHeader, AllowCredentials, specific origins from config. + +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 @@ -120,7 +159,9 @@ Mapped in `Development` only. Swagger UI at `/swagger` for testing endpoints loc ## Configuration files -- `appsettings.json`: Logging and hosting config. **Does not contain secrets.** +- `appsettings.json`: Logging, hosting, and CORS 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). - `environment/filedatabase.json` (required, loaded via CredentialTools, not in repo): ```json { @@ -137,6 +178,14 @@ Mapped in `Development` only. Swagger UI at `/swagger` for testing endpoints loc } } ``` +- `environment/connections.json` (required, loaded via CredentialTools, not in repo): + ```json + { + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Database=deepdrft;Username=postgres;Password=..." + } + } + ``` ## Development commands diff --git a/DeepDrftData/CLAUDE.md b/DeepDrftData/CLAUDE.md index 2d3166e..25958a5 100644 --- a/DeepDrftData/CLAUDE.md +++ b/DeepDrftData/CLAUDE.md @@ -1,33 +1,33 @@ -# CLAUDE.md - DeepDrftWeb.Services +# CLAUDE.md - DeepDrftData -Guidance for working in the DeepDrftWeb.Services project (the SQL-side domain logic). +Guidance for working in the DeepDrftData project (the SQL-side domain logic). See the root `CLAUDE.md` for full architecture overview. This file covers what is specific to this project. ## One-line purpose -SQL-side domain logic for tracks. EF Core context, configurations, migrations, repository, service, design-time factory. Consumed by both `DeepDrftWeb` (the host) and `DeepDrftCli` (the admin CLI). +SQL-side domain logic for tracks. EF Core context, configurations, migrations, repository, manager, design-time factory. Consumed by `DeepDrftContent` (the dual-database authority), `DeepDrftPublic` (the public API), and `DeepDrftCli` (the admin CLI). ## Why this project exists -Separating domain logic from the host so the CLI can reuse `TrackService` / `TrackRepository` / `DeepDrftContext` without referencing the ASP.NET host. The CLI is a local admin tool, not a network client — it needs direct DB access. +Separating domain logic from hosts so multiple consumers (CLI, public API, content API) can reuse `TrackManager` / `TrackRepository` / `DeepDrftContext` without referencing the ASP.NET hosts directly. The CLI is a local admin tool; the APIs proxy these services over HTTP. -**New SQL-side domain code goes here, not in `DeepDrftWeb`.** +**New SQL-side domain code goes here, not in the host projects.** ## Layout ``` -DeepDrftWeb.Services/ +DeepDrftData/ ├── Data/ │ ├── DeepDrftContext.cs # EF DbContext │ ├── DeepDrftContextFactory.cs # Design-time factory (hard-codes ../Database/deepdrft.db) │ └── Configurations/ │ └── TrackConfiguration.cs # EF fluent configuration for TrackEntity -├── Migrations/ # EF-generated migrations (namespace DeepDrftWeb.Migrations) +├── Migrations/ # EF-generated migrations (namespace DeepDrftData.Migrations) ├── Repositories/ │ └── TrackRepository.cs # Data access layer -├── TrackService.cs # Service orchestrator -└── DeepDrftWeb.Services.csproj +├── TrackManager.cs # Service orchestrator (public interface: ITrackService) +└── DeepDrftData.csproj ``` ## EF DbContext and configuration @@ -43,21 +43,22 @@ DeepDrftWeb.Services/ - `ReleaseDate`: optional `DateOnly` - `ImagePath`: optional, max 500 (currently a free-form URL string; points to images vault in future) -## Controller → Service → Repository → DbContext shape +## Service → Repository → DbContext shape -- **Service** (public contract): Takes `TrackRepository`, catches exceptions at service boundary, returns `Result` / `ResultContainer`. -- **Repository** (internal): Queries the DbContext. Throws on error (service catches). -- **DbContext** (EF): Directly accessed by repository, never by service (pattern isolation). +- **Service** (`TrackManager`, implements `ITrackService`): Public contract. Takes `TrackRepository`, catches exceptions at service boundary, returns `ResultContainer`. +- **Repository** (`TrackRepository`): Internal data access. Queries the DbContext. Throws on error (service catches). +- **DbContext** (`DeepDrftContext`): EF Core. Directly accessed by repository, never by service (pattern isolation). Example: ```csharp -// TrackService.GetPaged (public) +// TrackManager.GetPaged (public via ITrackService) public async Task>> GetPaged( int pageNumber = 1, int pageSize = 20, string? sortColumn = null, - bool sortDescending = false) + bool sortDescending = false, + CancellationToken cancellationToken = default) { try { @@ -68,7 +69,7 @@ public async Task>> GetPaged( OrderBy = GetOrderExpression(sortColumn), // Maps string to LINQ expression IsDescending = sortDescending }; - var result = await _repository.GetPagedAsync(parameters); + var result = await _repository.GetPagedAsync(parameters, cancellationToken); return ResultContainer>.CreatePassResult(result); } catch (Exception e) @@ -86,7 +87,7 @@ public async Task>> GetPaged( - `OrderBy: Expression>?` (LINQ expression for sorting) - `IsDescending` -`TrackService.GetPaged` maps a string `sortColumn` (from the API query) to an expression via a switch: +`TrackManager.GetPaged` (the public service method) maps a string `sortColumn` (from the API query) to an expression via a switch: ```csharp private static Expression> GetOrderExpression(string? sortColumn) @@ -109,37 +110,41 @@ Run from the solution root: ```bash # Add a migration -dotnet ef migrations add MigrationName --project DeepDrftWeb.Services --startup-project DeepDrftWeb +dotnet ef migrations add MigrationName --project DeepDrftData --startup-project DeepDrftPublic # Apply to database -dotnet ef database update --project DeepDrftWeb.Services --startup-project DeepDrftWeb +dotnet ef database update --project DeepDrftData --startup-project DeepDrftPublic ``` -The design-time factory means you can also run `dotnet ef ... --project DeepDrftWeb.Services` standalone for local development (it doesn't need the startup project). +The design-time factory means you can also run `dotnet ef ... --project DeepDrftData` standalone for local development (it doesn't need the startup project). ## Migrations namespace -Migrations live in the `DeepDrftWeb.Migrations` namespace (a legacy name retained for history continuity). Migration files are auto-generated and rarely edited by hand. +Migrations live in the `DeepDrftData.Migrations` namespace. Migration files are auto-generated and rarely edited by hand. ## Connection string -- **Web side**: `appsettings.json` → `ConnectionStrings:DefaultConnection` -- **CLI side**: `environment/connections.json` → `CliSettings:ConnectionString` -- Both point at `../Database/deepdrft.db` +- **DeepDrftPublic**: `appsettings.json` → `ConnectionStrings:DefaultConnection` +- **DeepDrftContent**: `environment/connections.json` → `ConnectionStrings:DefaultConnection` +- **DeepDrftCli**: `environment/connections.json` → `CliSettings:ConnectionString` +- All point at the same database (PostgreSQL in production, SQLite for local development). -The design-time factory hard-codes the path for `dotnet ef` commands. +The design-time factory hard-codes the local path for `dotnet ef` commands. ## Service registration -In `DeepDrftWeb/Program.cs` and `DeepDrftCli/Program.cs`: +In `DeepDrftContent/Program.cs`, `DeepDrftPublic/Program.cs`, and `DeepDrftCli/Program.cs`: ```csharp services.AddDbContext(options => - options.UseSqlite(configuration.GetConnectionString("DefaultConnection"))); + options.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))); // or UseSqlite for dev services.AddScoped(); -services.AddScoped(); +services.AddScoped(); +services.AddScoped(sp => sp.GetRequiredService()); ``` +This pattern allows callers to depend on `ITrackService` (the public interface) without knowing about `TrackManager` (the implementation). `DeepDrftContent` also registers `UnifiedTrackService` for dual-database orchestration. + ## Important patterns - **Required properties**: `EntryKey`, `TrackName`, `Artist` are `required` strings. This compile-time guarantee prevents half-built entities from reaching the database. @@ -151,23 +156,27 @@ services.AddScoped(); ```bash # Build -dotnet build DeepDrftWeb.Services +dotnet build DeepDrftData # Add migration (from solution root) -dotnet ef migrations add MigrationName --project DeepDrftWeb.Services --startup-project DeepDrftWeb +dotnet ef migrations add MigrationName --project DeepDrftData --startup-project DeepDrftPublic # Apply migration -dotnet ef database update --project DeepDrftWeb.Services --startup-project DeepDrftWeb +dotnet ef database update --project DeepDrftData --startup-project DeepDrftPublic # Run from CLI (which consumes this service) dotnet run --project DeepDrftCli -- list + +# Run from Content API (dual-database host) +dotnet run --project DeepDrftContent ``` ## What does NOT live here -- HTTP controllers or middleware -- Blazor components or rendering logic -- FileDatabase or binary content code (that's in `DeepDrftContent.Services`) -- Configuration for the web host (`appsettings.json` stays in `DeepDrftWeb`) +- HTTP controllers or middleware (in host projects) +- Blazor components or rendering logic (in host projects) +- FileDatabase or binary content code (in `DeepDrftContent.Services`) +- Host-specific wiring or configuration (in host `Program.cs` / `appsettings.json`) +- Host-internal services like `UnifiedTrackService` (in host `Services/` folders) -When working with this project, focus on the data layer (repository, service, EF configuration) and ensure all new SQL logic is testable and reusable by both the web host and the CLI. +When working with this project, focus on the data layer (repository, manager, EF configuration) and ensure all new SQL logic is testable and reusable by multiple consumers (Content API, Public API, CLI) without host-specific dependencies.