feat(012-admin-list-views): SLA-run, escalation-event, and product-catalog list endpoints

Adds GET /admin/sla-runs (filterable by status), GET /admin/escalation-
events (capped, most-recent-first), and GET /admin/products (with
integration status joined in, never the full ProductIntegration row).
None of these existed as a single query before - only per-ticket or
per-integration-id lookups did.

Discovered while planning supporthub-web's 001-agent-admin-ui User
Stories 6-7 (SLA/escalation monitoring, product catalog), the same way
011-agent-ticket-queue was discovered for User Story 1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
saqib mir
2026-09-07 16:31:20 +05:30
co-authored by Claude Sonnet 5
parent 43158ff0c4
commit 3bd068b031
18 changed files with 411 additions and 18 deletions
@@ -40,3 +40,13 @@
- Deliberately narrow: read-only, no new persisted entity, no general search/filter API beyond
the one filter (`status`) and one cap (`limit`) each list actually needs, per Assumptions.
- All items pass; no revision iterations were needed.
- **Implementation-time finding**: research.md's plan.md draft had described the existing
single-ticket `GET /tickets/:ticketId/sla-run` as "agent-facing (fastify.authenticate)" — it's
actually fully ungated (no preHandler at all). Didn't change this feature's own design
(`GET /admin/sla-runs`/`GET /admin/escalation-events` still use `fastify.authenticate`, a
deliberately more conservative choice than the existing route, matching spec.md's own
"agent-usable" wording), but worth correcting for anyone reading research.md later.
- No `SLA_RUN_STATUSES` constant existed anywhere before this feature — `SLARun.status` had
only ever been written as free strings across the pause/resume/breach-detection code paths.
Centralized it in `orchestration/sla/mapper/sla-run-status.ts` since this feature is the first
caller that needs to validate against it, not just write it.
+16 -16
View File
@@ -24,20 +24,20 @@ All file paths are relative to `supporthub-api/` (repo root).
**Independent Test**: Quickstart Scenario 1.
- [ ] T001 [P] [US1] Add `SLARunRepository.findAll(status?)` in
- [x] T001 [P] [US1] Add `SLARunRepository.findAll(status?)` in
`src/modules/orchestration/sla/repository/sla-run.repository.ts``include: { ticket:
{ select: { id: true, code: true } } }`, optional `where: { status }`
- [ ] T002 [US1] Add `SLAService.listAll(status?)` (or directly on the controller if no service
- [x] T002 [US1] Add `SLAService.listAll(status?)` (or directly on the controller if no service
method is warranted — check existing pattern) validating `status` against
`SLA_RUN_STATUSES` (400 on an invalid value) in
`src/modules/orchestration/sla/service/sla.service.ts` (depends on T001)
- [ ] T003 [US1] Add `GET /admin/sla-runs` (`fastify.authenticate` only) in
- [x] T003 [US1] Add `GET /admin/sla-runs` (`fastify.authenticate` only) in
`src/modules/orchestration/sla/controller/` + `routes/`, projecting each row to
`SlaRunListItem` (data-model.md) (depends on T002)
- [ ] T004 [US1] Integration test covering Quickstart Scenario 1 (unfiltered returns all;
- [x] T004 [US1] Integration test covering Quickstart Scenario 1 (unfiltered returns all;
`status=breached` filters correctly; an invalid status is 400) in
`tests/integration/admin-list-views.test.ts`
- [ ] T005 [US1] Run Quickstart Scenario 1 locally and confirm all 3 steps pass
- [x] T005 [US1] Run Quickstart Scenario 1 locally and confirm all 3 steps pass
---
@@ -45,18 +45,18 @@ All file paths are relative to `supporthub-api/` (repo root).
**Independent Test**: Quickstart Scenario 2.
- [ ] T006 [P] [US2] Add `EscalationEventRepository.findRecent(limit)` in
- [x] T006 [P] [US2] Add `EscalationEventRepository.findRecent(limit)` in
`src/modules/orchestration/escalation/repository/escalation-event.repository.ts`
`include: { ticket: { select: { id: true, code: true } } }`, `orderBy: { createdAt:
'desc' }`, `take: limit`
- [ ] T007 [US2] Add `GET /admin/escalation-events` (`fastify.authenticate` only, `limit` query
- [x] T007 [US2] Add `GET /admin/escalation-events` (`fastify.authenticate` only, `limit` query
param `z.coerce.number().int().positive().max(200).default(50)`) in
`src/modules/orchestration/escalation/controller/` + `routes/`, projecting to
`EscalationEventListItem` (depends on T006)
- [ ] T008 [US2] Integration test covering Quickstart Scenario 2 (both events appear, most-
- [x] T008 [US2] Integration test covering Quickstart Scenario 2 (both events appear, most-
recent-first, automatic vs manual distinguished by `ruleId`) in
`tests/integration/admin-list-views.test.ts` (same file as T004)
- [ ] T009 [US2] Run Quickstart Scenario 2 locally and confirm it passes
- [x] T009 [US2] Run Quickstart Scenario 2 locally and confirm it passes
---
@@ -64,26 +64,26 @@ All file paths are relative to `supporthub-api/` (repo root).
**Independent Test**: Quickstart Scenario 3.
- [ ] T010 [P] [US3] Add `ProductsRepository.findAllWithIntegrationStatus()` in
- [x] T010 [P] [US3] Add `ProductsRepository.findAllWithIntegrationStatus()` in
`src/modules/catalog/products/repository/products.repository.ts``include: {
integration: { select: { status: true } } }`, `orderBy: { name: 'asc' }`
- [ ] T011 [US3] Add `GET /admin/products` (`fastify.authenticate` + `requireRole('ADMIN')`) in
- [x] T011 [US3] Add `GET /admin/products` (`fastify.authenticate` + `requireRole('ADMIN')`) in
`src/modules/catalog/products/controller/` + `routes/`, projecting each row to
`ProductCatalogListItem` (`integrationStatus: product.integration?.status ?? null`
never the full `ProductIntegration` row, research.md) (depends on T010)
- [ ] T012 [US3] Integration test covering Quickstart Scenario 3 (active + no-integration
- [x] T012 [US3] Integration test covering Quickstart Scenario 3 (active + no-integration
products both correct; non-admin gets 403) in `tests/integration/admin-list-views.test.ts`
(same file as T004/T008)
- [ ] T013 [US3] Run Quickstart Scenario 3 locally and confirm both steps pass
- [x] T013 [US3] Run Quickstart Scenario 3 locally and confirm both steps pass
---
## Phase 4: Polish & Cross-Cutting Concerns
- [ ] T014 [P] Update `specs/012-admin-list-views/checklists/requirements.md` Notes with any
- [x] T014 [P] Update `specs/012-admin-list-views/checklists/requirements.md` Notes with any
implementation-time findings
- [ ] T015 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck`
- [ ] T016 Full regression: `npm run test:unit` then the full integration suite against real
- [x] T015 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck`
- [x] T016 Full regression: `npm run test:unit` then the full integration suite against real
Docker-provisioned Postgres/Redis, confirming nothing outside this feature regressed
---