From b139eea623dcc287268f99a7315fd7c3b03366bb Mon Sep 17 00:00:00 2001 From: Ulas Date: Fri, 3 Apr 2026 23:11:20 +0200 Subject: [PATCH] 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. --- package.json | 7 ++-- server/index.js | 63 +++++++++++++++--------------- server/middleware/csrf.js | 6 +-- server/middleware/validate.js | 4 +- server/routes/budget.js | 11 +++--- server/routes/calendar.js | 20 +++++----- server/routes/contacts.js | 11 +++--- server/routes/dashboard.js | 9 ++--- server/routes/meals.js | 11 +++--- server/routes/notes.js | 11 +++--- server/routes/shopping.js | 11 +++--- server/routes/tasks.js | 15 ++++--- server/routes/weather.js | 7 ++-- server/services/apple-calendar.js | 8 ++-- server/services/google-calendar.js | 12 +++--- server/services/recurrence.js | 4 +- setup.js | 15 ++----- test-calendar.js | 6 +-- test-dashboard.js | 6 +-- test-db.js | 6 +-- test-meals.js | 6 +-- test-notes-contacts-budget.js | 6 +-- test-shopping.js | 6 +-- test-tasks.js | 6 +-- 24 files changed, 113 insertions(+), 154 deletions(-) diff --git a/package.json b/package.json index 7b15cc8..5859309 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/index.js b/server/index.js index 7a23030..ac6e2f8 100644 --- a/server/index.js +++ b/server/index.js @@ -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; diff --git a/server/middleware/csrf.js b/server/middleware/csrf.js index 0724d98..f9a3995 100644 --- a/server/middleware/csrf.js +++ b/server/middleware/csrf.js @@ -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 }; diff --git a/server/middleware/validate.js b/server/middleware/validate.js index 838f3f1..dc6e861 100644 --- a/server/middleware/validate.js +++ b/server/middleware/validate.js @@ -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, diff --git a/server/routes/budget.js b/server/routes/budget.js index e71d495..0e026e4 100644 --- a/server/routes/budget.js +++ b/server/routes/budget.js @@ -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; diff --git a/server/routes/calendar.js b/server/routes/calendar.js index a875d05..587cf04 100644 --- a/server/routes/calendar.js +++ b/server/routes/calendar.js @@ -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; diff --git a/server/routes/contacts.js b/server/routes/contacts.js index 3e8547e..8d7f1b3 100644 --- a/server/routes/contacts.js +++ b/server/routes/contacts.js @@ -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; diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js index 834c38b..7f81777 100644 --- a/server/routes/dashboard.js +++ b/server/routes/dashboard.js @@ -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; diff --git a/server/routes/meals.js b/server/routes/meals.js index 6d88ed4..01ee1e3 100644 --- a/server/routes/meals.js +++ b/server/routes/meals.js @@ -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; diff --git a/server/routes/notes.js b/server/routes/notes.js index cceafb6..76c01af 100644 --- a/server/routes/notes.js +++ b/server/routes/notes.js @@ -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; diff --git a/server/routes/shopping.js b/server/routes/shopping.js index aff0ffa..18b4fac 100644 --- a/server/routes/shopping.js +++ b/server/routes/shopping.js @@ -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; diff --git a/server/routes/tasks.js b/server/routes/tasks.js index 1377c80..6492e87 100644 --- a/server/routes/tasks.js +++ b/server/routes/tasks.js @@ -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; diff --git a/server/routes/weather.js b/server/routes/weather.js index 301412e..b1b3aa1 100644 --- a/server/routes/weather.js +++ b/server/routes/weather.js @@ -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; diff --git a/server/services/apple-calendar.js b/server/services/apple-calendar.js index 8bf5ae0..928e7cd 100644 --- a/server/services/apple-calendar.js +++ b/server/services/apple-calendar.js @@ -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 }; diff --git a/server/services/google-calendar.js b/server/services/google-calendar.js index ab005a7..d9c8750 100644 --- a/server/services/google-calendar.js +++ b/server/services/google-calendar.js @@ -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 }; diff --git a/server/services/recurrence.js b/server/services/recurrence.js index a4ae8fb..db3f5c4 100644 --- a/server/services/recurrence.js +++ b/server/services/recurrence.js @@ -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 }; diff --git a/setup.js b/setup.js index 1645e9b..28b59bb 100644 --- a/setup.js +++ b/setup.js @@ -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") diff --git a/test-calendar.js b/test-calendar.js index 8041034..e68122d 100644 --- a/test-calendar.js +++ b/test-calendar.js @@ -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; diff --git a/test-dashboard.js b/test-dashboard.js index 22d07ca..f028191 100644 --- a/test-dashboard.js +++ b/test-dashboard.js @@ -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; diff --git a/test-db.js b/test-db.js index cd35edd..ac11f83 100644 --- a/test-db.js +++ b/test-db.js @@ -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; diff --git a/test-meals.js b/test-meals.js index 678d524..103f9a5 100644 --- a/test-meals.js +++ b/test-meals.js @@ -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; diff --git a/test-notes-contacts-budget.js b/test-notes-contacts-budget.js index 1a68a00..0e14785 100644 --- a/test-notes-contacts-budget.js +++ b/test-notes-contacts-budget.js @@ -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; diff --git a/test-shopping.js b/test-shopping.js index 4841f35..2ad801a 100644 --- a/test-shopping.js +++ b/test-shopping.js @@ -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; diff --git a/test-tasks.js b/test-tasks.js index ae7e481..eff8f80 100644 --- a/test-tasks.js +++ b/test-tasks.js @@ -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;