60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
/**
|
|
* Housekeeping run when the database is opened.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { randomBytes, scryptSync } from "node:crypto";
|
|
import type { DatabaseSync } from "node:sqlite";
|
|
import {
|
|
ATTACHMENTS_DIR,
|
|
ORPHANED_ATTACHMENTS_RETENTION_HOURS,
|
|
UNUSED_INVITES_RETENTION_DAYS,
|
|
} from "../config.ts";
|
|
|
|
/**
|
|
* Runs once when the database is opened, after the migrations: drops what has
|
|
* expired (reset tokens, unused invites, orphaned attachments), and seeds the
|
|
* default admin (admin/admin) on an empty instance.
|
|
*/
|
|
export async function runStartupTasks(db: DatabaseSync): Promise<void> {
|
|
// Purge expired/used password reset tokens
|
|
db.prepare(
|
|
`DELETE FROM password_reset_tokens WHERE expires_at < datetime('now') OR used_at IS NOT NULL;`,
|
|
).run();
|
|
|
|
// Purge expired unused invites
|
|
db.prepare(
|
|
`DELETE FROM invites WHERE used_at IS NULL AND created_at < datetime('now', '-${UNUSED_INVITES_RETENTION_DAYS} days');`,
|
|
).run();
|
|
|
|
// Prune orphaned attachments (uploaded but never linked to a resource) older than 1 hour
|
|
const orphanedAttachments = db.prepare(
|
|
`SELECT id FROM attachments WHERE resource_id IS NULL AND created_at < datetime('now', '-${ORPHANED_ATTACHMENTS_RETENTION_HOURS} hour');`,
|
|
).all() as { id: string }[];
|
|
if (orphanedAttachments.length > 0) {
|
|
for (const { id } of orphanedAttachments) {
|
|
await Deno.remove(`${ATTACHMENTS_DIR}/${id}`).catch(() => {});
|
|
}
|
|
db.prepare(
|
|
`DELETE FROM attachments WHERE resource_id IS NULL AND created_at < datetime('now', '-${ORPHANED_ATTACHMENTS_RETENTION_HOURS} hour');`,
|
|
).run();
|
|
}
|
|
|
|
// Create default admin user if no users exist
|
|
const userCount = db.prepare(`SELECT COUNT(*) as count FROM users`).get() as {
|
|
count: number;
|
|
};
|
|
if (userCount.count === 0) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync("admin", salt, 64).toString("hex");
|
|
const passwordHash = `${hash}.${salt}`;
|
|
db.prepare(
|
|
`INSERT INTO users (id, username, password_hash, role, created_at, email) VALUES (?, 'admin', ?, 'admin', datetime('now'), 'admin@localhost')`,
|
|
).run(crypto.randomUUID(), passwordHash);
|
|
console.log(
|
|
"Created default admin user (username: admin, password: admin)",
|
|
);
|
|
}
|
|
}
|