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:
2026-07-25 19:33:35 +02:00
co-authored by Claude Opus 5
parent 58c493ddbd
commit c9e09a2762
7 changed files with 473 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
import type { GitProvider, Note, Project, ProjectStatus, RepoSnapshot } from "@prisma/client";
import type { BranchInfo, CommitInfo, ReleaseInfo, TagInfo } from "@/lib/git/types";
/** Verbindung ohne Geheimnisse – so wird sie an den Browser gegeben. */
export interface ConnectionSafe {
id: string;
name: string;
provider: GitProvider;
baseUrl: string;
username: string | null;
tokenHint: string | null;
lastCheckedAt: Date | string | null;
lastCheckOk: boolean | null;
lastCheckMessage: string | null;
createdAt: Date | string;
updatedAt: Date | string;
}
export interface ConnectionWithCount extends ConnectionSafe {
_count?: { projects: number };
}
export interface ProjectListItem extends Project {
connection: ConnectionSafe | null;
snapshot: RepoSnapshot | null;
_count: { notes: number };
}
export interface ProjectDetail extends Project {
connection: ConnectionSafe | null;
snapshot: RepoSnapshot | null;
notes: Note[];
_count: { notes: number };
}
export interface RepoListEntry {
fullPath: string;
owner: string;
name: string;
description: string | null;
htmlUrl: string;
defaultBranch: string | null;
visibility: string | null;
updatedAt: string | null;
}
export type { BranchInfo, CommitInfo, Note, ProjectStatus, ReleaseInfo, RepoSnapshot, TagInfo };
/** Prisma liefert Json als unknown – hier bequem in Arrays zurueckholen. */
export function asArray<T>(value: unknown): T[] {
return Array.isArray(value) ? (value as T[]) : [];
}
export function asRecord(value: unknown): Record<string, number> {
if (value && typeof value === "object" && !Array.isArray(value)) {
return value as Record<string, number>;
}
return {};
}