import { DatabaseSync } from "node:sqlite" import { mkdirSync } from "node:fs" import { dirname } from "node:path" const dbPath = process.env.DB_PATH || "data/app.db" mkdirSync(dirname(dbPath), { recursive: true }) export const db = new DatabaseSync(dbPath) // A snapshot of the Slack workspace directory: one row per Slack user that has // an email, plus a single meta row recording when the snapshot was last swept. // An unmatched Member is simply one whose email isn't in this table (see ADR-0001). db.exec(` CREATE TABLE IF NOT EXISTS slack_directory ( email TEXT PRIMARY KEY, image_url TEXT, slack_id TEXT ); CREATE TABLE IF NOT EXISTS slack_directory_meta ( id INTEGER PRIMARY KEY CHECK (id = 1), refreshed_at INTEGER NOT NULL ); `) export interface DirectoryEntry { email: string imageUrl: string | null slackId: string } export function getDirectoryRefreshedAt(): number | null { const row = db.prepare("SELECT refreshed_at FROM slack_directory_meta WHERE id = 1").get() as | { refreshed_at: number } | undefined return row ? row.refreshed_at : null } // Atomically replace the whole directory snapshot and stamp refreshed_at. export function replaceDirectory(entries: DirectoryEntry[]): void { const insert = db.prepare( "INSERT OR REPLACE INTO slack_directory (email, image_url, slack_id) VALUES (?, ?, ?)", ) const stamp = db.prepare( "INSERT OR REPLACE INTO slack_directory_meta (id, refreshed_at) VALUES (1, ?)", ) db.exec("BEGIN") try { db.exec("DELETE FROM slack_directory") for (const e of entries) insert.run(e.email.toLowerCase(), e.imageUrl, e.slackId) stamp.run(Date.now()) db.exec("COMMIT") } catch (err) { db.exec("ROLLBACK") throw err } } export function getDirectoryMap(): Map { const rows = db.prepare("SELECT email, image_url, slack_id FROM slack_directory").all() as { email: string image_url: string | null slack_id: string }[] const map = new Map() for (const r of rows) { map.set(r.email, { email: r.email, imageUrl: r.image_url, slackId: r.slack_id }) } return map }