// lib/store.js — single-file local database (data/db.json) with atomic writes. // Portable: copy data/db.json to move your whole library + settings anywhere. import fs from "fs"; import path from "path"; const DATA_DIR = path.join(process.cwd(), "data"); const DB_PATH = path.join(DATA_DIR, "db.json"); export const DEFAULT_SETTINGS = { // Shown in the header of printed/exported assignments. profile: { teacherName: "", className: "", schoolName: "", logo: "", // small data-URL image (downscaled client-side before saving) }, provider: "ollama", providers: { // Env overrides let the Docker image default to the host machine's // Ollama / LM Studio (host.docker.internal) without touching the UI. ollama: { baseUrl: process.env.OLLAMA_BASE_URL || "http://localhost:11434", model: "" }, lmstudio: { baseUrl: process.env.LMSTUDIO_BASE_URL || "http://localhost:1234", model: "" }, openai: { apiKey: "", model: "gpt-4o-mini" }, anthropic: { apiKey: "", model: "claude-sonnet-4-6" }, google: { apiKey: "", model: "gemini-2.0-flash" }, }, generation: { auto: true, // size maxTokens/maxSourceChars to the selected model (lib/model-caps.js) temperature: 0.3, maxTokens: 8000, maxSourceChars: 24000, verification: true, }, }; function emptyDb() { return { assignments: [], settings: structuredClone(DEFAULT_SETTINGS) }; } function readDb() { try { if (!fs.existsSync(DB_PATH)) return emptyDb(); const raw = fs.readFileSync(DB_PATH, "utf8"); const db = JSON.parse(raw); if (!Array.isArray(db.assignments)) db.assignments = []; db.settings = mergeSettings(db.settings); return db; } catch { // Corrupt file: keep a backup, start fresh rather than crash. try { fs.copyFileSync(DB_PATH, DB_PATH + ".corrupt-" + Date.now()); } catch {} return emptyDb(); } } function writeDb(db) { fs.mkdirSync(DATA_DIR, { recursive: true }); const tmp = DB_PATH + ".tmp"; fs.writeFileSync(tmp, JSON.stringify(db, null, 2), "utf8"); fs.renameSync(tmp, DB_PATH); } export function mergeSettings(saved) { const base = structuredClone(DEFAULT_SETTINGS); if (!saved || typeof saved !== "object") return base; const out = { ...base, ...saved }; out.providers = { ...base.providers }; for (const key of Object.keys(base.providers)) { out.providers[key] = { ...base.providers[key], ...(saved.providers?.[key] || {}) }; } out.generation = { ...base.generation, ...(saved.generation || {}) }; out.profile = { ...base.profile, ...(saved.profile || {}) }; return out; } // ---------- Settings ---------- export function getSettings() { return readDb().settings; } export function saveSettings(settings) { const db = readDb(); db.settings = mergeSettings(settings); writeDb(db); return db.settings; } // ---------- Assignments ---------- export function listAssignments() { const db = readDb(); return db.assignments .map((a) => ({ id: a.id, title: a.title || "Untitled assignment", assignmentType: a.assignmentType, gradeLevel: a.gradeLevel, subject: a.subject, questionCount: (a.questions || []).length, totalPoints: (a.questions || []).reduce((s, q) => s + (Number(q.points) || 0), 0), createdAt: a.createdAt, updatedAt: a.updatedAt, })) .sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt))); } export function getAssignment(id) { return readDb().assignments.find((a) => a.id === id) || null; } export function createAssignment(assignment) { const db = readDb(); const now = new Date().toISOString(); const record = { ...assignment, id: assignment.id || "a_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 7), createdAt: now, updatedAt: now, }; db.assignments.push(record); writeDb(db); return record; } export function updateAssignment(id, patch) { const db = readDb(); const idx = db.assignments.findIndex((a) => a.id === id); if (idx === -1) return null; db.assignments[idx] = { ...db.assignments[idx], ...patch, id, createdAt: db.assignments[idx].createdAt, updatedAt: new Date().toISOString(), }; writeDb(db); return db.assignments[idx]; } export function deleteAssignment(id) { const db = readDb(); const before = db.assignments.length; db.assignments = db.assignments.filter((a) => a.id !== id); writeDb(db); return db.assignments.length < before; }