Run calendar module in isolated persistent container
This commit is contained in:
9
Dockerfile
Normal file
9
Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM node:24-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir -p /var/lib/mpm-module && chown node:node /var/lib/mpm-module
|
||||||
|
COPY --chown=node:node module.json ./module.json
|
||||||
|
COPY --chown=node:node backend ./backend
|
||||||
|
COPY --chown=node:node frontend ./frontend
|
||||||
|
USER node
|
||||||
|
EXPOSE 41002
|
||||||
|
CMD ["node", "backend/server.js"]
|
||||||
15
README.md
15
README.md
@@ -1,21 +1,20 @@
|
|||||||
# Kalender MVP
|
# Kalender MVP
|
||||||
|
|
||||||
Ein kleines, eigenständiges Kalender-Modul für MPM. Es zeigt einen Monatskalender, erlaubt Termine anzulegen und speichert sie in SQLite.
|
Ein kleines, eigenständiges Kalender-Modul für MPM. Es zeigt einen Monatskalender, erlaubt Termine anzulegen und speichert sie je MPM-Benutzer getrennt in SQLite.
|
||||||
|
|
||||||
## Voraussetzungen
|
## Containerbetrieb
|
||||||
|
|
||||||
- Node.js 24 oder neuer (verwendet `node:sqlite`)
|
MPM installiert das Repository als eigenen Compose-Stack. Der App-Container läuft mit Node.js 24, stellt Port `41002` nur intern bereit und lauscht auf `0.0.0.0`. Die SQLite-Datei liegt unter `MPM_MODULE_DATA_DIR` im persistenten MPM-Modulvolume und überlebt Container- sowie Modul-Neustarts.
|
||||||
- Installation als MPM-Modul aus einem Paket mit `module.json` im ZIP-Stammverzeichnis
|
|
||||||
|
|
||||||
MPM stellt die Modul-Identität über den Module Gateway bereit. Termine werden je MPM-Benutzer getrennt gespeichert. Die SQLite-Datei liegt derzeit im temporären Systemverzeichnis und ist daher nicht für dauerhafte Produktivdaten gedacht.
|
|
||||||
|
|
||||||
## Installation über den MPM-Marketplace
|
## Installation über den MPM-Marketplace
|
||||||
|
|
||||||
MPM kann dieses öffentliche Repository direkt hinzufügen und den aktuellen Stand des Standard-Branches installieren. `module.json` muss dafür im Repository-Stamm liegen. MPM prüft das Manifest und startet das Modul nach der Installation nicht automatisch.
|
Das öffentliche Repository kann mit der verbundenen Gitea-Instanz direkt aus dem Marketplace installiert werden. `module.json`, `compose.yml` und `Dockerfile` liegen im Repository-Stamm. MPM installiert den Standard-Branch; der Start erfolgt anschließend über die Modulverwaltung.
|
||||||
|
|
||||||
## Projektstruktur
|
## Projektstruktur
|
||||||
|
|
||||||
- `module.json` – MPM-Modulmanifest
|
- `module.json` – MPM-Modulmanifest einschließlich Container-Service
|
||||||
|
- `compose.yml` – interner App-Service für den Modul-Stack
|
||||||
|
- `Dockerfile` – Node.js-24-Laufzeit ohne externe npm-Abhängigkeiten
|
||||||
- `backend/server.js` – HTTP- und SQLite-Logik
|
- `backend/server.js` – HTTP- und SQLite-Logik
|
||||||
- `backend/platform-module-sdk.js` – MPM-Gateway-Integration
|
- `backend/platform-module-sdk.js` – MPM-Gateway-Integration
|
||||||
- `frontend/index.html` – Kalenderoberfläche
|
- `frontend/index.html` – Kalenderoberfläche
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const os = require('node:os');
|
|
||||||
const { DatabaseSync } = require('node:sqlite');
|
const { DatabaseSync } = require('node:sqlite');
|
||||||
const { createModuleServer, loadManifest, createLogger } = require('./platform-module-sdk');
|
const { createModuleServer, loadManifest, createLogger } = require('./platform-module-sdk');
|
||||||
process.umask(0o077);
|
process.umask(0o077);
|
||||||
const manifest = loadManifest(path.join(__dirname, '..', 'module.json'));
|
const manifest = loadManifest(path.join(__dirname, '..', 'module.json'));
|
||||||
const logger = createLogger({ moduleId: manifest.id });
|
const logger = createLogger({ moduleId: manifest.id });
|
||||||
const db = new DatabaseSync(path.join(os.tmpdir(), manifest.id + '.sqlite'));
|
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)');
|
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 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 insertEvent = db.prepare('INSERT INTO events(user_id,title,event_date,notes) VALUES(?,?,?,?)');
|
||||||
@@ -31,4 +32,10 @@ const server = createModuleServer({manifest,logger,routes:async(req,res,{path:pa
|
|||||||
if(pathname.startsWith('/api/'))return json(res,404,{error:'Route nicht gefunden.'});
|
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');
|
res.writeHead(404,{'content-type':'text/plain; charset=utf-8'});res.end('Nicht gefunden');
|
||||||
}});
|
}});
|
||||||
server.listen(Number(process.env.PORT||manifest.port),'127.0.0.1',()=>logger.info('Kalender MVP gestartet',{port:Number(process.env.PORT||manifest.port)}));
|
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);
|
||||||
|
|||||||
8
compose.yml
Normal file
8
compose.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
expose:
|
||||||
|
- "41002"
|
||||||
|
restart: unless-stopped
|
||||||
@@ -9,5 +9,7 @@
|
|||||||
"entrypoint": "server.js",
|
"entrypoint": "server.js",
|
||||||
"port": 41002,
|
"port": 41002,
|
||||||
"healthcheck": "/health",
|
"healthcheck": "/health",
|
||||||
"apiVersion": "v1"
|
"apiVersion": "v1",
|
||||||
|
"composeFile": "compose.yml",
|
||||||
|
"appService": "app"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user