115 lines
3.2 KiB
TypeScript
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);
|