Przeglądaj źródła

feat: add MongoDB integration, health check API and Docker integration

Code_Uwe 2 tygodni temu
rodzic
commit
b602c12753
6 zmienionych plików z 256 dodań i 1 usunięć
  1. 24 0
      Dockerfile
  2. 35 0
      app/api/health/route.js
  3. 28 0
      docker-compose.yml
  4. 26 0
      lib/db.js
  5. 142 1
      package-lock.json
  6. 1 0
      package.json

+ 24 - 0
Dockerfile

@@ -0,0 +1,24 @@
+# Basis: Node 22 (erfüllt Next.js 16 Node-Anforderungen)
+FROM node:22-slim
+
+# Arbeitsverzeichnis im Container
+WORKDIR /app
+
+# Nur package-Dateien kopieren und Dependencies installieren
+COPY package.json package-lock.json* ./
+RUN npm install
+
+# Restlichen Quellcode kopieren
+COPY . .
+
+# Build der Next.js-App (Production-Build)
+RUN npm run build
+
+# Umgebungsvariablen & Port
+ENV NODE_ENV=production
+ENV PORT=3000
+
+EXPOSE 3000
+
+# Start der App im Produktionsmodus
+CMD ["npm", "start"]

+ 35 - 0
app/api/health/route.js

@@ -0,0 +1,35 @@
+// app/api/health/route.js
+import { NextResponse } from "next/server";
+import { getDb } from "@/lib/db";
+import fs from "fs/promises";
+
+export async function GET() {
+	const result = {
+		db: null,
+		nas: null,
+	};
+
+	// DB-Check
+	try {
+		const db = await getDb();
+		await db.command({ ping: 1 });
+		result.db = "ok";
+	} catch (error) {
+		result.db = `error: ${error.message}`;
+	}
+
+	// NAS-Check
+	const nasPath = process.env.NAS_ROOT_PATH || "/mnt/niederlassungen";
+	try {
+		const entries = await fs.readdir(nasPath);
+		result.nas = {
+			path: nasPath,
+			entriesSample: entries.slice(0, 5),
+		};
+	} catch (error) {
+		// Lokal auf dem Mac ist der Pfad wahrscheinlich leer oder existiert nicht → Fehler ist ok
+		result.nas = `error: ${error.message}`;
+	}
+
+	return NextResponse.json(result);
+}

+ 28 - 0
docker-compose.yml

@@ -0,0 +1,28 @@
+services:
+  db:
+    image: mongo:7
+    container_name: rhl-lieferscheine-db
+    restart: unless-stopped
+    environment:
+      MONGO_INITDB_ROOT_USERNAME: root
+      MONGO_INITDB_ROOT_PASSWORD: supersecret
+    volumes:
+      - db_data:/data/db
+    ports:
+      - "27017:27017"
+
+  app:
+    build: .
+    container_name: rhl-lieferscheine-app
+    restart: unless-stopped
+    env_file:
+      - .env.docker
+    depends_on:
+      - db
+    ports:
+      - "3000:3000"
+    volumes:
+      - /mnt/niederlassungen:/mnt/niederlassungen:ro
+
+volumes:
+  db_data:

+ 26 - 0
lib/db.js

@@ -0,0 +1,26 @@
+// lib/db.js
+import { MongoClient } from "mongodb";
+
+const uri = process.env.MONGODB_URI;
+
+if (!uri) {
+	throw new Error("Bitte setze MONGODB_URI in .env.docker / .env.local");
+}
+
+// Wir cachen die Verbindung über globalThis, damit Next.js nicht bei jedem Request
+// eine neue Verbindung aufmacht (wichtig im Dev & bei Hot Reload).
+let client;
+let clientPromise;
+
+if (!global._mongoClientPromise) {
+	client = new MongoClient(uri);
+	global._mongoClientPromise = client.connect();
+}
+
+clientPromise = global._mongoClientPromise;
+
+export async function getDb() {
+	const client = await clientPromise;
+	// Wenn im URI ein DB-Name steht (/rhl-lieferscheine), nimmt der Client den automatisch.
+	return client.db();
+}

+ 142 - 1
package-lock.json

@@ -13,6 +13,7 @@
         "class-variance-authority": "^0.7.1",
         "clsx": "^2.1.1",
         "lucide-react": "^0.555.0",
+        "mongodb": "^7.0.0",
         "next": "16.0.7",
         "next-themes": "^0.4.6",
         "react": "19.2.0",
@@ -1063,6 +1064,15 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@mongodb-js/saslprep": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
+      "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
+      "license": "MIT",
+      "dependencies": {
+        "sparse-bitfield": "^3.0.3"
+      }
+    },
     "node_modules/@napi-rs/wasm-runtime": {
       "version": "0.2.12",
       "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@@ -2174,6 +2184,21 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/webidl-conversions": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+      "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/whatwg-url": {
+      "version": "13.0.0",
+      "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
+      "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/webidl-conversions": "*"
+      }
+    },
     "node_modules/@typescript-eslint/eslint-plugin": {
       "version": "8.48.1",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz",
@@ -3086,6 +3111,15 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/bson": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz",
+      "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=20.19.0"
+      }
+    },
     "node_modules/call-bind": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -5511,6 +5545,12 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "license": "MIT"
+    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -5558,6 +5598,65 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/mongodb": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz",
+      "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@mongodb-js/saslprep": "^1.3.0",
+        "bson": "^7.0.0",
+        "mongodb-connection-string-url": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=20.19.0"
+      },
+      "peerDependencies": {
+        "@aws-sdk/credential-providers": "^3.806.0",
+        "@mongodb-js/zstd": "^7.0.0",
+        "gcp-metadata": "^7.0.1",
+        "kerberos": "^7.0.0",
+        "mongodb-client-encryption": ">=7.0.0 <7.1.0",
+        "snappy": "^7.3.2",
+        "socks": "^2.8.6"
+      },
+      "peerDependenciesMeta": {
+        "@aws-sdk/credential-providers": {
+          "optional": true
+        },
+        "@mongodb-js/zstd": {
+          "optional": true
+        },
+        "gcp-metadata": {
+          "optional": true
+        },
+        "kerberos": {
+          "optional": true
+        },
+        "mongodb-client-encryption": {
+          "optional": true
+        },
+        "snappy": {
+          "optional": true
+        },
+        "socks": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/mongodb-connection-string-url": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz",
+      "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@types/whatwg-url": "^13.0.0",
+        "whatwg-url": "^14.1.0"
+      },
+      "engines": {
+        "node": ">=20.19.0"
+      }
+    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6018,7 +6117,6 @@
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
       "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
@@ -6548,6 +6646,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+      "license": "MIT",
+      "dependencies": {
+        "memory-pager": "^1.0.2"
+      }
+    },
     "node_modules/stable-hash": {
       "version": "0.0.5",
       "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -6846,6 +6953,18 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/tr46": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+      "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+      "license": "MIT",
+      "dependencies": {
+        "punycode": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/ts-api-utils": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -7169,6 +7288,28 @@
         }
       }
     },
+    "node_modules/webidl-conversions": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/whatwg-url": {
+      "version": "14.2.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+      "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "^5.1.0",
+        "webidl-conversions": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/which": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
     "lucide-react": "^0.555.0",
+    "mongodb": "^7.0.0",
     "next": "16.0.7",
     "next-themes": "^0.4.6",
     "react": "19.2.0",