feat(22-01): implement resolveTicketToCampaign resolver service

- Pure lookup: reports row for a ticket id -> found/reportId/campaignId/ticketNumber
- Parameterized query only (WHERE ticket_id = $1), no requirePermission/NextResponse
This commit is contained in:
lorentz 2026-07-16 14:26:32 -04:00
parent 20b1e1bb6f
commit e619321b4b

View file

@ -0,0 +1,48 @@
/**
* Phishing Ticket -> Campaign Resolver (Phase 22, Wave 0)
*
* Pure lookup service: given an Autotask ticket id, finds the linked
* `reports` row (if any) and reports back its campaign linkage. No auth,
* no param parsing that lives in the route (plan 02, GET
* /api/phishing/tickets/[ticket_id]/campaign). Extracted as its own module
* so it is unit-testable under vitest (whose `test.include` is
* `lib/**\/*.test.ts` only `app/**` route files have no coverage).
*/
import { postgresClient } from './postgres-client';
export interface TicketCampaignResolution {
found: boolean;
reportId?: string;
campaignId?: string | null;
ticketNumber?: string | null;
}
interface ReportLookupRow {
id: string;
campaign_id: string | null;
ticket_number: string | null;
}
/**
* Looks up the `reports` row for a given ticket id (tickets.id / reports.ticket_id).
* - No row: `{ found: false }`
* - Row with `campaign_id = null`: `{ found: true, reportId, campaignId: null, ticketNumber }` ungrouped report
* - Row with a `campaign_id`: `{ found: true, reportId, campaignId, ticketNumber }` grouped into a campaign
*/
export async function resolveTicketToCampaign(ticketId: number): Promise<TicketCampaignResolution> {
const res = await postgresClient.query<ReportLookupRow>(
`SELECT id::text, campaign_id::text, ticket_number FROM reports WHERE ticket_id = $1`,
[ticketId]
);
const report = res.rows[0];
if (!report) {
return { found: false };
}
return {
found: true,
reportId: report.id,
campaignId: report.campaign_id,
ticketNumber: report.ticket_number,
};
}