156 lines
11 KiB
Markdown
156 lines
11 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code when working with code in this repository.
|
||
|
||
## Architecture Overview
|
||
|
||
DeepDrftHome is a **net10.0** solution consisting of ten projects implementing a dual-database media management system for the DeepDrft electronic music collective, split into two independent Blazor applications: the public site (`DeepDrftPublic`) and the CMS (`DeepDrftManager`).
|
||
|
||
### Core Projects
|
||
|
||
- **DeepDrftPublic**: ASP.NET Core host. Blazor Web App with Server + WASM render modes. Owns browser-facing proxy controller for `api/track/*` (metadata listing and audio streaming), 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. 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`, `TrackManager`. Consumed by `DeepDrftAPI` and tests.
|
||
- **DeepDrftAPI**: ASP.NET Core host. Dual-database authority (SQL metadata + FileDatabase binary). AuthBlocks API host (owns registration, migration/seed, JWT endpoints). Seven track 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 (unauthenticated); `GET api/track/meta/{id:long}` single metadata (ApiKey); `PUT api/track/meta/{id:long}` metadata update (ApiKey).
|
||
- **DeepDrftContent**: 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<T>`, `PagedResult<T>`. Every project references this.
|
||
- **DeepDrftTests**: NUnit test suite. Comprehensive FileDatabase tests (vault creation, media storage, indexing, factory patterns, utilities). Integration-focused with temp-directory test isolation.
|
||
|
||
External: **NetBlocks** (absolute path `C:\lib\NetBlocks\`). Provides `Result`, `ResultContainer<T>`, `ApiResult<T>`, `ApiResultDto<T>`.
|
||
|
||
### Database Architecture
|
||
|
||
**Dual-database approach** — browser never reaches storage directly:
|
||
|
||
```
|
||
DeepDrftPublic.Client (WASM)
|
||
├── HttpClient "DeepDrft.API" ──► DeepDrftPublic proxy ──► DeepDrftAPI ──► EF Core / SQLite (metadata)
|
||
└── HttpClient "DeepDrft.Content" ──► DeepDrftPublic proxy ──► DeepDrftAPI ──► FileDatabase / disk (binary)
|
||
|
||
Server-side (SSR): Both clients point directly at DeepDrftAPI (server-to-server, no proxy hop).
|
||
```
|
||
|
||
1. **SQL Database (SQLite)**: Metadata and track info via Entity Framework
|
||
- Location: `../Database/deepdrft.db`
|
||
- Entity: `TrackEntity` with `Id`, `EntryKey`, `TrackName`, `Artist`, `Album?`, `Genre?`, `ReleaseDate?`, `ImagePath?`
|
||
- Context: `DeepDrftContext` in `DeepDrftData`
|
||
|
||
2. **FileDatabase**: Custom file-based binary storage system
|
||
- Location: `../Database/Vaults` (configurable via `filedatabase.json`)
|
||
- Root contains typed **MediaVaults** (Media, Image, Audio)
|
||
- Each vault has a JSON `index` file listing entries + per-entry metadata
|
||
- Entries are user-supplied strings sanitized to `[a-zA-Z0-9-]` + file extension
|
||
- Binary hierarchy: `FileBinary` → `MediaBinary` (+ Extension/MIME) → `AudioBinary` (+ Duration/Bitrate) | `ImageBinary` (+ AspectRatio)
|
||
- **Error-handling philosophy**: public operations swallow exceptions and return `null`/`false` — callers must check return values, not catch.
|
||
|
||
## Key Architectural Decisions
|
||
|
||
### Service projects vs. host projects
|
||
|
||
The split between host projects (`DeepDrftPublic`, `DeepDrftManager`, `DeepDrftContent`) and `*.Services` class libraries (e.g., `DeepDrftData`, `DeepDrftContent.Services`) is deliberate: hosts own HTTP surface (endpoints/controllers exposed to network), config, DI wiring, and UI components; `*.Services` are plain class libraries holding domain logic. This separation allows multiple hosts to consume the same service implementations. Within a host, domain logic like CMS mutations lives in host-internal service classes (e.g., `CmsTrackService` in `DeepDrftManager/Services/`), injected directly into Blazor components with no in-process HTTP roundtrip.
|
||
|
||
**New domain logic goes in `*.Services` (shared class libraries) for logic consumed by multiple hosts, or in host-internal service classes (e.g., `Services/`) for host-specific logic** — not in controllers, which should be thin HTTP boundaries.
|
||
|
||
### TrackEntity is a join, not a content blob
|
||
|
||
`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.TrackService.AddTrackFromWavAsync` processes WAV, generates entry GUID, stores audio in vault, returns unpersisted `TrackEntity`.
|
||
2. `DeepDrftAPI.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).
|
||
|
||
### Streaming-first audio playback
|
||
|
||
The player is not fetch-then-play:
|
||
|
||
1. Client calls `GET api/track/{id}` on DeepDrftContent and receives WAV bytes as a stream (`HttpCompletionOption.ResponseHeadersRead`).
|
||
2. `StreamingAudioPlayerService` reads in adaptive 16–64 KB chunks, pushes each via `AudioInteropService.processStreamingChunk`.
|
||
3. TypeScript `StreamDecoder` parses WAV header, decodes chunks to `AudioBuffer`s. `PlaybackScheduler` schedules them on a Web Audio graph.
|
||
4. Playback starts as soon as a min buffer is queued; UI duration from parsed header (not waiting for full file).
|
||
5. **Seek beyond buffer**: if seek target is past what's decoded, client issues `GET api/track/{id}?offset={byteOffset}`. Server's `WavOffsetService` block-aligns offset, synthesises a fresh 44-byte WAV header, streams `[new header][data from offset]`. Player tears down and re-initialises decoder for the new stream.
|
||
|
||
Keep this seam clean — it is the most architecturally load-bearing part of the playback path.
|
||
|
||
### Theming and dark mode
|
||
|
||
- MudBlazor is the UI framework. Light and dark palettes (bespoke "Charleston in the Day" / "Lowcountry Summer Nights") defined inline in `MainLayout.razor`.
|
||
- Dark mode toggles via cookie (`darkMode`, 365 days). Client-side via JS interop.
|
||
- During server prerender, `DarkModeService` (in `DeepDrftPublic`) reads the cookie and seeds `DarkModeSettings.IsDarkMode`, which carries into WASM render via `PersistentComponentState`. Avoids "wrong theme flash" on initial paint.
|
||
- `DarkModeSettings` lives in `DeepDrftPublic.Client.Common` (consumed by both server prerender and client components).
|
||
- Typography: Google Fonts (Bodoni Moda, Cormorant, DM Sans). Hand-rolled gas-lamp icon (lit/unlit) lives in `DDIcons.cs`.
|
||
|
||
### TypeScript interop, not raw JS
|
||
|
||
Audio interop authored in TypeScript under `DeepDrftPublic/Interop/audio/`, compiled to `wwwroot/js/audio/` via `Microsoft.TypeScript.MSBuild`. One module per responsibility (AudioContextManager, StreamDecoder, PlaybackScheduler, SpectrumAnalyzer, AudioPlayer), plus `index.ts` exposing `window.DeepDrftAudio`. `tsconfig.json` is **not** copied to output. In dev, raw `.ts` served from `/Interop/` for source-map debugging.
|
||
|
||
## Development Commands
|
||
|
||
### Build and Test
|
||
```bash
|
||
# Build entire solution
|
||
dotnet build DeepDrftHome.sln
|
||
|
||
# Run all tests
|
||
dotnet test DeepDrftTests/
|
||
|
||
# Run specific test class
|
||
dotnet test DeepDrftTests/ --filter "ClassName=FileDatabaseTests"
|
||
```
|
||
|
||
### Running Applications
|
||
```bash
|
||
# Run main web application
|
||
dotnet run --project DeepDrftPublic
|
||
|
||
# Run API (dual-database authority and AuthBlocks host)
|
||
dotnet run --project DeepDrftAPI
|
||
```
|
||
|
||
### Entity Framework (SQL Database)
|
||
```bash
|
||
# Add migration (from solution root)
|
||
dotnet ef migrations add MigrationName --project DeepDrftData --startup-project DeepDrftPublic
|
||
|
||
# Update database
|
||
dotnet ef database update --project DeepDrftData --startup-project DeepDrftPublic
|
||
```
|
||
|
||
## Key Configuration Files
|
||
|
||
All projects load secrets via `CredentialTools.ResolvePathOrThrow()` from gitignored `environment/` files:
|
||
|
||
- `DeepDrftPublic/appsettings.json`: Logging and URL config. Secrets loaded from `environment/api.json` (DeepDrftAPI base URLs for content and SQL metadata).
|
||
- `DeepDrftManager/appsettings.json`: Logging and URL config. Secrets loaded from `environment/api.json` (DeepDrftAPI base URL and API key).
|
||
- `DeepDrftAPI/appsettings.json`: Logging and hosting config. Secrets loaded from `environment/filedatabase.json` (FileDatabase vault path), `environment/apikey.json` (API key), `environment/connections.json` (SQL and Auth connection strings), `environment/authblocks.json` (AuthBlocks JWT/email/admin creds).
|
||
|
||
## Folder-Level Guidance
|
||
|
||
Folder-level `CLAUDE.md` files provide specifics on structure, patterns, and commands for each project. Start with the project's folder `CLAUDE.md` when entering that directory. The root CLAUDE.md here sets the architectural context; folder files answer "what's in this folder and how do I work here?"
|
||
|
||
## Important Patterns
|
||
|
||
### Result Pattern (NetBlocks)
|
||
|
||
Services return `Result`, `ResultContainer<T>`, or `ApiResult<T>` rather than throwing for expected failure modes. Catch at service boundary, surface via result.
|
||
|
||
### Error Swallowing in FileDatabase
|
||
|
||
Public `Load*` / `Register*` operations in FileDatabase swallow exceptions and return `null` / `false`. Callers must check return values. This matches the TypeScript original and is load-bearing — do not change without a design pass.
|
||
|
||
### Pagination
|
||
|
||
Services build `PagingParameters<T>` with an `OrderBy` expression. Switch in the service maps a string sort column to the expression. New sort columns extend this switch. Nulls sort to end (padded sentinel strings / `DateOnly.MaxValue`).
|
||
|
||
## External Dependencies
|
||
|
||
- Entity Framework Core 10.0.1 (SQLite)
|
||
- MudBlazor 8.15.0
|
||
- NUnit 4.4.0
|
||
- NetBlocks (Result patterns)
|
||
- Microsoft.TypeScript.MSBuild (TS compilation)
|
||
|
||
See individual project files for detailed dependency lists and versions.
|