Files
support_backend/src/modules/orchestration/escalation/controller/escalation.controller.ts
T
saqib mirandClaude Sonnet 5 3bd068b031 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>
2026-09-07 16:31:20 +05:30

80 lines
3.0 KiB
TypeScript

import { FastifyReply, FastifyRequest } from 'fastify';
import { escalationService, EscalationService } from '../service';
import {
createEscalationPolicySchema,
createEscalationRuleSchema,
updateEscalationRuleSchema,
manualEscalationSchema,
listRecentEventsQuerySchema,
} from '../schema';
function actorFrom(request: FastifyRequest): string {
return request.reqContext?.actorId ?? 'unknown';
}
export class EscalationController {
constructor(private readonly service: EscalationService = escalationService) {}
async createPolicy(request: FastifyRequest, reply: FastifyReply) {
const body = createEscalationPolicySchema.parse(request.body);
const policy = await this.service.createPolicy(body);
return reply.status(201).send({ success: true, data: policy, meta: null });
}
async listPolicies(_request: FastifyRequest, reply: FastifyReply) {
const policies = await this.service.listPolicies();
return reply.status(200).send({ success: true, data: policies, meta: null });
}
async createRule(request: FastifyRequest, reply: FastifyReply) {
const { id } = request.params as { id: string };
const body = createEscalationRuleSchema.parse(request.body);
const rule = await this.service.createRule(id, body);
return reply.status(201).send({ success: true, data: rule, meta: null });
}
async updateRule(request: FastifyRequest, reply: FastifyReply) {
const { ruleId } = request.params as { id: string; ruleId: string };
const body = updateEscalationRuleSchema.parse(request.body);
const rule = await this.service.updateRule(ruleId, body);
return reply.status(200).send({ success: true, data: rule, meta: null });
}
async deleteRule(request: FastifyRequest, reply: FastifyReply) {
const { ruleId } = request.params as { id: string; ruleId: string };
await this.service.deactivateRule(ruleId);
return reply.status(204).send();
}
/** 012-admin-list-views: recent escalation events across every ticket, for the monitoring
* view. */
async listRecentEvents(request: FastifyRequest, reply: FastifyReply) {
const { limit } = listRecentEventsQuerySchema.parse(request.query);
const events = await this.service.listRecentEvents(limit);
const data = events.map((event) => ({
ticketId: event.ticketId,
ticketCode: event.ticket.code,
reason: event.reason,
ruleId: event.ruleId,
triggeredBy: event.triggeredBy,
toNodeId: event.toNodeId,
createdAt: event.createdAt,
}));
return reply.status(200).send({ success: true, data, meta: null });
}
async escalateManually(request: FastifyRequest, reply: FastifyReply) {
const { ticketId } = request.params as { ticketId: string };
const body = manualEscalationSchema.parse(request.body);
const event = await this.service.escalateManually(
ticketId,
body.targetNodeId,
actorFrom(request),
body.reason,
);
return reply.status(201).send({ success: true, data: event, meta: null });
}
}
export const escalationController = new EscalationController();