refactor(esm): migrate server and tests from CommonJS to ESM

Convert all server/, test, and setup files from require()/module.exports
to import/export syntax. Activate ESM globally via "type": "module" in
package.json and load dotenv via --import dotenv/config in npm scripts.
This commit is contained in:
Ulas
2026-04-03 23:11:20 +02:00
parent 2f6127911e
commit b139eea623
24 changed files with 113 additions and 154 deletions
+4 -3
View File
@@ -3,13 +3,14 @@
"version": "0.5.9",
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js",
"type": "module",
"engines": {
"node": ">=22.0.0"
},
"scripts": {
"start": "node server/index.js",
"dev": "node --watch server/index.js",
"setup": "node setup.js",
"start": "node --import dotenv/config server/index.js",
"dev": "node --import dotenv/config --watch server/index.js",
"setup": "node --import dotenv/config setup.js",
"test:db": "node --experimental-sqlite test-db.js",
"test:dashboard": "node --experimental-sqlite test-dashboard.js",
"test:tasks": "node --experimental-sqlite test-tasks.js",
+31 -32
View File
@@ -4,31 +4,30 @@
* Abhängigkeiten: express, helmet, dotenv, server/db.js, server/auth.js, server/routes/*
*/
'use strict';
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const path = require('path');
const { createLogger } = require('./logger');
import express from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import path from 'path';
import { createLogger } from './logger.js';
import * as db from './db.js';
import { router as authRouter, sessionMiddleware, requireAuth } from './auth.js';
import { csrfMiddleware } from './middleware/csrf.js';
import * as googleCalendar from './services/google-calendar.js';
import * as appleCalendar from './services/apple-calendar.js';
import dashboardRouter from './routes/dashboard.js';
import tasksRouter from './routes/tasks.js';
import shoppingRouter from './routes/shopping.js';
import mealsRouter from './routes/meals.js';
import calendarRouter from './routes/calendar.js';
import notesRouter from './routes/notes.js';
import contactsRouter from './routes/contacts.js';
import budgetRouter from './routes/budget.js';
import weatherRouter from './routes/weather.js';
const log = createLogger('Server');
const logSync = createLogger('Sync');
const logOikos = createLogger('Oikos');
// --------------------------------------------------------
// Datenbank initialisieren (muss vor require('./auth') stehen,
// da BetterSQLiteStore im Konstruktor db.get() aufruft)
// --------------------------------------------------------
const db = require('./db');
db.init();
const { router: authRouter, sessionMiddleware, requireAuth } = require('./auth');
const { csrfMiddleware } = require('./middleware/csrf');
const googleCalendar = require('./services/google-calendar');
const appleCalendar = require('./services/apple-calendar');
const app = express();
const PORT = process.env.PORT || 3000;
@@ -107,7 +106,7 @@ app.use('/api/', (req, res, next) => {
// Bilder + Icons + Fonts: 30 Tage immutable (ändern sich praktisch nie).
// manifest.json + sw.js: no-cache (PWA-Updates sollen sofort greifen).
// --------------------------------------------------------
app.use(express.static(path.join(__dirname, '..', 'public'), {
app.use(express.static(path.join(import.meta.dirname, '..', 'public'), {
etag: true,
lastModified: true,
setHeaders(res, filePath) {
@@ -152,15 +151,15 @@ app.use('/api/v1/auth', authRouter);
// Alle weiteren API-Routen erfordern Authentifizierung + CSRF-Schutz
app.use('/api/v1', requireAuth);
app.use('/api/v1', csrfMiddleware);
app.use('/api/v1/dashboard', require('./routes/dashboard'));
app.use('/api/v1/tasks', require('./routes/tasks'));
app.use('/api/v1/shopping', require('./routes/shopping'));
app.use('/api/v1/meals', require('./routes/meals'));
app.use('/api/v1/calendar', require('./routes/calendar'));
app.use('/api/v1/notes', require('./routes/notes'));
app.use('/api/v1/contacts', require('./routes/contacts'));
app.use('/api/v1/budget', require('./routes/budget'));
app.use('/api/v1/weather', require('./routes/weather'));
app.use('/api/v1/dashboard', dashboardRouter);
app.use('/api/v1/tasks', tasksRouter);
app.use('/api/v1/shopping', shoppingRouter);
app.use('/api/v1/meals', mealsRouter);
app.use('/api/v1/calendar', calendarRouter);
app.use('/api/v1/notes', notesRouter);
app.use('/api/v1/contacts', contactsRouter);
app.use('/api/v1/budget', budgetRouter);
app.use('/api/v1/weather', weatherRouter);
// --------------------------------------------------------
// Health-Check (für Docker)
@@ -187,7 +186,7 @@ app.get('*', spaLimiter, (req, res) => {
if (req.path.startsWith('/api/')) {
return res.status(404).json({ error: 'Nicht gefunden.', code: 404 });
}
res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
res.sendFile(path.join(import.meta.dirname, '..', 'public', 'index.html'));
});
// --------------------------------------------------------
@@ -231,4 +230,4 @@ app.listen(PORT, () => {
}, 10_000);
});
module.exports = app;
export default app;
+2 -4
View File
@@ -11,9 +11,7 @@
* 5. GET/HEAD/OPTIONS sind ausgenommen (safe methods).
*/
'use strict';
const crypto = require('node:crypto');
import crypto from 'node:crypto';
const TOKEN_LENGTH = 32; // Bytes → 64 Hex-Zeichen
@@ -68,4 +66,4 @@ function csrfMiddleware(req, res, next) {
next();
}
module.exports = { csrfMiddleware, generateToken };
export { csrfMiddleware, generateToken };
+1 -3
View File
@@ -4,8 +4,6 @@
* Abhängigkeiten: keine
*/
'use strict';
// Globale Längengrenzen
const MAX_TITLE = 200;
const MAX_TEXT = 5000;
@@ -158,7 +156,7 @@ function id(val, field) {
return { value: n, error: null };
}
module.exports = {
export {
str, oneOf, date, time, datetime, month, num, color, rrule, id, collectErrors,
MAX_TITLE, MAX_TEXT, MAX_SHORT, MAX_RRULE,
DATE_RE, TIME_RE, DATETIME_RE, COLOR_RE, MONTH_RE,
+5 -6
View File
@@ -4,15 +4,14 @@
* Abhängigkeiten: express, server/db.js, server/auth.js
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
import { str, oneOf, date as validateDate, num, rrule, collectErrors, MAX_TITLE, MONTH_RE } from '../middleware/validate.js';
const { createLogger } = require('../logger');
const log = createLogger('Budget');
const express = require('express');
const router = express.Router();
const db = require('../db');
const { str, oneOf, date: validateDate, num, rrule, collectErrors, MAX_TITLE, MONTH_RE } = require('../middleware/validate');
// --------------------------------------------------------
// Wiederkehrende Einträge: fehlende Instanzen für einen Monat erzeugen
@@ -348,4 +347,4 @@ router.delete('/:id', (req, res) => {
}
});
module.exports = router;
export default router;
+9 -11
View File
@@ -5,20 +5,18 @@
* Abhängigkeiten: express, server/db.js, server/auth.js
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
import * as googleCalendar from '../services/google-calendar.js';
import * as appleCalendar from '../services/apple-calendar.js';
import { requireAdmin } from '../auth.js';
import { str, color, datetime, rrule, collectErrors, MAX_TITLE, MAX_TEXT, DATE_RE, DATETIME_RE } from '../middleware/validate.js';
import { nextOccurrence } from '../services/recurrence.js';
const { createLogger } = require('../logger');
const log = createLogger('Calendar');
const express = require('express');
const router = express.Router();
const db = require('../db');
const googleCalendar = require('../services/google-calendar');
const appleCalendar = require('../services/apple-calendar');
const { requireAdmin } = require('../auth');
const { str, color, datetime, rrule, collectErrors, MAX_TITLE, MAX_TEXT, DATE_RE, DATETIME_RE } = require('../middleware/validate');
const { nextOccurrence } = require('../services/recurrence');
const VALID_SOURCES = ['local', 'google', 'apple'];
@@ -545,4 +543,4 @@ router.delete('/:id', (req, res) => {
}
});
module.exports = router;
export default router;
+5 -6
View File
@@ -4,15 +4,14 @@
* Abhängigkeiten: express, server/db.js, server/auth.js
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
import { str, oneOf, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT } from '../middleware/validate.js';
const { createLogger } = require('../logger');
const log = createLogger('Contacts');
const express = require('express');
const router = express.Router();
const db = require('../db');
const { str, oneOf, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT } = require('../middleware/validate');
const VALID_CATEGORIES = ['Arzt', 'Schule/Kita', 'Behörde', 'Versicherung',
'Handwerker', 'Notfall', 'Sonstiges'];
@@ -201,4 +200,4 @@ router.get('/:id/vcard', (req, res) => {
}
});
module.exports = router;
export default router;
+4 -5
View File
@@ -4,14 +4,13 @@
* Abhängigkeiten: express, server/db.js
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
const { createLogger } = require('../logger');
const log = createLogger('Dashboard');
const express = require('express');
const router = express.Router();
const db = require('../db');
/**
* GET /api/v1/dashboard
@@ -129,4 +128,4 @@ router.get('/', (req, res) => {
}
});
module.exports = router;
export default router;
+5 -6
View File
@@ -4,15 +4,14 @@
* Abhängigkeiten: express, server/db.js, server/auth.js
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
import { str, oneOf, date, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT, DATE_RE } from '../middleware/validate.js';
const { createLogger } = require('../logger');
const log = createLogger('Meals');
const express = require('express');
const router = express.Router();
const db = require('../db');
const { str, oneOf, date, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT, DATE_RE } = require('../middleware/validate');
const VALID_MEAL_TYPES = ['breakfast', 'lunch', 'dinner', 'snack'];
@@ -471,4 +470,4 @@ router.post('/week-to-shopping-list', (req, res) => {
}
});
module.exports = router;
export default router;
+5 -6
View File
@@ -4,15 +4,14 @@
* Abhängigkeiten: express, server/db.js, server/auth.js
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
import { str, color, collectErrors, MAX_TEXT, MAX_TITLE } from '../middleware/validate.js';
const { createLogger } = require('../logger');
const log = createLogger('Notes');
const express = require('express');
const router = express.Router();
const db = require('../db');
const { str, color, collectErrors, MAX_TEXT, MAX_TITLE } = require('../middleware/validate');
/**
* GET /api/v1/notes
@@ -152,4 +151,4 @@ router.delete('/:id', (req, res) => {
}
});
module.exports = router;
export default router;
+5 -6
View File
@@ -7,15 +7,14 @@
* vor dynamischen (/:listId) registriert sein, damit Express korrekt matcht.
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
import { str, oneOf, collectErrors, MAX_TITLE, MAX_SHORT } from '../middleware/validate.js';
const { createLogger } = require('../logger');
const log = createLogger('Shopping');
const express = require('express');
const router = express.Router();
const db = require('../db');
const { str, oneOf, collectErrors, MAX_TITLE, MAX_SHORT } = require('../middleware/validate');
// --------------------------------------------------------
// Konstanten
@@ -286,4 +285,4 @@ router.delete('/:listId/items/checked', (req, res) => {
}
});
module.exports = router;
export default router;
+7 -8
View File
@@ -4,16 +4,15 @@
* Abhängigkeiten: express, server/db.js
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
import * as db from '../db.js';
import { nextOccurrence } from '../services/recurrence.js';
import * as v from '../middleware/validate.js';
const { createLogger } = require('../logger');
const log = createLogger('Tasks');
const express = require('express');
const router = express.Router();
const db = require('../db');
const { nextOccurrence } = require('../services/recurrence');
const v = require('../middleware/validate');
const router = express.Router();
// --------------------------------------------------------
// Konstanten
@@ -321,4 +320,4 @@ router.get('/meta/options', (req, res) => {
}
});
module.exports = router;
export default router;
+3 -4
View File
@@ -4,12 +4,11 @@
* Abhängigkeiten: express, node-fetch, dotenv
*/
'use strict';
import { createLogger } from '../logger.js';
import express from 'express';
const { createLogger } = require('../logger');
const log = createLogger('Weather');
const express = require('express');
const router = express.Router();
// Cache: Daten für 30 Minuten halten
@@ -124,4 +123,4 @@ router.get('/icon/:code', async (req, res) => {
}
});
module.exports = router;
export default router;
+3 -5
View File
@@ -12,12 +12,10 @@
* apple_last_sync - ISO-8601-Timestamp des letzten Syncs
*/
'use strict';
const { createLogger } = require('../logger');
import { createLogger } from '../logger.js';
const log = createLogger('Apple');
const db = require('../db');
import * as db from '../db.js';
const APPLE_COLOR = '#FC3C44';
@@ -407,4 +405,4 @@ async function sync() {
log.info(`Sync abgeschlossen - ${totalObjects} Objekte aus ${syncCalendars.length} Kalendern inbound, ${localEvents.length} lokal → iCloud.`);
}
module.exports = { sync, getStatus, saveCredentials, clearCredentials, testConnection };
export { sync, getStatus, saveCredentials, clearCredentials, testConnection };
+5 -7
View File
@@ -11,14 +11,12 @@
* google_last_sync - ISO-8601-Timestamp des letzten erfolgreichen Syncs
*/
'use strict';
const { createLogger } = require('../logger');
import { createLogger } from '../logger.js';
const log = createLogger('Google');
const { google } = require('googleapis');
const crypto = require('crypto');
const db = require('../db');
import { google } from 'googleapis';
import crypto from 'crypto';
import * as db from '../db.js';
const GOOGLE_COLOR = '#4285F4';
@@ -334,4 +332,4 @@ function localEventToGoogle(event) {
return gEvent;
}
module.exports = { getAuthUrl, handleCallback, getStatus, disconnect, sync };
export { getAuthUrl, handleCallback, getStatus, disconnect, sync };
+1 -3
View File
@@ -5,8 +5,6 @@
* Abhängigkeiten: keine
*/
'use strict';
const DAY_MAP = { MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6, SU: 0 };
/**
@@ -107,4 +105,4 @@ function nextOccurrence(baseDateStr, rrule) {
return next.toISOString().slice(0, 10); // YYYY-MM-DD
}
module.exports = { parseRRule, nextOccurrence };
export { parseRRule, nextOccurrence };
+4 -11
View File
@@ -5,14 +5,10 @@
* Abhängigkeiten: server/db.js, bcrypt, dotenv
*/
'use strict';
require('dotenv').config();
const readline = require('node:readline');
const bcrypt = require('bcrypt');
const db = require('./server/db');
const os = require('node:os');
import readline from 'node:readline';
import bcrypt from 'bcrypt';
import * as db from './server/db.js';
import os from 'node:os';
function getLocalIP() {
const interfaces = os.networkInterfaces();
@@ -67,9 +63,6 @@ function promptPassword(question) {
async function main() {
console.log('\n=== Oikos Setup ===\n');
// Datenbank initialisieren
db.init();
// Prüfen ob bereits Admin vorhanden
const existingAdmin = db.get()
.prepare("SELECT id FROM users WHERE role = 'admin' LIMIT 1")
+2 -4
View File
@@ -5,10 +5,8 @@
* Ausführen: node --experimental-sqlite test-calendar.js
*/
'use strict';
const { DatabaseSync } = require('node:sqlite');
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
import { DatabaseSync } from 'node:sqlite';
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
let passed = 0;
let failed = 0;
+2 -4
View File
@@ -4,10 +4,8 @@
* Ausführen: node --experimental-sqlite test-dashboard.js
*/
'use strict';
const { DatabaseSync } = require('node:sqlite');
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
import { DatabaseSync } from 'node:sqlite';
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
let passed = 0;
let failed = 0;
+2 -4
View File
@@ -7,15 +7,13 @@
* Ausführen: node test-db.js
*/
'use strict';
const { DatabaseSync } = require('node:sqlite');
import { DatabaseSync } from 'node:sqlite';
// --------------------------------------------------------
// Migrations-SQL direkt aus db.js extrahieren
// (Nur für Tests - in Produktion läuft db.js mit better-sqlite3)
// --------------------------------------------------------
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
let passed = 0;
let failed = 0;
+2 -4
View File
@@ -5,10 +5,8 @@
* Ausführen: node --experimental-sqlite test-meals.js
*/
'use strict';
const { DatabaseSync } = require('node:sqlite');
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
import { DatabaseSync } from 'node:sqlite';
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
let passed = 0;
let failed = 0;
+2 -4
View File
@@ -4,10 +4,8 @@
* Ausführen: node --experimental-sqlite test-notes-contacts-budget.js
*/
'use strict';
const { DatabaseSync } = require('node:sqlite');
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
import { DatabaseSync } from 'node:sqlite';
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
let passed = 0;
let failed = 0;
+2 -4
View File
@@ -4,10 +4,8 @@
* Ausführen: node --experimental-sqlite test-shopping.js
*/
'use strict';
const { DatabaseSync } = require('node:sqlite');
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
import { DatabaseSync } from 'node:sqlite';
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
let passed = 0;
let failed = 0;
+2 -4
View File
@@ -4,10 +4,8 @@
* Ausführen: node --experimental-sqlite test-tasks.js
*/
'use strict';
const { DatabaseSync } = require('node:sqlite');
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
import { DatabaseSync } from 'node:sqlite';
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
let passed = 0;
let failed = 0;