45 lines
3.9 KiB
JavaScript
45 lines
3.9 KiB
JavaScript
'use strict';
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
const { createModuleServer, loadManifest, createLogger } = require('./platform-module-sdk');
|
|
process.umask(0o077);
|
|
const manifest = loadManifest(path.join(__dirname, '..', 'module.json'));
|
|
const logger = createLogger({ moduleId: manifest.id });
|
|
const dataDirectory = process.env.MPM_MODULE_DATA_DIR || path.join(__dirname, '..', 'data');
|
|
fs.mkdirSync(dataDirectory, { recursive: true, mode: 0o700 });
|
|
const db = new DatabaseSync(path.join(dataDirectory, manifest.id + '.sqlite'));
|
|
db.exec('CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, title TEXT NOT NULL CHECK(length(title)<=120), event_date TEXT NOT NULL, notes TEXT NOT NULL DEFAULT "" CHECK(length(notes)<=1000), created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)');
|
|
const listEvents = db.prepare('SELECT id,title,event_date,notes FROM events WHERE user_id=? AND event_date>=? AND event_date<? ORDER BY event_date,title');
|
|
const insertEvent = db.prepare('INSERT INTO events(user_id,title,event_date,notes) VALUES(?,?,?,?)');
|
|
const calendarTitle = process.env.CALENDAR_TITLE?.trim();
|
|
if (!calendarTitle) throw new Error('CALENDAR_TITLE muss gesetzt werden');
|
|
const escapeHtml = (value) => value.replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', "'": ''' })[character]);
|
|
const html = fs.readFileSync(path.join(__dirname, '..', 'frontend', 'index.html'), 'utf8').replaceAll('Mein Kalender', escapeHtml(calendarTitle));
|
|
function json(res, status, value) { res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }); res.end(JSON.stringify(value)); }
|
|
async function bodyJson(req) { let data=''; for await (const chunk of req) { data+=chunk; if(data.length>16384) throw new Error('too large'); } return JSON.parse(data||'{}'); }
|
|
const server = createModuleServer({manifest,logger,routes:async(req,res,{path:pathname,url,identity})=>{
|
|
if(req.method==='GET'&&(pathname==='/'||pathname==='/index.html')){res.writeHead(200,{'content-type':'text/html; charset=utf-8','cache-control':'no-store'});res.end(html);return;}
|
|
if(pathname==='/api/events'&&req.method==='GET'){
|
|
const month=url.searchParams.get('month')||new Date().toISOString().slice(0,7);
|
|
if(!/^\d{4}-(0[1-9]|1[0-2])$/.test(month))return json(res,400,{error:'Ungültiger Monat.'});
|
|
const start=month+'-01',next=new Date(Date.UTC(Number(month.slice(0,4)),Number(month.slice(5,7)),1)).toISOString().slice(0,10);
|
|
return json(res,200,{events:listEvents.all(identity.userId,start,next)});
|
|
}
|
|
if(pathname==='/api/events'&&req.method==='POST'){
|
|
try {const data=await bodyJson(req);
|
|
if(typeof data.title!=='string'||!data.title.trim()||data.title.trim().length>120||typeof data.event_date!=='string'||!/^\d{4}-\d{2}-\d{2}$/.test(data.event_date)||Number.isNaN(Date.parse(data.event_date+'T00:00:00Z'))||(data.notes!==undefined&&(typeof data.notes!=='string'||data.notes.length>1000)))return json(res,400,{error:'Titel, Datum oder Notiz ist ungültig.'});
|
|
const result=insertEvent.run(identity.userId,data.title.trim(),data.event_date,data.notes||'');return json(res,201,{id:Number(result.lastInsertRowid)});
|
|
} catch(error){return json(res,400,{error:error.message==='too large'?'Anfrage zu groß.':'Ungültige Anfrage.'});}
|
|
}
|
|
if(pathname.startsWith('/api/'))return json(res,404,{error:'Route nicht gefunden.'});
|
|
res.writeHead(404,{'content-type':'text/plain; charset=utf-8'});res.end('Nicht gefunden');
|
|
}});
|
|
server.listen(Number(process.env.PORT||manifest.port),'0.0.0.0',()=>logger.info('Kalender MVP gestartet',{port:Number(process.env.PORT||manifest.port)}));
|
|
|
|
function shutdown() {
|
|
try { db.close(); } finally { server.close(() => process.exit(0)); }
|
|
}
|
|
process.once('SIGTERM', shutdown);
|
|
process.once('SIGINT', shutdown);
|