import fs from 'fs'; import path from 'path'; import { StoreData, Session, Team, Juror, Score } from '../shared/types.js'; const DATA_DIR = path.join(process.cwd(), 'server', 'data'); const SESSION_FILE = path.join(DATA_DIR, 'session.json'); const SEED_FILE = path.join(DATA_DIR, 'seed.json'); const BACKUP_FILE = path.join(DATA_DIR, 'session.json.bak'); /** * Ensure the data directory exists. */ function ensureDataDir(): void { if (!fs.existsSync(DATA_DIR)) { fs.mkdirSync(DATA_DIR, { recursive: true }); } } /** * Initialize session.json from seed.json if it doesn't exist. */ function initializeStore(): void { ensureDataDir(); if (!fs.existsSync(SESSION_FILE)) { const seedData = fs.readFileSync(SEED_FILE, 'utf-8'); fs.writeFileSync(SESSION_FILE, seedData, 'utf-8'); } } // Initialize on module load initializeStore(); /** * Read the current store data from session.json. */ export function getStore(): StoreData { const raw = fs.readFileSync(SESSION_FILE, 'utf-8'); return JSON.parse(raw) as StoreData; } /** * Write store data atomically: write to temp file, then rename. * Creates a .bak backup before each write. */ export function saveStore(data: StoreData): void { ensureDataDir(); // Create backup of current file if it exists if (fs.existsSync(SESSION_FILE)) { fs.copyFileSync(SESSION_FILE, BACKUP_FILE); } // Write to temp file, then rename (atomic on most filesystems) const tempFile = path.join(DATA_DIR, 'session.json.tmp'); fs.writeFileSync(tempFile, JSON.stringify(data, null, 2), 'utf-8'); fs.renameSync(tempFile, SESSION_FILE); } /** * Get the current session. */ export function getSession(): Session { const store = getStore(); return store.session; } /** * Get all teams. */ export function getTeams(): Team[] { const store = getStore(); return store.teams; } /** * Get all jurors. */ export function getJurors(): Juror[] { const store = getStore(); return store.jurors; } /** * Get all scores. */ export function getScores(): Score[] { const store = getStore(); return store.scores; } /** * Add or update a score. Matches by jurorId + teamId + criterionId. */ export function addScore(score: Score): void { const store = getStore(); const existingIndex = store.scores.findIndex( (s) => s.jurorId === score.jurorId && s.teamId === score.teamId && s.criterionId === score.criterionId ); if (existingIndex >= 0) { store.scores[existingIndex] = score; } else { store.scores.push(score); } saveStore(store); } /** * Update session with partial updates. Returns the updated session. */ export function updateSession(updates: Partial): Session { const store = getStore(); store.session = { ...store.session, ...updates }; saveStore(store); return store.session; } /** * Update a juror by ID with partial updates. Returns the updated juror or null if not found. */ export function updateJuror(jurorId: string, updates: Partial): Juror | null { const store = getStore(); const jurorIndex = store.jurors.findIndex((j) => j.id === jurorId); if (jurorIndex < 0) { return null; } store.jurors[jurorIndex] = { ...store.jurors[jurorIndex], ...updates }; saveStore(store); return store.jurors[jurorIndex]; }