70 lines
2.1 KiB
TypeScript
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
|
|
}
|