feat: Phase 1 — Projektstruktur, DB-Schema, Auth-System
- Vollständige Verzeichnisstruktur gemäß CLAUDE.md - Express-Server mit Helmet, Sessions, Rate Limiting, SPA-Fallback - SQLite-Schema (Migration v1): 10 Tabellen, updated_at-Triggers, Indizes - Versioniertes Migrations-System (schema_migrations) - Auth-Routen: Login, Logout, /me, Admin-User-CRUD - Frontend App-Shell: SPA-Router, API-Client, Design-System (CSS Tokens) - PWA: Service Worker, Web App Manifest - Setup-Script für ersten Admin-User (node setup.js) - DB-Tests mit node:sqlite built-in: 29/29 bestanden - Docker Compose + Dockerfile + Nginx-Beispielkonfiguration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Modul: Server Entry Point
|
||||
* Zweck: Express-App initialisieren, Middleware einbinden, Routen registrieren
|
||||
* 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 path = require('path');
|
||||
const db = require('./db');
|
||||
const { router: authRouter, sessionMiddleware, requireAuth } = require('./auth');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Datenbank initialisieren
|
||||
// --------------------------------------------------------
|
||||
db.init();
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Security-Middleware
|
||||
// --------------------------------------------------------
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: [
|
||||
"'self'",
|
||||
// Alpine.js CDN
|
||||
'https://cdn.jsdelivr.net',
|
||||
// Lucide Icons CDN
|
||||
'https://unpkg.com',
|
||||
],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", 'data:', 'https://openweathermap.org'],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'"],
|
||||
objectSrc: ["'none'"],
|
||||
frameSrc: ["'none'"],
|
||||
},
|
||||
},
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true,
|
||||
},
|
||||
}));
|
||||
|
||||
// Trust Proxy für korrekte IP hinter Nginx
|
||||
app.set('trust proxy', 1);
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Request-Parsing
|
||||
// --------------------------------------------------------
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '1mb' }));
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Sessions
|
||||
// --------------------------------------------------------
|
||||
app.use(sessionMiddleware);
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Statische Dateien (Frontend)
|
||||
// --------------------------------------------------------
|
||||
app.use(express.static(path.join(__dirname, '..', 'public'), {
|
||||
maxAge: process.env.NODE_ENV === 'production' ? '7d' : 0,
|
||||
etag: true,
|
||||
}));
|
||||
|
||||
// --------------------------------------------------------
|
||||
// API-Routen
|
||||
// --------------------------------------------------------
|
||||
app.use('/api/v1/auth', authRouter);
|
||||
|
||||
// Alle weiteren API-Routen erfordern Authentifizierung
|
||||
app.use('/api/v1', requireAuth);
|
||||
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'));
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Health-Check (für Docker)
|
||||
// --------------------------------------------------------
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// --------------------------------------------------------
|
||||
// SPA Fallback: Alle nicht-API-Routen → index.html
|
||||
// --------------------------------------------------------
|
||||
app.get('*', (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'));
|
||||
});
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Globaler Error-Handler
|
||||
// --------------------------------------------------------
|
||||
app.use((err, req, res, _next) => {
|
||||
console.error('[Server] Unbehandelter Fehler:', err);
|
||||
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
|
||||
});
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Server starten
|
||||
// --------------------------------------------------------
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[Oikos] Server läuft auf Port ${PORT}`);
|
||||
console.log(`[Oikos] Umgebung: ${process.env.NODE_ENV || 'development'}`);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
Reference in New Issue
Block a user