diff --git a/backend/src/services/store.ts b/backend/src/services/store.ts new file mode 100644 index 0000000..07cf020 --- /dev/null +++ b/backend/src/services/store.ts @@ -0,0 +1,55 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { env } from '../config/env.js'; +import type { DatabaseShape } from '../types.js'; + +const emptyDb = (): DatabaseShape => ({ + users: [], + refreshTokens: [], + files: [], + events: [], +}); + +export class JsonStore { + private dbPath: string; + private writeQueue: Promise = Promise.resolve(); + + constructor(dataDir = env.dataDir) { + this.dbPath = path.join(dataDir, 'db.json'); + } + + async init() { + await fs.mkdir(path.dirname(this.dbPath), { recursive: true }); + try { + await fs.access(this.dbPath); + } catch { + await fs.writeFile(this.dbPath, JSON.stringify(emptyDb(), null, 2)); + } + } + + async read(): Promise { + await this.init(); + const raw = await fs.readFile(this.dbPath, 'utf8'); + return { ...emptyDb(), ...JSON.parse(raw) }; + } + + async write(db: DatabaseShape) { + await this.init(); + this.writeQueue = this.writeQueue.then(async () => { + const tmpPath = `${this.dbPath}.tmp`; + await fs.writeFile(tmpPath, JSON.stringify(db, null, 2)); + await fs.rename(tmpPath, this.dbPath); + }); + await this.writeQueue; + } + + async update(mutator: (db: DatabaseShape) => T | Promise): Promise { + const db = await this.read(); + const result = await mutator(db); + await this.write(db); + return result; + } +} + +export const store = new JsonStore(); +