Files
Orchestrator/dhive/Jury-Voting/server/store.ts
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

137 lines
3.2 KiB
TypeScript

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>): 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>): 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];
}