Use SQLite for shared program state
This commit is contained in:
BIN
data/flow.db
Normal file
BIN
data/flow.db
Normal file
Binary file not shown.
@@ -1,13 +1,18 @@
|
|||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { DatabaseSync } from 'node:sqlite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
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 {
|
try {
|
||||||
const raw = await fs.readFile(databasePath, 'utf8');
|
const raw = await fs.readFile(seedJsonPath, 'utf8');
|
||||||
return JSON.parse(raw);
|
return JSON.parse(raw);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') return null;
|
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) {
|
async function writeDatabase(data) {
|
||||||
await fs.mkdir(path.dirname(databasePath), { recursive: true });
|
const db = await getDatabase();
|
||||||
await fs.writeFile(databasePath, JSON.stringify({
|
const updatedAt = new Date().toISOString();
|
||||||
...data,
|
const content = data.content ?? {};
|
||||||
updatedAt: new Date().toISOString()
|
const programStates = data.programStates ?? {};
|
||||||
}, null, 2));
|
|
||||||
|
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) {
|
function readRequestBody(request) {
|
||||||
@@ -48,7 +115,7 @@ function sendJson(response, statusCode, payload) {
|
|||||||
|
|
||||||
function serverDatabasePlugin() {
|
function serverDatabasePlugin() {
|
||||||
return {
|
return {
|
||||||
name: 'flow-server-database',
|
name: 'flow-server-sqlite-database',
|
||||||
configureServer(server) {
|
configureServer(server) {
|
||||||
server.middlewares.use('/api/state', async (request, response) => {
|
server.middlewares.use('/api/state', async (request, response) => {
|
||||||
try {
|
try {
|
||||||
@@ -66,11 +133,10 @@ function serverDatabasePlugin() {
|
|||||||
const current = request.method === 'PATCH' ? (await readDatabase()) ?? {} : {};
|
const current = request.method === 'PATCH' ? (await readDatabase()) ?? {} : {};
|
||||||
const body = await readRequestBody(request);
|
const body = await readRequestBody(request);
|
||||||
const payload = body ? JSON.parse(body) : {};
|
const payload = body ? JSON.parse(body) : {};
|
||||||
const nextData = {
|
const nextData = await writeDatabase({
|
||||||
...current,
|
...current,
|
||||||
...payload
|
...payload
|
||||||
};
|
});
|
||||||
await writeDatabase(nextData);
|
|
||||||
sendJson(response, 200, nextData);
|
sendJson(response, 200, nextData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user