SPB Git

spb/drive Public

SPB Drive — self-hosted personal cloud drive (files, previews, sharing) on the MacLustr cluster.

JavaScript 82.7% CSS 10.6% Nunjucks 3.6% Shell 1.8% SQL 1.3%
2.8 KB · 83 lines javascript
Raw Blame History
1/**2 * ─────────────────────────────────────────────3 *  SPB Drive — Personal Cloud Drive4 * ─────────────────────────────────────────────5 *  Author  : Simon-Pierre Boucher6 *  Contact : contact@spboucher.ai7 *  File    : src/db/db.mjs8 *  Purpose : SQLite bootstrap (better-sqlite3, WAL), migrations, root node seed9 *  License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213import { readFileSync } from 'node:fs';14import path from 'node:path';15import { fileURLToPath } from 'node:url';16import Database from 'better-sqlite3';17import { config } from '../config.mjs';1819const HERE = path.dirname(fileURLToPath(import.meta.url));2021/** @type {import('better-sqlite3').Database | null} */22let db = null;2324/**25 * Open (or return the already-open) database, applying schema migrations26 * and seeding the root folder node on first run.27 * @param {string} [file] Override DB path (tests).28 * @returns {import('better-sqlite3').Database}29 */30export function getDb(file) {31  if (db) return db;32  db = new Database(file ?? config.dbFile);33  db.pragma('journal_mode = WAL');34  db.pragma('foreign_keys = ON');35  db.pragma('synchronous = NORMAL');3637  db.exec(readFileSync(path.join(HERE, 'schema.sql'), 'utf8'));38  migrate(db);3940  const root = db.prepare('SELECT id FROM nodes WHERE id = 1').get();41  if (!root) {42    db.prepare(43      `INSERT INTO nodes (id, parent_id, name, type, created, modified)44       VALUES (1, NULL, '', 'folder', ?, ?)`,45    ).run(Date.now(), Date.now());46  }47  return db;48}4950/**51 * Column-level migrations for tables that predate v2 (CREATE IF NOT EXISTS52 * won't touch existing tables, so new columns are added here).53 */54function migrate(database) {55  const addColumn = (table, column, ddl) => {56    const present = database57      .prepare(`SELECT 1 FROM pragma_table_info(?) WHERE name = ?`)58      .get(table, column);59    if (!present) database.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddl}`);60  };61  // v2: public file-request uploads are tagged with their request id.62  addColumn('uploads', 'source', 'TEXT');63}6465/** Close the database (tests / graceful shutdown). */66export function closeDb() {67  if (db) {68    db.close();69    db = null;70  }71}7273/**74 * Record an activity-log entry.75 * @param {string} kind e.g. 'login', 'upload', 'rename', 'share.create'76 * @param {{nodeId?: number|null, detail?: string, ip?: string}} [extra]77 */78export function logActivity(kind, { nodeId = null, detail = '', ip = '' } = {}) {79  getDb()80    .prepare('INSERT INTO activity (ts, kind, node_id, detail, ip) VALUES (?, ?, ?, ?, ?)')81    .run(Date.now(), kind, nodeId, detail, ip);82}83