Files
notification-wakeup-service/src/db/alertSearchCursorsSqlite.ts
T

115 lines
3.2 KiB
TypeScript

import { getDatabase } from "./sqlite.js";
export type AlertSearchCursorRecord = {
userId: string;
endorserAfterId: string | null;
partnerAfterAt: string | null;
createdAt: string;
updatedAt: string;
};
type CursorDbRow = {
user_id: string;
endorser_after_id: string | null;
partner_after_at: string | null;
created_at: string;
updated_at: string;
};
function toRecord(row: CursorDbRow): AlertSearchCursorRecord {
return {
userId: row.user_id,
endorserAfterId: row.endorser_after_id,
partnerAfterAt: row.partner_after_at,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
export const FCM_ALERT_SEARCH_CURSORS_TABLE = "alert_search_cursors";
export const SMS_ALERT_SEARCH_CURSORS_TABLE = "sms_alert_search_cursors";
export type AlertSearchCursorsStore = ReturnType<
typeof createAlertSearchCursorsStore
>;
function ensureRow(table: string, userId: string, now: string): void {
getDatabase()
.prepare(
`
INSERT INTO ${table} (
user_id, endorser_after_id, partner_after_at, created_at, updated_at
) VALUES (?, NULL, NULL, ?, ?)
ON CONFLICT(user_id) DO NOTHING
`
)
.run(userId, now, now);
}
/**
* Builds a store over one cursor table. Each channel runs its own daily
* retrieval, so sharing one row would let whichever fired first consume the
* delta and leave the other reporting nothing.
*/
export function createAlertSearchCursorsStore(table: string) {
return {
async get(
userId: string
): Promise<AlertSearchCursorRecord | undefined> {
const row = getDatabase()
.prepare(
`
SELECT user_id, endorser_after_id, partner_after_at, created_at, updated_at
FROM ${table}
WHERE user_id = ?
`
)
.get(userId) as CursorDbRow | undefined;
return row === undefined ? undefined : toRecord(row);
},
async setEndorserAfterId(userId: string, afterId: string): Promise<void> {
const now = new Date().toISOString();
const connection = getDatabase();
const run = connection.transaction(() => {
ensureRow(table, userId, now);
connection
.prepare(
`
UPDATE ${table}
SET endorser_after_id = ?, updated_at = ?
WHERE user_id = ?
`
)
.run(afterId, now, userId);
});
run();
},
async setPartnerAfterAt(userId: string, afterAt: string): Promise<void> {
const now = new Date().toISOString();
const connection = getDatabase();
const run = connection.transaction(() => {
ensureRow(table, userId, now);
connection
.prepare(
`
UPDATE ${table}
SET partner_after_at = ?, updated_at = ?
WHERE user_id = ?
`
)
.run(afterAt, now, userId);
});
run();
},
};
}
export const alertSearchCursorsDb: AlertSearchCursorsStore =
createAlertSearchCursorsStore(FCM_ALERT_SEARCH_CURSORS_TABLE);
/** The SMS channel's own cursor row per DID. */
export const smsAlertSearchCursorsDb: AlertSearchCursorsStore =
createAlertSearchCursorsStore(SMS_ALERT_SEARCH_CURSORS_TABLE);