Files
notification-wakeup-service/src/alertSearch/daily.ts
T

117 lines
3.1 KiB
TypeScript

import {
alertAuthorizationDb,
type AlertAuthorizationStore,
} from "../db/alertAuthorizationSqlite.js";
import { smsAlertAuthorizationDb } from "../db/smsAlertAuthorizationSqlite.js";
import { utcCalendarDay } from "../services/alertAuthorization.js";
import {
runAlertSearchCycle,
type AlertSearchCycleInput,
} from "./cycle.js";
import {
buildAlertSearchDigest,
sourceCompletedDailyRun,
type AlertSearchDigest,
} from "./digest.js";
import type {
AlertSearchChannel,
AlertSearchQueryOutcome,
} from "./types.js";
const JWT_INVENTORIES: Record<AlertSearchChannel, AlertAuthorizationStore> = {
fcm: alertAuthorizationDb,
sms: smsAlertAuthorizationDb,
};
export { sourceCompletedDailyRun };
export type DailyAlertSearchCycleInput = Omit<AlertSearchCycleInput, "jwt">;
export type DailyAlertSearchResult = {
userId: string;
/** The UTC day whose JWT this run used, or null when none was selected. */
utcDay: string | null;
batchId: string | null;
jwtSequence: number | null;
endorserOutcome: AlertSearchQueryOutcome | null;
partnerOutcome: AlertSearchQueryOutcome | null;
completed: boolean;
consumed: boolean;
/** Phase 6A digest after a retrieve; null when no batch/JWT was used. */
digest: AlertSearchDigest | null;
};
function noJwtResult(
userId: string,
utcDay: string | null,
batchId: string | null
): DailyAlertSearchResult {
return {
userId,
utcDay,
batchId,
jwtSequence: null,
endorserOutcome: null,
partnerOutcome: null,
completed: false,
consumed: false,
digest: null,
};
}
/**
* Select today's unused delegated JWT by UTC day, run the existing alertSearch
* cycle, and consume that JWT only when both required sources completed
* (success or empty). Not invoked by the scheduler.
*/
export async function runDailyAlertSearch(
userId: string,
now: Date = new Date(),
cycleInput: DailyAlertSearchCycleInput = {},
channel: AlertSearchChannel = "fcm"
): Promise<DailyAlertSearchResult> {
const inventory = JWT_INVENTORIES[channel];
const batch = await inventory.getLatestBatch(userId);
if (batch === undefined) {
return noJwtResult(userId, null, null);
}
const utcDay = utcCalendarDay(Math.floor(now.getTime() / 1000));
const selected = await inventory.getUnusedForDay(userId, utcDay);
if (selected === undefined) {
return noJwtResult(userId, utcDay, batch.batchId);
}
const cycle = await runAlertSearchCycle(
userId,
{ ...cycleInput, jwt: selected.jwt },
channel
);
const digest = buildAlertSearchDigest(cycle.retrieved);
const endorserOutcome = cycle.retrieved.endorser.outcome;
const partnerOutcome = cycle.retrieved.partner.outcome;
const completed =
sourceCompletedDailyRun(endorserOutcome) &&
sourceCompletedDailyRun(partnerOutcome);
let consumed = false;
if (completed) {
consumed = await inventory.consumeUnusedJwt({
id: selected.id,
userId,
});
}
return {
userId,
utcDay,
batchId: selected.batchId,
jwtSequence: selected.sequence,
endorserOutcome,
partnerOutcome,
completed,
consumed,
digest,
};
}