9.0 KiB
MilkyShots – Agent Guide
Gitea repo owner: MilkyShots
Prerequisites
- .NET 10 SDK (pinned in
global.json;allowPrerelease: true) - PostgreSQL with pgvector extension
- SCSS compiled by
AspNetCore.SassCompilerMSBuild task (no Node.js needed; compiles duringdotnet build) - Run
dotnet tool restorebefore first migration — installsdotnet-ef
SCSS
SCSS source is in MilkStream.Client/Styles/. Compiled via AspNetCore.SassCompiler MSBuild target:
dotnet build # compiles SCSS automatically
- Config:
MilkStream.Client/Styles/sasscompiler.json(expanded in Debug, compressed in Release) - Source maps disabled
- Bootstrap 5.3.7 SCSS vendored in
Styles/bootstrap/ - Theme variables adjustable in
Styles/_variables.scss($accentfor primary color) - Aggressive gradient mixins in
Styles/_gradients.scss - Works in Docker — MSBuild task runs during
dotnet build/dotnet publish, no hosted service
Projects (5 in solution)
| Project | Role | Entrypoint |
|---|---|---|
| Butter | Shared class library (DTOs, enums, MIME types) | — |
| Lactose.Analyzers | Roslyn analyzer — enforces DateTime.UtcNow (not DateTime.Now) as error MS001 |
— |
| Lactose | ASP.NET Core Web API — controllers, EF Core, repos, background jobs | Lactose/Program.cs |
| MilkStream | Blazor WASM host — serves WASM files + dynamic /appsettings.json |
MilkStream/Program.cs |
| MilkStream.Client | Blazor WASM client — Razor components, SCSS, frontend services, shared UI components (ModalFrame, EmptyState) | MilkStream.Client/Program.cs |
Build & run
dotnet build MilkyShots.sln # .NET 10, C# 12
dotnet run --project Lactose # API on :5162 (host) / :8080 (container)
dotnet run --project MilkStream # WASM host on :5269 (host) / :8080 (container)
Database: Default credentials in appsettings.json. Docker compose spins up all services.
Port gotcha: appsettings.json defaults DatabaseAddress:Port to 3306 (legacy MySQL port). PostgreSQL runs on 5432. The raw connection string must be overridden, or use docker-compose.yml which maps host 3306 → container 5432 automatically.
Infrastructure gotchas
- MilkStream
/appsettings.json: Dynamically generated from server config — maps beforeUseStaticFiles, so it shadows the static file inMilkStream.Client/wwwroot/. This endpoint providesLactoseBaseUrlto the WASM client. - Auth requires claims transformation:
RefreshTokenTransformation(registered asIClaimsTransformation) must succeed on every authenticated request, or all endpoints return 403. - Microsoft.EntityFrameworkCore.Tools v8.0.15 vs EF Core Design v10.0.9: The Tools package is outdated but the
dotnet-efCLI (fromdotnet-tools.json) and Design package are both v10.0.9 — CLI should work fine. - WASM client DI:
LoginServiceis Singleton; all other services areScoped. TwoHttpClientregistrations: one unauthenticated (default) for login, and one named"MilkstreamClient"which hasJwtTokenRefresheras aDelegatingHandler. The handler transparently refreshes expired tokens pre-flight and retries once on 401 — authenticated services must never handle token refreshes manually. Do not addSendWithRefreshAsyncor similar wrappers; rely solely on the named client. - HTTPS redirection is commented out in Lactose — API does not enforce HTTPS.
SharedWith.Any()EF Core translation: Cannot be translated to SQL through many-to-many join tables (AlbumAsset,AssetUser). When visibility filtering includesSharedWith.Any(u => u.Id == userId), you must materialize records first with.Include().ThenInclude()chains, then filter client-side in C#. The two-pass pagination approach preserves page-limited loading — seePersonRepository.SearchQueryandAlbumRepository.SearchQueryfor the pattern.- DateTime analyzer:
Lactose.AnalyzerstreatsDateTime.Nowas error MS001,DateTime.Todayas error MS002,DateTime.Dateas warning MS003, and non-UTCSpecifyKindas error MS004. Always useDateTime.UtcNow. All timestamp columns usetimestamp with time zone.
XML docs enforced
Directory.Build.props at solution root enables GenerateDocumentationFile and treats CS1591 (missing XML comment) as error. All public APIs must have XML docs. Repo implementations use <inheritdoc /> — keep interface docs in sync.
Conventions
HttpPut= create,HttpPost("{id}")= update — not REST-idiomatic; do not "fix" without team buy-in- Soft delete only — set
DeletedAt, never hard-delete (models: Asset, User) - Repository
Save()must be called explicitly after insert/update/delete - Enum prefix
E(e.g.EAccessLevel,EAssetType,EJobStatus) - JWT access token: 10 min, refresh token: 60 min (constants in
LactoseAuthService.cs) - Pagination is zero-based everywhere —
Page = 0is the first page. All repositories useSkip(page * pageSize). All controllers validatePage < 0(reject negative) andPageSize < 1 || PageSize > 250(clamp 1–250). - Conventional Commits enforced via
cliff.toml; changelog generated withgit-cliff - Granular commits — commit each logical change separately (e.g., DTO change, controller logic, UI component) with a descriptive commit message
- Reuse API endpoints — prefer adding optional query parameters to existing endpoints over creating new routes. New endpoints are a last resort when the existing ones fundamentally cannot serve the need.
Modularity
Reusable UI components live in MilkStream.Client/Components/Shared/.
When a markup pattern appears in 2+ places, extract it into a shared component.
See issue #52 for the tracking list of candidate components.
CORS gotchas
CorsAllowedOriginsenv var replaces the JSON array entirely (semicolon-separated)- MilkStream browser origin must be listed in Lactose's CORS
LactoseBaseUrlon MilkStream = URL the browser uses to reach Lactose
EF Core / Migrations
Migrations in Lactose/Migrations/ — generated, do not hand-edit.
dotnet ef migrations add <Name> --project Lactose
dotnet ef database update --project Lactose
Database indexes for performance should be defined using the fluent API in LactoseDbContext.OnModelCreating via HasIndex().HasFilter(). Do not use the [Index] data annotation — the Filter parameter is not available in the current package configuration (EF Core Tools v8.0.15 vs Design v10.0.9 mismatch). Example:
modelBuilder.Entity<Asset>().HasIndex(e => new { e.IsPubliclyShared, e.OwnerId })
.HasDatabaseName("IX_Assets_VisibleForSearch")
.HasFilter("\"DeletedAt\" IS NULL");
- Trigram GIN indexes on
People.NameandAlbums.TitleuseHasMethod("gin")withHasOperators("gin_trgm_ops")for case-insensitiveILikesearch. - pgvector and pg_trgm extensions are enabled in
OnModelCreatingviaHasPostgresExtension.
Tests
No unit/integration test project exists. API endpoint testing is done via REST Client .http files:
Lactose/WepApiTest.http— 67 numbered tests covering all API endpoints across 10 sections (Auth → User CRUD → Person → Album → Tag → Asset → Stats → Settings → Cleanup). Tests verify authorization at every level (anonymous, user, curator, admin). Self-contained — all test data is created at runtime and cleaned up.Lactose/http-client.env.json— provides pre-filled variable values (userName,curatorName, etc.) for the REST Client extension.- Open in VS Code/Rider with the REST Client extension and run individual tests or the full suite.
- Server must be running locally (default
http://localhost:5162). - Paginated list tests (people, albums, users) use
?page=0&pageSize=5— change these carefully.
PR workflow
- Before starting work, ask the user whether they want the fix in a PR or directly on
develop(for quick/trivial fixes). - Larger work always goes in a PR.
- Always mark the PR as draft until the user confirms they are satisfied and have tested it works.
- When making the PR ready (removing draft status), mark it ready for review and request:
- REDCODE when the PR contains frontend changes
- Fastwind when the PR contains backend changes
- Both when both areas are touched
Data sizes
Approximate record counts in the database:
| Entity | Count |
|---|---|
| Assets | 1,000,000+ |
| Albums | 10,000+ |
| People | 1,000+ |
| Users | 100+ |
| Maintainers | 10+ |
| Curators | single digit |
| Admins | single digit |
Gitea labels
Labels live at two levels:
- Org level (
MilkyShotsorg): generic issue types (bug,enhancement,help wanted,question,duplicate,invalid,wontfix) - Repo level (
MilkyShots/MilkyShots): project-specific labels (area:frontend,area:backend,page:*,type:refactor,priority:*, etc.) - When labelling issues, check both levels with the
label_readtool (list_org_labels/list_repo_labels).