fix: the five metadata self links point at a readable resource #110
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/metadata-get-by-id"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What's in
GET /api/tags/{id},/api/people/{id},/api/circles/{id},/api/characters/{id}and/api/series/{id}— the five routes every metadata envelope has always advertised aslinks.selfbut which were never mapped.Because
PUTandDELETEdo exist on/{id:guid}, ASP.NET answered aGETthere with a bodiless 405 Method Not Allowed, not even a 404. ADR 0007 defineslinksas read affordances, so the envelope was advertising a resource the API refused to serve.Each route is backed by a new query use case (
IGetTagUseCaseand its four siblings, implemented in Infrastructure, registered inUseCaseRegistrationHelper), so endpoints still call use cases only. The services'GetByIdAsyncalready.Includes theDoujinsnavigation — the delete use cases depend on it — so the response carries an accuratedoujinCountand drops the conditionaldeleteaction for an entity in use, with no extra query. The result is the same envelope the sibling list/update/alias routes already emit for the same entity.Per ADR 0021 every route declares
.Produces<ResourceResponse<T>>(200),.ProducesProblem(404)and.ProducesProblem(401).No ADR is amended: the change satisfies ADR 0007 (the self link is now dereferenceable), ADR 0021, and ADR 0022 (all five metadata types keep one shape) rather than contradicting any. The README API table already listed
GET /{id}for metadata — it is now accurate.Verification
MetadataEntityIntegrationTests(15 cases) — the detail payload reached through the advertisedselfhref (id, self href, name, description, aliases,doujinCount,update/deleteactions); usage (entity assigned to a doujin reportsdoujinCount == 1and losesdelete); and 404 +"Not Found"title for an unknown id in each collection.hasUsage: false→ the usage theory red; nulling theDescriptionmapping → the detail theory red; replacingAliases ?? []with an empty list → the detail theory red.RestAdapter.Tests442 passed (includingEndpointResponseTypeTests, the ADR 0021 reflection test),ApplicationCore.Tests16,Infrastructure.Tests85,IntegrationTests1.Follow-ups
_refreshAfterWriteEpic→LoadMetadataAction(force: true)) still reloads all five metadata collections after every approved write; with these routes in place it can refetch just the entity that changed. (Note: this is not triggered by plain UI metadata CRUD, which patches Redux state from the write response — the pre-existing backlog entry that claimed otherwise is corrected in #107.)/api/backups/{fileName}has the same defect — its envelope advertises aselflink on a template that only exists forDELETE. The fix there is a download endpoint, which is a product decision rather than a gap.DoujinRepositoryport method was added for the new routes; nothing in the app needs one yet.Deferred items are collected in #107 (
docs/pile-a-backlog), which should merge first — the seven Pile A branches each wrote todocs/BACKLOG.mdand would otherwise all conflict on it.Verified independently on this branch with the full
scripts/verify.sh(backend build and tests, app codegen, analyze and tests): PASS.🤖 Generated with Claude Code
Every single-resource envelope for tags, people, circles, characters and series advertises links.self = /api/<collection>/{id}, but no GET was mapped for that template. Since PUT and DELETE do exist on it, following the advertised link answered a bodiless 405, not even a 404 — ADR 0007 calls links read affordances, so the envelope was advertising a resource the API did not serve. Adds GET /{id:guid} to all five groups, each backed by a new query use case (IGetTagUseCase and siblings) so endpoints still call use cases only. The services' GetByIdAsync already includes the Doujins navigation, so the response reports doujinCount and omits the conditional delete action for an entity in use without a second query — the same envelope the list and update routes emit for the same entity. Each route declares .Produces<ResourceResponse<T>>(200), .ProducesProblem(404) and .ProducesProblem(401) per ADR 0021. No ADR changes: the fix satisfies ADR 0007 (a self link is now dereferenceable), ADR 0021 and ADR 0022 (all five types keep one shape) rather than contradicting any. The README API table already documented GET /{id} for metadata; it is now true. Verification: three theories over all five collections in MetadataEntityIntegrationTests (detail payload + self link, usage/delete action, 404) — 15 cases. Mutation-probed: removing the routes reddens all 15; dropping the null guard reddens the 404 theory; pinning hasUsage to false reddens the usage theory; nulling description or dropping aliases reddens the detail theory. Full RestAdapter.Tests 442 passed, plus ApplicationCore.Tests 16, Infrastructure.Tests 85, IntegrationTests 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Summary
Summary
Coverage
DoujinManager.ApplicationCore - 89.4%
DoujinManager.Infrastructure - 95.5%
on
pshot
Series
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
DoujinManager.RestAdapter - 89.2%
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
DoujinManager.Server - 29.5%
🔮 fufu~ Jibril reviewed your code!
Oh? Ohhh~ An API that advertises a
selflink and then answers the door with a bodiless 405? How wonderfully rude of it~ A link is a promise, you know. ADR 0007 sayslinksare read affordances, and this PR makes every one of the five metadata envelopes keep that promise. Five routes, five one-line query use cases, zero drama. I'm delighted~ ♪Verdict: ✅ Looks good to me~
✅ What I liked~
GetByIdAsync(TagService:18, PersonService:18, CircleService:18, CharacterService:18, SeriesService:18) — every single one already.Includes theDoujinsnavigation. SohasUsage: x.Doujins.Count > 0can't NRE,doujinCountis accurate, and no second query was spent. Reusing the Include the delete use cases already depend on, instead of inventing a count query — elegant~ ♡DoujinEndpoints.ProblemResult(404, "Not Found", "Tag not found")matches theGET /api/doujins/{id}sibling's shape, the envelope flows through the sameHypermediaHelpers.Build*Links/Actions, and ADR 0022's one-shape rule holds — the detail route emits the same envelope the write routes already did.IGetTagUseCase+ four siblings follow theIQuerypattern (twin ofGetDoujinUseCase), DI registrations sit in their per-entity groups. One public class per concern, no shortcuts taken straight to the service layer. Fufu~ you know how I feel about people who bypass use cases… they don't get to merge~ ♡hasUsage: falsereddened exactly the tags usage theory (1/15) and left the other 14 green. Not a tautology~ TheMetadataResourceflattening helper across the five DTO shapes (with the tags-has-no-count null arm!) is genuinely clever test design.EndpointResponseTypeTests(ADR 0021 reflection) 6/6 — the.Produces<ResourceResponse<T>>(200)/ 404 / 401 metadata is all present. My clone is restored pristine at4778180./api/backups/{fileName}cousin-with-the-same-disease note show good judgment about what this PR is and isn't.💡 Little ideas (non-blocking)~
ReadMetadataResourceAsync's five-arm switch is verbose, but the alternative (a shared interface on wire DTOs) would couple production types to tests. Keep as-is; just noting I looked and chose to say nothing~ ♪Come merge it before I change my mind~ fufu~ ♡
Automated review by Jibril · 2026-09-08
CI/CD: absent for head SHA
4778180· Local checks: build 0 errors, 15/15 new integration theories + 6/6 ADR-0021 reflection tests passed, mutation probe directional (clone restored)