89 lines
2.6 KiB
JavaScript
89 lines
2.6 KiB
JavaScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 80;
|
|
const DATA_DIR = process.env.DATA_DIR || path.resolve(__dirname, '../data_store');
|
|
const STATE_FILE = path.join(DATA_DIR, 'wortkraft_state.json');
|
|
|
|
if (!fs.existsSync(DATA_DIR)) {
|
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
function readState() {
|
|
try {
|
|
if (fs.existsSync(STATE_FILE)) {
|
|
const raw = fs.readFileSync(STATE_FILE, 'utf8');
|
|
return JSON.parse(raw);
|
|
}
|
|
} catch (err) {
|
|
console.error('[WORTKRAFT] Error reading state file:', err);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function writeState(stateObj) {
|
|
try {
|
|
fs.writeFileSync(STATE_FILE, JSON.stringify(stateObj, null, 2), 'utf8');
|
|
return true;
|
|
} catch (err) {
|
|
console.error('[WORTKRAFT] Error writing state file:', err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
app.use(cors());
|
|
app.use(express.json({ limit: '10mb' }));
|
|
|
|
// Healthcheck endpoint for Docker & Homepage
|
|
app.get('/api/health', (_req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
app: 'wortkraft',
|
|
timestamp: new Date().toISOString(),
|
|
uptimeSeconds: Math.floor(process.uptime())
|
|
});
|
|
});
|
|
|
|
// Get persisted learning state (SRS deck, streak, custom words & stories)
|
|
app.get('/api/state', (_req, res) => {
|
|
const state = readState();
|
|
res.json({ exists: Boolean(state), state });
|
|
});
|
|
|
|
// Save learning state
|
|
app.post('/api/state', (req, res) => {
|
|
const payload = req.body;
|
|
if (!payload || typeof payload !== 'object') {
|
|
return res.status(400).json({ error: 'Invalid state payload' });
|
|
}
|
|
payload.updatedAt = new Date().toISOString();
|
|
const ok = writeState(payload);
|
|
res.json({ success: ok, updatedAt: payload.updatedAt });
|
|
});
|
|
|
|
// Serve static frontend files from project root
|
|
const PUBLIC_DIR = path.resolve(__dirname, '..');
|
|
app.use('/data', express.static(path.join(PUBLIC_DIR, 'data')));
|
|
app.get('/styles.css', (_req, res) => res.sendFile(path.join(PUBLIC_DIR, 'styles.css')));
|
|
app.get('/app.js', (_req, res) => res.sendFile(path.join(PUBLIC_DIR, 'app.js')));
|
|
app.get('/', (_req, res) => res.sendFile(path.join(PUBLIC_DIR, 'index.html')));
|
|
|
|
app.use((req, res, next) => {
|
|
if (req.method === 'GET' && !req.path.startsWith('/api')) {
|
|
return res.sendFile(path.join(PUBLIC_DIR, 'index.html'));
|
|
}
|
|
next();
|
|
});
|
|
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`[WORTKRAFT] Server listening on port ${PORT}`);
|
|
console.log(`[WORTKRAFT] Persistent data directory: ${DATA_DIR}`);
|
|
});
|