refactor(quick-260921-m34): Aufgabe 3a - 18 Fehlerfaenger auf unknown, mit echter Eingrenzung

catch (e: any) in groups, module-grants, ldap, user, admin-seed, calendar
und vier tenders-Diensten auf catch (e: unknown) umgestellt. Die
Eingrenzung passiert an der Verwendungsstelle, nicht per Zusicherung.

Neu: apps/api/src/prisma/prisma-error.ts mit prismaErrorCode() und
prismaErrorTarget(). Bewusst Form-Pruefungen statt instanceof
Prisma.PrismaClientKnownRequestError - gemessen: samtliche Testdoppel in
apps/api werfen new Error(...) mit angehaengtem .code (groups, user, ldap,
tenders, module-grants, admin-seed) und dashboard.service.spec.ts:451 ein
reines { code: 'P2002' }. Ein instanceof-Test haette all diese Werte in den
anderen Zweig geschickt - Verhaltensaenderung, verboten nach D-03/T-M34-06.
Die Helfer bilden err?.code und err?.meta?.target eins zu eins ab.

ldap.service.ts liest zusaetzlich meta.target; prismaErrorTarget() gibt
unknown zurueck, weil der Bestand dort Array UND Zeichenkette getrennt
behandelt - ein engerer Typ waere eine Behauptung.

calendar.service.ts:341 nutzt instanceof Error statt e?.message: gemessen
wirft validateUrlNotPrivate() ausschliesslich ForbiddenException (der
eigene catch dort setzt jeden Fremdfehler in eine um), also trifft
instanceof dieselben Faelle. Ersatzzweig 'URL not allowed' unveraendert.

noExplicitAny in apps/api/src: 56 -> 38. type-check 4/4, lint 5/5 (0
error), apps/api 72/1143, apps/web 73/531, rls-access-inventory 30/30.
noNonNullAssertion 56, as unknown as 33, Unterdrueckungsmarker 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 17:18:12 +02:00
parent 52668c2c88
commit 32591b6690
11 changed files with 103 additions and 38 deletions
+11 -10
View File
@@ -7,6 +7,7 @@ import {
import { MembershipSource } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { forTenant, withTenantTransaction } from '../prisma/prisma-tenant.extension';
import { prismaErrorCode } from '../prisma/prisma-error';
/**
* Name der automatisch angelegten Standardgruppe (D-13). Geteilte Wahrheit
@@ -91,8 +92,8 @@ export class GroupsService {
return await tenantPrisma.group.create({
data: { tenantId, name },
});
} catch (err: any) {
if (err?.code === 'P2002') {
} catch (err: unknown) {
if (prismaErrorCode(err) === 'P2002') {
throw new ConflictException(
`Eine Gruppe mit dem Namen '${name}' existiert bereits in diesem Mandanten`,
);
@@ -204,8 +205,8 @@ export class GroupsService {
where: { id },
data: updateData,
});
} catch (err: any) {
if (err?.code === 'P2002') {
} catch (err: unknown) {
if (prismaErrorCode(err) === 'P2002') {
throw new ConflictException(
`Eine Gruppe mit dem Namen '${updateData.name}' existiert bereits in diesem Mandanten`,
);
@@ -244,8 +245,8 @@ export class GroupsService {
const tenantPrisma = forTenant(this.prisma, tenantId);
try {
return await tenantPrisma.group.delete({ where: { id } });
} catch (err: any) {
if (err?.code === 'P2025') {
} catch (err: unknown) {
if (prismaErrorCode(err) === 'P2025') {
throw new NotFoundException(`Gruppe '${id}' nicht gefunden`);
}
throw err;
@@ -398,8 +399,8 @@ export class GroupsService {
return group;
});
} catch (err: any) {
if (err?.code === 'P2002') {
} catch (err: unknown) {
if (prismaErrorCode(err) === 'P2002') {
return null;
}
throw err;
@@ -468,8 +469,8 @@ export class GroupsService {
});
});
return true;
} catch (err: any) {
if (err?.code === 'P2002') {
} catch (err: unknown) {
if (prismaErrorCode(err) === 'P2002') {
return false;
}
throw err;
+3 -2
View File
@@ -6,6 +6,7 @@ import {
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { forTenant } from '../prisma/prisma-tenant.extension';
import { prismaErrorCode } from '../prisma/prisma-error';
/**
* Schreibseite der Modul-Freigaben (PERM-03): Grants für Gruppen und für
@@ -132,8 +133,8 @@ export class ModuleGrantsService {
`Grant erteilt: tenant=${tenantId} module=${moduleId} ${target}`,
);
return created;
} catch (err: any) {
if (err?.code === 'P2002') {
} catch (err: unknown) {
if (prismaErrorCode(err) === 'P2002') {
const existing = await tenantPrisma.moduleGrant.findFirst({
where: {
tenantId,