bizzle 5a51a0f112 Mr. Drew's Assignment Creator — Docker share build
Self-contained Dockerized build for end users. Run via docker compose;
see README.md for setup. Source-only, no sample data or build artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:58:36 -04:00

145 lines
4.4 KiB
JavaScript

// 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;
}