diff --git a/data/flow.db b/data/flow.db new file mode 100644 index 0000000..2c512b7 Binary files /dev/null and b/data/flow.db differ diff --git a/vite.config.js b/vite.config.js index e285b88..2b6410a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,13 +1,18 @@ import fs from 'node:fs/promises'; import path from 'node:path'; +import { DatabaseSync } from 'node:sqlite'; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -const databasePath = path.resolve(process.cwd(), 'data', 'flow-db.json'); +const dataDirectory = path.resolve(process.cwd(), 'data'); +const sqlitePath = path.join(dataDirectory, 'flow.db'); +const seedJsonPath = path.join(dataDirectory, 'flow-db.json'); -async function readDatabase() { +let database; + +async function readSeedJson() { try { - const raw = await fs.readFile(databasePath, 'utf8'); + const raw = await fs.readFile(seedJsonPath, 'utf8'); return JSON.parse(raw); } catch (error) { if (error.code === 'ENOENT') return null; @@ -15,12 +20,74 @@ async function readDatabase() { } } +async function getDatabase() { + if (database) return database; + + await fs.mkdir(dataDirectory, { recursive: true }); + database = new DatabaseSync(sqlitePath); + database.exec(` + CREATE TABLE IF NOT EXISTS app_state ( + id INTEGER PRIMARY KEY CHECK (id = 1), + content TEXT NOT NULL, + program_states TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + `); + + const existing = database.prepare('SELECT id FROM app_state WHERE id = 1').get(); + if (!existing) { + const seed = await readSeedJson(); + if (seed) writeDatabase(seed); + } else { + const seed = await readSeedJson(); + const row = database.prepare('SELECT content FROM app_state WHERE id = 1').get(); + const existingContent = row ? JSON.parse(row.content) : null; + const existingExtraCount = existingContent?.extraPrograms?.length ?? 0; + const seedExtraCount = seed?.content?.extraPrograms?.length ?? 0; + + if (existingExtraCount === 0 && seedExtraCount > 0) { + writeDatabase(seed); + } + } + + return database; +} + +async function readDatabase() { + const db = await getDatabase(); + const row = db + .prepare('SELECT content, program_states AS programStates, updated_at AS updatedAt FROM app_state WHERE id = 1') + .get(); + + if (!row) return null; + + return { + content: JSON.parse(row.content), + programStates: JSON.parse(row.programStates), + updatedAt: row.updatedAt + }; +} + async function writeDatabase(data) { - await fs.mkdir(path.dirname(databasePath), { recursive: true }); - await fs.writeFile(databasePath, JSON.stringify({ - ...data, - updatedAt: new Date().toISOString() - }, null, 2)); + const db = await getDatabase(); + const updatedAt = new Date().toISOString(); + const content = data.content ?? {}; + const programStates = data.programStates ?? {}; + + db.prepare(` + INSERT INTO app_state (id, content, program_states, updated_at) + VALUES (1, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + content = excluded.content, + program_states = excluded.program_states, + updated_at = excluded.updated_at + `).run(JSON.stringify(content), JSON.stringify(programStates), updatedAt); + + return { + content, + programStates, + updatedAt + }; } function readRequestBody(request) { @@ -48,7 +115,7 @@ function sendJson(response, statusCode, payload) { function serverDatabasePlugin() { return { - name: 'flow-server-database', + name: 'flow-server-sqlite-database', configureServer(server) { server.middlewares.use('/api/state', async (request, response) => { try { @@ -66,11 +133,10 @@ function serverDatabasePlugin() { const current = request.method === 'PATCH' ? (await readDatabase()) ?? {} : {}; const body = await readRequestBody(request); const payload = body ? JSON.parse(body) : {}; - const nextData = { + const nextData = await writeDatabase({ ...current, ...payload - }; - await writeDatabase(nextData); + }); sendJson(response, 200, nextData); return; }