/** * 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 { const res = await postgresClient.query( `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, }; }