- Extract PathFromGuid to shared PathUtils.cs (PreviewJob + ThumbnailJob) - Consolidate RegexHighlighter duplicate methods via delegation - Fix default port 3306 -> 5432 in appsettings.json + docker-compose.yml - Remove port gotcha section from AGENTS.md - Fix ITagRepository XML docs after parameter rename (id -> tag)
188 lines
10 KiB
Markdown
188 lines
10 KiB
Markdown
# 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.SassCompiler` MSBuild task (no Node.js needed; compiles during `dotnet build`)
|
||
- Run `dotnet tool restore` before first migration — installs `dotnet-ef` (tool manifest at `dotnet-tools.json` in repo root)
|
||
|
||
## SCSS
|
||
|
||
SCSS source is in `MilkStream.Client/Styles/`. Compiled via `AspNetCore.SassCompiler` MSBuild target:
|
||
|
||
```bash
|
||
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` (`$accent` for 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
|
||
|
||
```bash
|
||
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.
|
||
|
||
## Infrastructure gotchas
|
||
|
||
- **MilkStream `/appsettings.json`:** Dynamically generated from server config — maps **before** `UseStaticFiles`, so it shadows the static file in `MilkStream.Client/wwwroot/`. This endpoint provides `LactoseBaseUrl` to the WASM client.
|
||
- **Auth requires claims transformation:** `RefreshTokenTransformation` (registered as `IClaimsTransformation`) must succeed on every authenticated request, or all endpoints return 403.
|
||
- **`dotnet-ef` CLI vs Microsoft.EntityFrameworkCore.Tools:** The global `dotnet-ef` (from `dotnet-tools.json`, v10.0.9) is the correct CLI to use. The Tools package reference in `Lactose.csproj` (v10.0.9) provides the `dotnet ef` target for `dotnet build`-time validation.
|
||
- **WASM client DI:** `LoginService` is **Singleton**; all other services are `Scoped`. Two `HttpClient` registrations: one unauthenticated (default) for login, and one named `"MilkstreamClient"` which has `JwtTokenRefresher` as a `DelegatingHandler`. The handler transparently refreshes expired tokens pre-flight and retries once on 401 — authenticated services must **never** handle token refreshes manually. Do not add `SendWithRefreshAsync` or similar wrappers; rely solely on the named client.
|
||
- **HTTPS redirection is commented out** in Lactose — API does not enforce HTTPS.
|
||
- **DateTime analyzer:** `Lactose.Analyzers` treats `DateTime.Now` as error MS001, `DateTime.Today` as error MS002, `DateTime.Date` as warning MS003, and non-UTC `SpecifyKind` as error MS004. Always use `DateTime.UtcNow`. All timestamp columns use `timestamp 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 = 0` is the first page. All repositories use `Skip(page * pageSize)`. All controllers validate `Page < 0` (reject negative) and `PageSize < 1 || PageSize > 250` (clamp 1–250).
|
||
- **Conventional Commits** enforced via `cliff.toml`; changelog generated with `git-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.
|
||
|
||
## Visibility Rules (R1–R7)
|
||
|
||
Defined in issue [#129](https://git.r3d.codes/MilkyShots/MilkyShots/issues/129). Each entity (Asset, Album, Person) carries **independent** `EVisibility` — no inheritance from container to child.
|
||
|
||
### R1 — Independent Visibility
|
||
No entity's visibility is overridden by its container. An asset in a Public album does NOT become Public; the asset is only visible if the viewer has permission for the asset's own visibility.
|
||
|
||
### R2 — Asset Visibility (Per-Asset Gate)
|
||
|
||
| Actor | Public | Protected | Private |
|
||
|---|---|---|---|
|
||
| Anonymous | ✅ | ❌ | ❌ |
|
||
| User | ✅ | ✅ | ❌ |
|
||
| Maintainer (not owning / not linked) | ✅ | ✅ | ❌ |
|
||
| Maintainer (uploaded by self) | ✅ | ✅ | ✅ |
|
||
| Maintainer (album → person they maintain) | ✅ | ✅ | ✅ |
|
||
| Curator (non-deleted) | ✅ | ✅ | ✅ |
|
||
| Curator (deleted, own upload) | ✅ | ✅ | ✅ |
|
||
| Admin (all, including deleted) | ✅ | ✅ | ✅ |
|
||
|
||
### R3 — Album Access Gating
|
||
Album's own visibility determines who can access it. Not visible → 404.
|
||
|
||
| Actor | Public | Protected | Private |
|
||
|---|---|---|---|
|
||
| Anonymous / User | ✅ | ✅ | ❌ |
|
||
| Maintainer (unassigned) | ✅ | ✅ | ❌ |
|
||
| Maintainer (maintains PersonOwner) | ✅ | ✅ | ✅ |
|
||
| Curator / Admin | ✅ | ✅ | ✅ |
|
||
|
||
### R4 — Person Access Gating
|
||
Same as R3. Not visible → 404.
|
||
|
||
### R5 — Media Serving
|
||
Uses per-asset rules (R2). Album provenance not checked — too complex, marginal benefit.
|
||
|
||
### R6 — Edit Mode (Frontend)
|
||
API returns Private assets to privileged users (Admin/Curator/Maintainer-on-owned-content). Frontend hides them in browse mode, shows them in edit/curator mode. No backend flag.
|
||
|
||
### R7 — Deleted Assets
|
||
- **Admin**: all deleted assets visible
|
||
- **Curator**: own deleted uploads visible
|
||
- **Everyone else**: deleted assets hidden (`DeletedAt IS NULL`)
|
||
|
||
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
|
||
|
||
- `CorsAllowedOrigins` env var **replaces** the JSON array entirely (semicolon-separated)
|
||
- MilkStream browser origin must be listed in Lactose's CORS
|
||
- `LactoseBaseUrl` on MilkStream = URL the browser uses to reach Lactose
|
||
|
||
## EF Core / Migrations
|
||
|
||
Migrations in `Lactose/Migrations/` — generated, do not hand-edit.
|
||
|
||
```bash
|
||
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 v10.0.9 vs Design v10.0.9 mismatch). Example:
|
||
|
||
```csharp
|
||
modelBuilder.Entity<Asset>().HasIndex(e => new { e.Visibility, e.UploadedBy })
|
||
.HasDatabaseName("IX_Assets_VisibleForSearch")
|
||
.HasFilter("\"DeletedAt\" IS NULL");
|
||
```
|
||
|
||
- **Trigram GIN indexes** on `People.Name` and `Albums.Title` use `HasMethod("gin")` with `HasOperators("gin_trgm_ops")` for case-insensitive `ILike` search.
|
||
- **pgvector and pg_trgm** extensions are enabled in `OnModelCreating` via `HasPostgresExtension`.
|
||
|
||
## Tests
|
||
|
||
No unit/integration test project exists. API endpoint testing is done via **REST Client `.http` files**:
|
||
|
||
- **`Lactose/WepApiTest.http`** — 100+ numbered tests covering all API endpoints across 12 sections (Auth → User CRUD → Person → Album → Tag → Asset → Stats → Settings → Data Visibility → Visibility Gates → Cleanup). Tests verify authorization at every level (anonymous, user, curator, admin) and include comprehensive visibility gate tests (R2–R4). 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** (`MilkyShots` org): 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_read` tool (`list_org_labels` / `list_repo_labels`).
|
||
|
||
|