Files
growqr-backend/src/events/projectors/service-session-projector.ts
2026-06-04 16:12:32 +05:30

77 lines
2.6 KiB
TypeScript

import { eq } from "drizzle-orm";
import { db } from "../../db/client.js";
import { missionServiceSessions, type GrowEventRow } from "../../db/schema.js";
import { asRecord, getString } from "../envelope.js";
import { normalizeServiceId } from "../record-grow-event.js";
function extractExternalId(event: GrowEventRow): string | undefined {
const correlation = asRecord(event.correlation);
const payload = event.payload ?? {};
return getString(
correlation.sessionId ??
correlation.session_id ??
correlation.externalId ??
correlation.external_id ??
payload.session_id ??
payload.sessionId ??
payload.id,
);
}
function statusFor(event: GrowEventRow): string {
const payload = event.payload ?? {};
const explicit = getString(payload.status);
if (explicit) return explicit;
if (event.type.includes("review_completed") || event.type.includes("completed")) return "completed";
if (event.type.includes("failed")) return "failed";
if (event.type.includes("configured") || event.type.includes("created")) return "active";
return "active";
}
export async function applyServiceSessionProjection(event: GrowEventRow) {
if (!event.userId) return null;
const externalId = extractExternalId(event);
if (!externalId) return null;
const serviceId = normalizeServiceId(event.source);
if (!["interview", "roleplay", "resume"].includes(serviceId)) return null;
const mission = asRecord(event.mission);
const metadata = {
lastType: event.type,
subject: event.subject,
payloadStatus: event.payload?.status,
};
const [row] = await db
.insert(missionServiceSessions)
.values({
userId: event.userId,
missionInstanceId: getString(mission.instanceId ?? mission.instance_id),
missionId: getString(mission.missionId ?? mission.mission_id),
stageId: getString(mission.stageId ?? mission.stage_id),
serviceId,
externalId,
status: statusFor(event),
metadata,
lastEventId: event.id,
updatedAt: new Date(),
})
.onConflictDoUpdate({
target: [missionServiceSessions.serviceId, missionServiceSessions.externalId],
set: {
status: statusFor(event),
metadata,
lastEventId: event.id,
lastCheckedAt: event.type.includes("review") ? new Date() : undefined,
updatedAt: new Date(),
},
})
.returning();
// Touch the row if Drizzle ever returns no row for an upsert variant.
if (!row) {
await db.update(missionServiceSessions).set({ updatedAt: new Date() }).where(eq(missionServiceSessions.externalId, externalId));
}
return row ?? null;
}