117 lines
3.1 KiB
TypeScript
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,
|
|
};
|
|
}
|