Initial commit: StudyOS platform
This commit is contained in:
181
server/db.js
Normal file
181
server/db.js
Normal file
@@ -0,0 +1,181 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const DATA_DIR = path.resolve(__dirname, '..', 'data');
|
||||
const DB_PATH = path.join(DATA_DIR, 'studyos.db');
|
||||
|
||||
if (!fs.existsSync(DATA_DIR)) {
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
let _sqlDb = null;
|
||||
|
||||
function saveToDisk() {
|
||||
if (!_sqlDb) return;
|
||||
const data = _sqlDb.export();
|
||||
fs.writeFileSync(DB_PATH, Buffer.from(data));
|
||||
}
|
||||
|
||||
function flatParams(params) {
|
||||
if (params.length === 1 && Array.isArray(params[0])) return params[0];
|
||||
return params;
|
||||
}
|
||||
|
||||
function createWrapper(sqlDb) {
|
||||
return {
|
||||
prepare(sql) {
|
||||
return {
|
||||
run(...params) {
|
||||
const p = flatParams(params);
|
||||
sqlDb.run(sql, p);
|
||||
const rowidResult = sqlDb.exec('SELECT last_insert_rowid()');
|
||||
const lastInsertRowid = rowidResult.length > 0 ? rowidResult[0].values[0][0] : 0;
|
||||
const changes = sqlDb.getRowsModified();
|
||||
saveToDisk();
|
||||
return { changes, lastInsertRowid };
|
||||
},
|
||||
get(...params) {
|
||||
const p = flatParams(params);
|
||||
let stmt = null;
|
||||
try {
|
||||
stmt = sqlDb.prepare(sql);
|
||||
stmt.bind(p);
|
||||
let result = null;
|
||||
if (stmt.step()) result = stmt.getAsObject();
|
||||
return result;
|
||||
} finally {
|
||||
if (stmt) stmt.free();
|
||||
}
|
||||
},
|
||||
all(...params) {
|
||||
const p = flatParams(params);
|
||||
let stmt = null;
|
||||
try {
|
||||
stmt = sqlDb.prepare(sql);
|
||||
stmt.bind(p);
|
||||
const results = [];
|
||||
while (stmt.step()) results.push(stmt.getAsObject());
|
||||
return results;
|
||||
} finally {
|
||||
if (stmt) stmt.free();
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
exec(sql) {
|
||||
sqlDb.exec(sql);
|
||||
saveToDisk();
|
||||
},
|
||||
pragma(sql) {
|
||||
sqlDb.exec('PRAGMA ' + sql + ';');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const db = createWrapper(null);
|
||||
|
||||
async function initDB() {
|
||||
const SQL = await import('sql.js');
|
||||
const initSqlJs = SQL.default;
|
||||
const sqlModule = await initSqlJs();
|
||||
|
||||
let sqlDb;
|
||||
if (fs.existsSync(DB_PATH)) {
|
||||
const buffer = fs.readFileSync(DB_PATH);
|
||||
sqlDb = new sqlModule.Database(buffer);
|
||||
} else {
|
||||
sqlDb = new sqlModule.Database();
|
||||
}
|
||||
|
||||
_sqlDb = sqlDb;
|
||||
|
||||
const real = createWrapper(sqlDb);
|
||||
db.prepare = real.prepare;
|
||||
db.exec = real.exec;
|
||||
db.pragma = real.pragma;
|
||||
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS models (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
api_base TEXT NOT NULL,
|
||||
api_key TEXT NOT NULL DEFAULT '',
|
||||
provider TEXT NOT NULL CHECK(provider IN ('openai', 'anthropic')),
|
||||
is_default_main INTEGER NOT NULL DEFAULT 0,
|
||||
is_default_fork INTEGER NOT NULL DEFAULT 0,
|
||||
is_default_exam INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS conversations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('main', 'fork')),
|
||||
parent_id INTEGER REFERENCES conversations(id) ON DELETE SET NULL,
|
||||
model_id INTEGER REFERENCES models(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conversation_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system', 'context_merge')),
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pdfs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
filename TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
content_markdown TEXT NOT NULL DEFAULT '',
|
||||
pages INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
reorder_index INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS progress (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
topic TEXT NOT NULL UNIQUE,
|
||||
exercises_done INTEGER NOT NULL DEFAULT 0,
|
||||
exercises_correct INTEGER NOT NULL DEFAULT 0,
|
||||
last_session TEXT,
|
||||
notes TEXT NOT NULL DEFAULT '[]'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content_markdown TEXT NOT NULL DEFAULT '',
|
||||
tags TEXT NOT NULL DEFAULT '[]',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
`);
|
||||
|
||||
const modelCount = db.prepare('SELECT COUNT(*) as count FROM models').get();
|
||||
if (!modelCount || modelCount.count === 0) {
|
||||
db.prepare(`
|
||||
INSERT INTO models (name, api_base, api_key, provider, is_default_main)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run('claude-sonnet-4', 'https://api.anthropic.com', '', 'anthropic', 1);
|
||||
}
|
||||
|
||||
const vlmConfig = db.prepare("SELECT value FROM config WHERE key = ?").get('vlm_endpoint');
|
||||
if (!vlmConfig) {
|
||||
db.prepare('INSERT INTO config (key, value) VALUES (?, ?)').run('vlm_endpoint', 'http://localhost:8080/vlm');
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
module.exports = db;
|
||||
module.exports.initDB = initDB;
|
||||
Reference in New Issue
Block a user