feat(core): Verschluesselung, Validierung und gemeinsame Helfer
- crypto.ts: AES-256-GCM fuer die Git-Access-Tokens, Schluessel per scrypt aus APP_SECRET abgeleitet. Chiffrat ist versioniert (v1.<iv>.<tag>.<data>), damit ein spaeterer Formatwechsel moeglich bleibt. Fehlt APP_SECRET, warnt die App und faellt auf einen Entwicklungsschluessel zurueck. - validation.ts: Zod-Schemas fuer alle Eingaben an der API-Grenze, inklusive Umwandlung leerer Strings zu null und Feldfehlern fuer die Oberflaeche. - status.ts: Metadaten zu Status, Prioritaeten und Akzentfarben an einer Stelle, damit Board, Karte und Detailseite nicht auseinanderlaufen. - utils.ts: Slug-Erzeugung, deutsche Datumsformate mit fester Zeitzone (verhindert Abweichungen zwischen Server-Render und Hydration) und relative Zeitangaben. - projects.ts: wiederverwendete Prisma-Selects. Die Verbindungsauswahl laesst tokenCipher bewusst weg, damit kein Token in eine Client-Komponente gelangt. - db.ts: Prisma-Client mit Cache ueber globalThis gegen Verbindungslecks beim Hot Reload. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { slugify } from "@/lib/utils";
|
||||
|
||||
/** Erzeugt einen eindeutigen Slug, notfalls mit angehaengtem Zaehler. */
|
||||
export async function uniqueSlug(name: string, ignoreId?: string): Promise<string> {
|
||||
const base = slugify(name) || "projekt";
|
||||
let candidate = base;
|
||||
let counter = 2;
|
||||
|
||||
// In der Praxis endet die Schleife nach wenigen Runden.
|
||||
for (let attempt = 0; attempt < 50; attempt += 1) {
|
||||
const existing = await prisma.project.findUnique({
|
||||
where: { slug: candidate },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing || existing.id === ignoreId) return candidate;
|
||||
candidate = `${base}-${counter}`;
|
||||
counter += 1;
|
||||
}
|
||||
return `${base}-${Date.now()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbindungsfelder, die an den Browser gehen duerfen.
|
||||
* tokenCipher ist bewusst nicht dabei.
|
||||
*/
|
||||
export const connectionSafeSelect = Prisma.validator<Prisma.GitConnectionSelect>()({
|
||||
id: true,
|
||||
name: true,
|
||||
provider: true,
|
||||
baseUrl: true,
|
||||
username: true,
|
||||
tokenHint: true,
|
||||
lastCheckedAt: true,
|
||||
lastCheckOk: true,
|
||||
lastCheckMessage: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
|
||||
export const projectListInclude = Prisma.validator<Prisma.ProjectInclude>()({
|
||||
connection: { select: connectionSafeSelect },
|
||||
snapshot: true,
|
||||
_count: { select: { notes: true } },
|
||||
});
|
||||
|
||||
export const projectDetailInclude = Prisma.validator<Prisma.ProjectInclude>()({
|
||||
connection: { select: connectionSafeSelect },
|
||||
snapshot: true,
|
||||
notes: {
|
||||
orderBy: [{ pinned: "desc" }, { updatedAt: "desc" }],
|
||||
},
|
||||
_count: { select: { notes: true } },
|
||||
});
|
||||
Reference in New Issue
Block a user