chore(canary-phase11): manage page API complete (GET + DELETE /m/{id})

Phase 11 deliverables (audited PASS):

Service primitives
- event.Service.CountActiveDedup(ctx, tokenID): Redis SCAN over
  dedup:trigger:{tokenID}:* (token-scoped pattern), batched 100,
  returns sum(value-1) — value=1 (first trigger fired) contributes 0,
  value=N (N-1 INCRs) contributes N-1 silenced events. Matches
  spec §10.5 "counter = total silenced" semantics.
- token.Service.DeleteByManageID: thin wrapper around
  Repository.DeleteByManageID; ServiceRepository interface gains the
  method.

DTOs
- ManageTokenView (slimmer than the create-time Response — drops
  manage_id / manage_url / metadata since the client is already on
  the manage page); ManagePage{NextCursor, HasMore}; ManageResponse
  shape exactly matches spec §8.4 example. event.Response (already
  in place from Phase 1) reused for the events array.

Handlers
- GET /api/m/{manage_id}: parses ?cursor (int64, must be ≥ 0; 400
  BAD_CURSOR otherwise) and ?limit (default 20, capped at 100).
  Calls GetByManageID + ListByToken + CountByToken + CountActiveDedup
  via the new EventQuery + DedupCounter interfaces, assembles
  ManageResponse. Page derived from list.HasMore + list.NextCursor
  (audit-fix ca67cf63 — was re-deriving from len before).
- DELETE /api/m/{manage_id}: 204 on success; 404 envelope on miss.
  Cascade is via the existing tokens-events FK ON DELETE CASCADE
  (verified by integration test).
- 404 on unknown manage_id returns the JSON envelope (not bare
  http.NotFound) so the frontend gets a parseable error code.
- NewHandler signature 5 → 6 args (added eventQuery + dedupCounter).
  Both test sites and main.go updated.

Wire-up
- cmd/canary/main.go: tokenH.RegisterManageRoutes(api) inside the
  /api subrouter. Inherits CORS + read-tier rate limit from /api;
  no Turnstile, no create-tier limits (read-style endpoint per
  spec §8.1).
- Extracted buildHTTPDeps to keep run() under the funlen ceiling.

Integration tests (testcontainers-Postgres + miniredis):
- ManagePageReturnsTokenAndEventsAndSilencedCount: 3 same-IP triggers
  → events_total=3, events_silenced_active=2, one NotifySent + two
  NotifyDeduped, no next page.
- ManagePageReturns404OnUnknownManageID
- ManageDeleteCascadesEvents: DELETE → CountByToken==0 + token gone

Audited: superpowers:code-reviewer + general-purpose spec adherence —
PASS. Both audits flagged one non-blocking nit (buildPage was
re-deriving NextCursor from len rather than using the repo's HasMore
flag); cleaned up in ca67cf63 to avoid future drift if the repo's
pagination logic evolves.
This commit is contained in:
CarterPerez-dev 2026-05-14 01:12:43 -04:00
parent ca67cf6313
commit de236328f8

Diff Content Not Available