chore: Projektgeruest mit Next.js 16, Prisma 7 und pnpm (#001)

Konfiguration aus Laufzettel uebernommen, damit alle Projekte gleich
gebaut und geprueft werden. Entwicklungsdatenbank auf 5436, weil 5433
und 5434 von ProxLedger und Laufzettel belegt sind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 09:05:15 +02:00
co-authored by Claude Opus 5.5
parent cd83542482
commit cef9510f18
29 changed files with 8127 additions and 9 deletions
+7
View File
@@ -0,0 +1,7 @@
import { NextResponse } from "next/server";
import { VERSION } from "@/lib/version";
/** Healthcheck fuer Docker und Reverse Proxy. Verraet nichts ausser der Version. */
export function GET() {
return NextResponse.json({ status: "ok", version: VERSION });
}
+1
View File
@@ -0,0 +1 @@
@import "tailwindcss";
+16
View File
@@ -0,0 +1,16 @@
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";
export const metadata: Metadata = {
title: { default: "SSHKeyVault", template: "%s · SSHKeyVault" },
description: "Zentrale Verwaltung von SSH-Schlüsseln",
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="de">
<body>{children}</body>
</html>
);
}
+3
View File
@@ -0,0 +1,3 @@
export default function Startseite() {
return <main>SSHKeyVault</main>;
}
+39
View File
@@ -0,0 +1,39 @@
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@/generated/prisma/client";
/**
* Zugriff auf die Datenbank.
*
* Der Client wird erst beim ersten Zugriff gebaut. Dadurch laeuft `next build`
* ohne gesetzte DATABASE_URL durch, obwohl Module den Import auf oberster Ebene
* haben. In der Entwicklung liegt die Instanz an globalThis, damit Hot Reload
* nicht bei jedem Speichern eine neue Verbindung aufbaut.
*/
function clientBauen(): PrismaClient {
const url = process.env["DATABASE_URL"];
if (!url) {
throw new Error("DATABASE_URL ist nicht gesetzt. Siehe .env.example.");
}
return new PrismaClient({ adapter: new PrismaPg({ connectionString: url }) });
}
const globalerZwischenspeicher = globalThis as typeof globalThis & {
sshkvPrisma?: PrismaClient;
};
let instanz: PrismaClient | undefined;
function instanzHolen(): PrismaClient {
if (process.env["NODE_ENV"] !== "production") {
globalerZwischenspeicher.sshkvPrisma ??= clientBauen();
return globalerZwischenspeicher.sshkvPrisma;
}
instanz ??= clientBauen();
return instanz;
}
export const prisma = new Proxy({} as PrismaClient, {
get(_ziel, eigenschaft, empfaenger) {
return Reflect.get(instanzHolen(), eigenschaft, empfaenger);
},
});
+23
View File
@@ -0,0 +1,23 @@
import { z } from "zod";
/**
* Zod mit deutschen Standardmeldungen (#001, uebernommen aus Laufzettel).
*
* Ohne diese Einstellung kommen bei fehlenden oder falsch typisierten Feldern
* englische Saetze heraus („Invalid input: expected string“), und die landen
* ueber die Server Actions unmittelbar in der Oberflaeche. Eigene `message`-Texte
* helfen nur dort, wo man an sie gedacht hat — die Lokalisierung deckt auch alles
* ab, woran niemand gedacht hat.
*
* Deshalb wird `z` **hier** eingestellt und von hier weitergereicht: ein Import
* direkt aus "zod" umginge die Einstellung. Die ESLint-Regel
* `no-restricted-imports` sorgt dafuer, dass das auffaellt.
*/
z.config(z.locales.de());
export { z };
// Was sonst noch aus Zod gebraucht wird, laeuft ebenfalls ueber diesen Pfad –
// sonst muesste doch wieder jemand direkt aus "zod" importieren.
export { ZodError } from "zod";
export type { ZodIssue, ZodType } from "zod";
+6
View File
@@ -0,0 +1,6 @@
import paket from "../../package.json" with { type: "json" };
/** Version aus package.json – erscheint in der Seitenleiste und im Healthcheck. */
export const VERSION: string = paket.version;
export const KURZNAME = "sshkv";
export const PRODUKTNAME = "SSHKeyVault";