236 lines
14 KiB
Markdown
236 lines
14 KiB
Markdown
# Handoff: backend refactor, parts P12 to P15
|
||
|
||
> For the next AI. Read this file first, then `backend/docs/backend-refactor-plan.md`
|
||
> (section A, B, and the parts P12–P15, plus the Log in section D). Both files are
|
||
> **untracked on purpose**: never commit them.
|
||
|
||
## 1. Where things stand
|
||
|
||
- Worktree: `C:\Users\XShin\OneDrive\Documentos\Musify\MusifyM\.claude\worktrees\backend-refactor-plan-9b4465`
|
||
- Branch: `claude/backend-refactor-plan-9b4465` (base `master` at `87c3f97`). Nothing is pushed.
|
||
- Done and committed: **P0 to P11** (one commit per part, see `git log --oneline`).
|
||
- Left: **P12** (API layer), **P13** (Worker and Gateway hosts), **P14** (tests), **P15** (final sweep and docs).
|
||
- State at the last commit (`405df08`): build with 0 warnings, 365 tests green
|
||
(Infrastructure tests need Docker), `npm run check` in `web-player/` clean.
|
||
- The Log (section D of the plan) already has notes for P0–P11 and is the source of truth
|
||
for decisions taken. Update it (checkbox, date, notes) for every part you finish.
|
||
|
||
### Deviations from the plan already taken (do not undo)
|
||
|
||
- `IOwnedEntity` lives in `Domain/Abstractions` and has no `Id`; `IHasLifeCycle` provides it.
|
||
`FindOwnedAsync` requires both and treats non-active entities as NotFound.
|
||
- List query records keep `int PageNumber, int PageSize`; the handler builds a `PageRequest`.
|
||
The API binds them through `Api/Models/PageQuery` (camelCase names) + `PageQueryValidator`.
|
||
- Events and `Publish*Arguments` carry `ImageSizes Sizes`.
|
||
- Cover endpoint helper `CoverEndpoint.Stream` already exists (this was P12.2).
|
||
- Mix titles were replaced by `MixKind`; the web-player renders the text (`src/lib/data/mixes.ts`).
|
||
- The two migrations planned in P4 were merged into `DropUploadTableAndTrackAudioRetryColumns`.
|
||
- Adding a duplicate track to a playlist or album is now a silent success (bulk endpoints).
|
||
- Hangfire cron has 1-minute resolution, so job intervals in seconds are rounded up to minutes.
|
||
- Pending inside P11: log-and-rethrow still present in `MassTransit/Activities/Files/*`
|
||
(Copy, Download, Remove, Transfer, Upload). Clean it in P15 or when touching them.
|
||
|
||
## 2. Rules the user gave (follow them exactly)
|
||
|
||
1. **Language of the conversation: Spanish.** Talk to the user in Spanish.
|
||
2. **Commit messages: English, only about the code that changed.** Never mention the plan,
|
||
part numbers (P0, P1...), the Log, or "step X". One commit per part, imperative mood,
|
||
e.g. `Simplify infrastructure services and move all recurring jobs to Hangfire`.
|
||
(This is also saved in the memory file `feedback_commit_messages.md`.)
|
||
3. **Never include the plan documents in commits.** `backend/docs/backend-refactor-plan.md`
|
||
and `backend/docs/backend-refactor-handoff.md` stay untracked. Also never commit the
|
||
temporary test `backend/Tests/Musify.Api.Tests/Endpoints/ZzOpenApiDumpTests.cs`.
|
||
Stage with `git add backend web-player` and then
|
||
`git reset -q backend/docs backend/Tests/Musify.Api.Tests/Endpoints/ZzOpenApiDumpTests.cs`.
|
||
4. **Do the plan in parts, one part per commit, in order.** Do not mix parts. Never mix
|
||
formatting with behaviour.
|
||
5. **When the user says to stop at a point, stop there** (the last message was "para en esta
|
||
parte cuando termines", so work stopped after P11). Only continue when asked.
|
||
6. Follow the plan's own rules: read section A first; create a shared helper from section B
|
||
yourself if it does not exist yet; keep the Log and the findings index (section E) up to date;
|
||
a contract change needs the web-player regeneration (section 4 below); an EF model change
|
||
needs a migration and `TestDatabase` kept in sync; a MassTransit message change must be
|
||
noted in the Log ("drain queues before deploying").
|
||
7. Pronouns: use they/them for anyone whose pronouns are not stated.
|
||
8. Outward-facing or hard-to-reverse actions (push, PR, deleting things) need explicit approval.
|
||
Nothing has been pushed; do not push or open a PR unless asked.
|
||
9. Report faithfully: if a test fails or something was skipped or unverifiable
|
||
(e.g. manual end-to-end runs), say so in the Log and in the final message.
|
||
|
||
## 3. Coding conventions (from the plan, section B.2, keep applying them)
|
||
|
||
- File-scoped namespaces, usings outside, sorted (`.editorconfig` enforces it; the build treats
|
||
warnings as errors, including unused usings IDE0005).
|
||
- Private fields camelCase without underscore; constants PascalCase; explicit accessibility.
|
||
- Application: `<Feature>/<UseCase>.cs` holds record + handler; responses in `Responses/`.
|
||
- Errors via `AppErrors` (`NotFound`, `Forbidden` = 403 for "not owner", `Conflict`);
|
||
`Unauthorized` (401) only for "not signed in". Success value is `Result.Success`.
|
||
- No try/catch around `SaveChangesAsync`/`PublishAsync` in handlers (outbox: publish then save
|
||
is atomic). Infrastructure either handles or rethrows, never log-and-rethrow.
|
||
- Handlers that cannot fail return plain types; lists are paginated, ordered with `Id` tiebreak,
|
||
public reads return only `LifeCycleStatus.Active`.
|
||
- Text: trim stored names; normalized columns use `TextNormalizer.Normalize`.
|
||
- `ArgumentNullException.ThrowIfNull(x)` without `nameof`.
|
||
- User-facing strings in English (the web-player owns translation).
|
||
- Naming: `PlayList` everywhere; config POCOs end in `Configuration`; injected services are
|
||
named after their type (`storageService`).
|
||
|
||
## 4. Practical procedures (learned the hard way)
|
||
|
||
Run everything from `backend/` unless stated.
|
||
|
||
### Build, format fixes, tests
|
||
|
||
```bash
|
||
dotnet build Musify.slnx
|
||
dotnet test Musify.slnx
|
||
```
|
||
|
||
- Do **not** chain `dotnet test ... | grep && commit`: a failing run can look successful.
|
||
Read the totals (`total:`, `succeeded:`) before committing.
|
||
- Infrastructure and Api tests need Docker Desktop running. If `docker info` fails, start
|
||
`C:\Users\XShin\AppData\Local\Programs\DockerDesktop\Docker Desktop.exe` and wait.
|
||
- Unused-using errors (IDE0005) are common after edits. A helper script removes them:
|
||
`python <scratchpad>/fixusings.py` (loops `dotnet build`, deletes the flagged using lines).
|
||
If the scratchpad is gone, do it by hand or with `dotnet format`.
|
||
|
||
### Editing files from the shell (important)
|
||
|
||
- The Bash tool **fails to parse** many commands that combine several heredocs, inline
|
||
`python - <<'EOF'` with backslashes/quotes, or big C# blocks ("unexpected EOF while looking
|
||
for matching `'`"). When that happens nothing runs. Use the **Write tool** for files and put
|
||
Python edit scripts in a file (in the scratchpad) and run `python that_file.py`.
|
||
- Working tree files use LF while git has autocrlf on; warnings like "LF will be replaced by
|
||
CRLF" are noise. Redirect stderr (`2>/dev/null`) on `git add/commit`. Python edit helpers
|
||
should read/write with `newline=''` and preserve CRLF if present.
|
||
|
||
### EF migrations
|
||
|
||
```bash
|
||
export Database__ConnectionString="Host=localhost;Database=x;Username=x;Password=x"
|
||
dotnet ef migrations add <Name> --project Core/Musify.Infrastructure --startup-project Core/Musify.Infrastructure
|
||
```
|
||
|
||
- `dotnet ef migrations remove` needs a DB connection and fails offline: delete the migration
|
||
files by hand and `git checkout` the snapshot, then re-add.
|
||
- To verify a model refactor keeps the schema: add a migration named `Check`, confirm `Up`
|
||
is empty, delete its two files (keep snapshot changes if they only rename types).
|
||
- Hand-edit migrations when data must be preserved (see `MixKind`, `PlayListTrackUnique`).
|
||
- `Application.Tests/TestSupport/TestDatabase.cs` mirrors part of `Database.OnModelCreating`
|
||
(P14.3 will extract `ModelConfiguration.Apply`).
|
||
|
||
### Web-player contract regeneration (when an HTTP contract changes)
|
||
|
||
1. `web-player/node_modules` is a junction to the main checkout's `node_modules`
|
||
(git-ignored). If missing:
|
||
`cmd //c "mklink /J web-player\node_modules ..\..\..\web-player\node_modules"` from the worktree root.
|
||
2. The OpenAPI document is dumped by the temporary test `ZzOpenApiDumpTests` (untracked; it
|
||
returns early unless env `OPENAPI_OUT` is set and runs the API in Development). Script used:
|
||
|
||
```bash
|
||
cd backend
|
||
export ASPNETCORE_ENVIRONMENT=Development OPENAPI_OUT="<scratchpad>/openapi.json"
|
||
dotnet test Tests/Musify.Api.Tests --filter "FullyQualifiedName~ZzOpenApiDump"
|
||
cd ../web-player
|
||
npx -y openapi-typescript@7 "<scratchpad>/openapi.json" -o src/lib/api/schema.d.ts
|
||
npm run check
|
||
```
|
||
|
||
If the test file is gone, recreate it: it fetches `/openapi/v1.json` through
|
||
`fixture.CreateAnonymousClient()` and writes it to `OPENAPI_OUT`.
|
||
P12.1 changes `WebApplication.CreateBuilder(args)`; keep the dump working afterwards.
|
||
3. Fix web-player call sites, run `npx prettier --write` on touched files, then `npm run check`.
|
||
4. Delete `ZzOpenApiDumpTests.cs` at the very end (P15).
|
||
|
||
## 5. What is left, part by part
|
||
|
||
### P12: API layer (plan section C, P12)
|
||
|
||
1. `Program.cs`: `WebApplication.CreateBuilder(args)`, sorted usings, CORS constant at top.
|
||
2. Cover endpoints: **already done** with `CoverEndpoint.Stream`.
|
||
3. Routes: resource id is `{id}`, sub-resources `{trackId}`; Guid routes use `:guid`,
|
||
user ids `:long`. Playlist routes keep `/playlists`. (Contract change: regenerate.)
|
||
4. Every endpoint returns `IResult` through `ToHttpResult` (some still return raw types,
|
||
e.g. `GetListeningHistory`, `IsFollowing`); use `Results.Ok` where the handler cannot fail.
|
||
5. DTOs: drop the `New...` prefix (`UpdateAlbumRequest(Title, Description, ReleaseYear,
|
||
PictureIntentId)`; same for playlists). Decision recommended in the plan: rename.
|
||
6. Validators: merge the album/playlist picture upload DTOs into one
|
||
`PictureUploadRequest` and one validator (the responses are already merged).
|
||
7. `OpenApiOptionsSetup` and `ScalarOptionsSetup` share the `SecuritySchemeId` constant.
|
||
8. `.WithSummary(...)` strings in sentence case, no trailing period. (Current ones are Title Case.)
|
||
9. `JwtBearerOptionsSetup`: single-line guard without braces.
|
||
10. `Validators/Limits.cs` with every validation limit (playlist name 50, descriptions 256,
|
||
titles 200, user name 48, release year range). List them in the Log for the web-player
|
||
plan (P7.1). Database column length wins on disagreement.
|
||
|
||
Coordination table (section A.4): P3.1 and P8.1 of the web-player plan are unblocked by P7;
|
||
P8.4 (bulk add) by P9; P7.1 (shared limits) by P12.10. Tick them in the web-player plan's Log too
|
||
(`web-player/docs/frontend-audit-plan.md`, also untracked).
|
||
|
||
### P13: Worker and Gateway hosts
|
||
|
||
1. Worker `Program.cs`: call `AddApplication(configuration)` instead of registering Application
|
||
options by hand (Album config is missing today), add `AddObservability`, remove
|
||
`ValidateOnBuild = false` and make the container validate. Register the extra POCO configs
|
||
the handlers need (stream tickets, gateway config) rather than disabling validation.
|
||
Then `AddUploadIntentConfiguration` (kept only for the Worker) can go.
|
||
2. Worker `appsettings.json`: complete and consistent with the Api for shared sections
|
||
(add `Album`; keys were renamed in P5), remove stale ones.
|
||
3. Gateway: keep option registration local and self-contained; field naming without
|
||
underscores (`TicketValidator` uses `_options`, `_rsa`, ...; the middleware field that
|
||
shadowed a constructor parameter is already gone); replace the default bucket in the
|
||
YARP transform of `appsettings.json` with a placeholder like `/buckets/CHANGE_ME/{**rest}`
|
||
(compose overrides it) or document it in `docs/streaming.md`.
|
||
Check `deploy/` when renaming config keys.
|
||
|
||
### P14: Tests
|
||
|
||
1. Add tests for what has none: MassTransit activities (generic lifecycle and picture-update
|
||
ones with the SQLite `TestDatabase`), `ProcessingSlipFaultConsumer` (every process kind),
|
||
routing slip builders (activity order and arguments), saga state machines with MassTransit's
|
||
test harness (happy path, failure, missing instance on update; sagas already use
|
||
`OnMissingInstance(Discard)`), `RecurringJobsRegistrar.EveryCron` (internal, may need
|
||
`InternalsVisibleTo`).
|
||
2. Endpoint tests for visibility and ownership if not already covered (P1, P3 added several).
|
||
3. Extract the owned-type and index configuration of `Database` into
|
||
`ModelConfiguration.Apply(ModelBuilder)` in Infrastructure and call it from `Database` and
|
||
`TestDatabase` (`Application.Tests` then references Infrastructure; note it in the Log).
|
||
|
||
### P15: Final sweep and documentation
|
||
|
||
1. Recompute the metrics of section A.5 (script in the plan) and fill D.1 (baseline was
|
||
12,687 non-migration, non-test `.cs` lines; targets in A.5). Also record the MassTransit
|
||
line count (3,547 to about 2,530) and the Application line count.
|
||
2. Grep for leftovers: `Error.Unauthorized(`, `StaticPagedList`, `new ImageSize(` (only in
|
||
`PictureSizes.ToImageSizes`), `ThrowIfNull(.*nameof`, `#pragma warning disable CS8618`,
|
||
try blocks around `SaveChangesAsync` in `Application/`, `serviceDescriptors`,
|
||
`storageHandler`, `PlaylistVisibility`, log-and-rethrow in `MassTransit/Activities/Files`.
|
||
3. The analyzer rules downgraded to `suggestion` in `.editorconfig` in P0 (CA1848, CA1873,
|
||
CA1305, CA1707, CA1859, CA1716, CA1000, CA1725, CA1816, CA1822, CA1852, CA1068, CA1711,
|
||
CA1861) should be revisited: fix the cheap ones and remove their overrides.
|
||
4. Run `dotnet format Musify.slnx` and check the diff is whitespace only (some test files have a
|
||
stray blank line before the closing brace).
|
||
5. Update `docs/architecture.md`, `docs/media-processing.md`, `docs/projects.md`,
|
||
`docs/storage.md`, `docs/streaming.md` with the new names (jobs, entities, routes, config
|
||
keys such as `ParentFolder`, `Temp*`, activity/queue names, `MixKind`).
|
||
6. Add a short `backend/CLAUDE.md` with the conventions of section 3 above (this one **is**
|
||
committed).
|
||
7. Delete `ZzOpenApiDumpTests.cs`, keep the plan (section E rows should all point to a checked
|
||
part in D), and remove the `web-player/node_modules` junction only if desired (it is ignored).
|
||
|
||
## 6. Deployment notes to keep in the Log
|
||
|
||
Queues must be drained before deploying because these message contracts changed:
|
||
processing-slip process kinds and variables (P2), `ImageSizes` in events and arguments (P8),
|
||
new lifecycle/delete/update-picture activities and arguments (P11), `DeleteAlbumEvent` (P3).
|
||
Migrations added so far: `PlayListTrackUnique`, `DropUploadTableAndTrackAudioRetryColumns`,
|
||
`DropUserHasTrack`, `MixKind`. Old bucket objects under the `temporal/` prefix (from before the
|
||
`Temp*` config fix) may need a one-off clean-up.
|
||
|
||
## 7. Suggested first steps for the next session
|
||
|
||
1. `git log --oneline -14` and `git status --short` (expect only the two untracked docs and
|
||
`ZzOpenApiDumpTests.cs`).
|
||
2. Run build + tests to confirm the green baseline.
|
||
3. Start P12, commit, update the Log, then continue only if the user has not asked to stop.
|