# CLAUDE.md - DeepDrftModels Guidance for working in the DeepDrftModels project (shared contracts across all projects). See the root `CLAUDE.md` for full architecture overview. This file covers what is specific to this project. ## One-line purpose Shared contracts. Entities, DTOs, pagination types. Every project references this; nothing else references the projects that reference this. ## Layout ``` DeepDrftModels/ ├── Entities/ │ └── TrackEntity.cs # Database entity for tracks ├── DTOs/ │ ├── TrackDto.cs # DTO mirror of TrackEntity │ └── HomeStatsDto.cs # Aggregate figures for the public home hero stat row ├── Models/ │ ├── PagingParameters.cs # Pagination configuration (base + generic) │ └── PagedResult.cs # Paginated result wrapper └── DeepDrftModels.csproj ``` ## TrackEntity (the core entity) The single source of truth for track metadata. Fields: ```csharp public long Id { get; set; } // Primary key (auto-assigned by SQLite) public required string EntryKey { get; set; } // FileDatabase entry id (max 100) public required string TrackName { get; set; } // Track title (max 200) public required string Artist { get; set; } // Artist name (max 200) public string? Album { get; set; } // Optional album (max 200) public string? Genre { get; set; } // Optional genre (max 100) public DateOnly? ReleaseDate { get; set; } // Optional release date public string? ImagePath { get; set; } // Optional image URL (max 500) public double? DurationSeconds { get; set; } // Audio runtime in seconds (nullable; populated at upload, backfilled for older rows) ``` **No `MediaPath` field exists.** That was a legacy name. The field is `EntryKey`. The `EntryKey` is the link to binary content — it's the entry id in the `tracks` vault inside FileDatabase. The database column is `entry_key` (snake_case). Convention: required reference fields use `required` modifier; optional reference fields are `?`. Don't relax `required` — it's the compile-time guarantee that prevents half-built entities from reaching the database. ## TrackDto Mirrors `TrackEntity` structure (identical fields, same nullability). Used where DTO/entity separation is needed for serialisation. In practice, both flow over the wire today, but the separation is available if APIs need to diverge (e.g., hide `Id` in responses). ## HomeStatsDto Aggregate figures behind the public home hero stat row (`NowPlayingStats`). A single round-trip returns everything the three cards need. Fields: - `CutTrackCount` (int): total non-deleted tracks on Cut-medium releases. - `CutReleaseTypeCounts` (`List`): per-`ReleaseType` Cut release counts; zero-count types are absent (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). Rendered as `hh:mm` by `RuntimeFormat` on the client. `CutReleaseTypeCount` is a nested type (`ReleaseType`, `Count` int) defined in the same file. ## Pagination system ### PagingParameters (base) ```csharp public class PagingParameters { public int Page { get; set; } = 1; // Current page (1-based) public int PageSize { get; set; } = 20; // Items per page (default 20, max 100) } ``` `PageSize` setter enforces a hard ceiling at 100 — prevents clients from requesting huge pages that would tank performance. ### PagingParameters (generic) ```csharp public class PagingParameters : PagingParameters { public Expression>? OrderBy { get; set; } // Type-safe sort expression public bool IsDescending { get; set; } // Sort direction public int Skip => (Page - 1) * PageSize; // Calculated (0-based) } ``` Used by services to build type-safe LINQ queries. The service takes a string sort column from the API, maps it to an `Expression>`, and passes it in `OrderBy`. The repository applies the expression to the DbSet. ### PagedResult ```csharp public class PagedResult { public IEnumerable Items { get; set; } // Current page items public int TotalCount { get; set; } // Total items available public int Page { get; set; } // Current page public int PageSize { get; set; } // Items per page // Calculated properties public int TotalPages => (TotalCount + PageSize - 1) / PageSize; public bool HasNextPage => Page < TotalPages; public bool HasPreviousPage => Page > 1; } ``` Returned by service `GetPaged` methods. Includes a static factory `From` for cross-type mapping (e.g., `PagedResult` → `PagedResult`). ## Key patterns ### Required fields Essential fields use the `required` modifier. This is a compile-time guarantee that prevents half-built entities from being instantiated. ```csharp public required string EntryKey { get; set; } public required string Artist { get; set; } ``` ### Nullable reference types Explicit nullability throughout: ```csharp public string? Album { get; set; } // Optional, can be null public required string Artist { get; set; } // Required, must be assigned ``` ### Expression-based sorting Type-safe LINQ expressions for dynamic sorting. No string-based `OrderBy` at runtime: ```csharp parameters.OrderBy = entity => entity.TrackName; // Compile-time checked ``` Services map string column names to expressions in a switch, so only valid sorts are possible. ### Pagination defaults - `Page`: 1 (1-based) - `PageSize`: 20 (per-item default) - `PageSize` max: 100 (hard cap, enforced by setter) Client requests outside these bounds are clamped by the API controller before being passed to the service. ## Integration points - **EF Core**: `TrackEntity` is configured in `DeepDrftWeb.Services.Data.Configurations.TrackConfiguration`. - **API serialisation**: All models JSON-serializable. Controllers return `ActionResult>>`. - **Cross-project usage**: - `DeepDrftWeb`: Server render preload, controller responses. - `DeepDrftWeb.Client`: HTTP client deserialization, UI display. - `DeepDrftContent.Services`: `TrackService.AddTrackFromWavAsync` returns populated `TrackEntity`. - `DeepDrftCli`: Entity display, table formatting. - `DeepDrftTests`: Test assertions. ## NetBlocks transitivity This project does not directly reference NetBlocks. The result types (`Result`, `ResultContainer`, `ApiResult`, `ApiResultDto`) come into services and clients directly via their own NetBlocks references. Models stay independent of result wrappers — they are serializable POCOs. ## Important conventions - **Column naming**: Entity property `EntryKey` maps to table column `entry_key` (snake_case, configured in EF). - **Table name**: `track` (singular, configured in EF). - **Required fields**: Properties with `required` modifier cannot be left unassigned. - **Page numbers**: 1-based (page 1 is the first page). The `Skip` calculation converts to 0-based for LINQ (`(Page - 1) * PageSize`). ## Development commands ```bash # Build dotnet build DeepDrftModels # No tests live here (models are simple POCOs) # Tests that use these models live in DeepDrftTests/ ``` When working with this project, maintain the required/optional distinction rigorously, keep the pagination contract tight, and remember that this project is the interface between all other projects — changes here ripple everywhere.