Files
photofetch/backend/src/db.ts
2026-06-26 16:25:08 +01:00

70 lines
2.1 KiB
TypeScript

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<string, DirectoryEntry> {
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<string, DirectoryEntry>()
for (const r of rows) {
map.set(r.email, { email: r.email, imageUrl: r.image_url, slackId: r.slack_id })
}
return map
}