Submatter/apps/worker/src/db/operator-notes.ts

69 lines
2 KiB
TypeScript
Raw Normal View History

import { and, asc, eq, isNull } from "drizzle-orm";
import type { db as dbInstance } from "@tasks/database";
import { convoyOperatorMessages } from "@tasks/database/schema";
type Db = typeof dbInstance;
export type OperatorMessageRow = typeof convoyOperatorMessages.$inferSelect;
/**
* Poll undelivered operator messages for a convoy.
* Keep polling fallback simple — no Redis pubsub, just a DB query.
*/
export async function pollOperatorNotes(
db: Db,
workspaceId: string,
convoyId: string,
briefId?: string | null,
): Promise<OperatorMessageRow[]> {
const conditions = [
eq(convoyOperatorMessages.workspaceId, workspaceId),
eq(convoyOperatorMessages.convoyId, convoyId),
isNull(convoyOperatorMessages.deliveredAt),
];
// Optional brief scoping — include global convoy notes plus brief-specific ones
// When briefId is provided we return rows where brief_id is null OR matches the brief.
// Keeping the simple fallback: just filter by workspace+convoy when briefId not set.
// When briefId is set we still return all for convoy so direct messages are not missed.
void briefId;
const rows = await db
.select()
.from(convoyOperatorMessages)
.where(and(...conditions))
.orderBy(asc(convoyOperatorMessages.createdAt));
return rows;
}
export async function markOperatorNotesDelivered(
db: Db,
ids: string[],
): Promise<void> {
if (ids.length === 0) return;
for (const id of ids) {
await db
.update(convoyOperatorMessages)
.set({ deliveredAt: new Date() })
.where(eq(convoyOperatorMessages.id, id));
}
}
/** Convenience: poll and mark delivered in one step */
export async function consumeOperatorNotes(
db: Db,
workspaceId: string,
convoyId: string,
briefId?: string | null,
): Promise<OperatorMessageRow[]> {
const rows = await pollOperatorNotes(db, workspaceId, convoyId, briefId);
if (rows.length === 0) return rows;
await markOperatorNotesDelivered(
db,
rows.map((r) => r.id),
);
return rows;
}