77 lines
2.6 KiB
TypeScript
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;
|
|
}
|