diff --git a/.env.example b/.env.example index 08cf4579..c9d83d97 100644 --- a/.env.example +++ b/.env.example @@ -18,14 +18,17 @@ FACTORY_DEFAULTS_DIR=./data/factory-defaults BACKUPS_DIR=./data/backups # ============== DATENBANK ============== +DB_HOST=localhost # Im Container überschreibt docker-compose das auf "db" DB_NAME=opencrm DB_USER=opencrm DB_PASSWORD=change-this-password DB_ROOT_PASSWORD=change-this-root-password -# Connection-String fürs Backend (im Container = Service-Name `db`) -# Im Dev-Modus (npm run dev) lokal: localhost:DB_PORT -DATABASE_URL=mysql://root:change-this-root-password@localhost:3306/opencrm +# Connection-String wird aus den DB_*-Komponenten zusammengebaut (dotenv-expand). +# Manuell überschreiben nur wenn Sonderfälle (z.B. extra Query-Parameter). +# Hinweis: für lokales Dev mit MariaDB im Container nutze DB_HOST=localhost, +# weil docker-compose den DB-Port auf 127.0.0.1:DB_PORT mappt. +DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} # ============== SECURITY ============== # JWT-Secret: min. 32 Zeichen. Generieren: openssl rand -hex 64 diff --git a/backend/package-lock.json b/backend/package-lock.json index b67d2a83..b03a316e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "opencrm-backend", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencrm-backend", - "version": "1.0.0", + "version": "1.1.0", "dependencies": { "@prisma/client": "^5.22.0", "adm-zip": "^0.5.16", @@ -14,6 +14,7 @@ "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", + "dotenv-expand": "^13.0.0", "express": "^4.21.1", "express-rate-limit": "^8.4.0", "express-validator": "^7.2.0", @@ -1463,6 +1464,33 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-13.0.0.tgz", + "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^17.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/backend/package.json b/backend/package.json index fab5e624..db1b01e0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,6 +25,7 @@ "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", + "dotenv-expand": "^13.0.0", "express": "^4.21.1", "express-rate-limit": "^8.4.0", "express-validator": "^7.2.0", diff --git a/backend/src/index.ts b/backend/src/index.ts index 295a9cc4..def4be12 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -3,6 +3,7 @@ import cors from 'cors'; import helmet from 'helmet'; import path from 'path'; import dotenv from 'dotenv'; +import dotenvExpand from 'dotenv-expand'; // .env-Dateien laden – Root-.env hat Priorität (zentrale Konfiguration für // Dev + Docker), backend/.env als Legacy-Fallback. Im Container sind @@ -10,9 +11,23 @@ import dotenv from 'dotenv'; // existierende process.env-Werte nicht. // __dirname zeigt auf src/ (dev via tsx) oder dist/ (build). In beiden Fällen // liegt Root /.env zwei Ebenen darüber. -dotenv.config({ path: path.resolve(__dirname, '../../.env') }); // Root /.env -dotenv.config({ path: path.resolve(__dirname, '../.env') }); // backend/.env (Fallback) -dotenv.config(); // cwd-relativ (Container etc.) +// +// dotenvExpand löst ${VAR}-Substitution auf, sodass z.B. +// DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} +// dynamisch aus den Komponenten zusammengebaut wird (kein Doppel-Pflegen). +dotenvExpand.expand(dotenv.config({ path: path.resolve(__dirname, '../../.env') })); +dotenvExpand.expand(dotenv.config({ path: path.resolve(__dirname, '../.env') })); +dotenvExpand.expand(dotenv.config()); + +// Fallback: wenn DATABASE_URL nicht direkt gesetzt ist (oder Substitution +// nicht funktioniert hat), aus den DB_*-Komponenten zusammenbauen. +if (!process.env.DATABASE_URL && process.env.DB_USER && process.env.DB_PASSWORD && process.env.DB_NAME) { + const u = encodeURIComponent(process.env.DB_USER); + const p = encodeURIComponent(process.env.DB_PASSWORD); + const h = process.env.DB_HOST || 'localhost'; + const port = process.env.DB_PORT || '3306'; + process.env.DATABASE_URL = `mysql://${u}:${p}@${h}:${port}/${process.env.DB_NAME}`; +} import authRoutes from './routes/auth.routes.js'; import customerRoutes from './routes/customer.routes.js';