From b188946e318f6a1be39efc02fbf65eabcb58f5d6 Mon Sep 17 00:00:00 2001 From: Schalli Date: Mon, 21 Sep 2026 16:19:16 +0200 Subject: [PATCH] refactor(quick-260921-m34): Aufgabe 1 - Mandantenbindung entzaubert, 105 unnoetige any-Zusicherungen entfernt - prisma-tenant.extension.ts: (prisma as any) und die Handannotation an $allOperations in forTenant()/forSystem() entfernt; Kopfkommentar unveraendert. .then((results: any[]) => ...) auf unknown[] umgestellt. - 105 Aufrufstellen `const X = forTenant(...) as any` / `forSystem(...) as any` von der Zusicherung befreit, Zuweisungsform woertlich erhalten (rls-access-inventory.spec.ts bleibt scharf, 30/30 gruen einzeln geprueft). - withTenantTransaction(): Prisma.TransactionClient fuer tx probiert, gemessen verworfen - bricht das Testdoppel in prisma-tenant.extension.spec.ts (TS2322 auf einem absichtlich unvollstaendigen Fake-Objekt). tx bleibt any, mit Begruendung am Typ. - Gefolge des jetzt getypten Klienten entfernt: any[]-Annotationen und .map((x: any) => ...) in groups.service.ts, module-grants.service.ts, dkv.service.ts, ldap-config.service.ts, tenders.controller.ts:270. - Befund (D-03): tender-matching.service.ts:159 trug eine Handannotation (match: { tender: unknown }), die den Wert nur deshalb auf unknown verengte, um TS7006 unter dem alten any-Klienten zu vermeiden - mit dem getypten Klienten war das falsch. Annotation geloescht, kein Ersatz durch Zusicherung. - Zwei any bleiben gezielt in groups.service.ts (u/a in ensureDefaultGroup(), gefolge von tx: any) - Begruendung am Code. noExplicitAny apps/api/src: 288 -> 149 (Schranke 155). type-check 4/4, lint 5/5 (0 error). apps/api 72/1143 gruen, apps/web 73/531 gruen, rls-access-inventory.spec.ts 30/30 gruen. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J --- apps/api/src/auth/auth.service.ts | 12 ++--- .../src/bug-reports/bug-reports.service.ts | 2 +- apps/api/src/dkv/dkv.service.ts | 32 ++++++------- apps/api/src/favorites/favorites.service.ts | 12 ++--- apps/api/src/groups/groups.service.ts | 47 +++++++++---------- apps/api/src/groups/module-grants.service.ts | 34 +++++++------- apps/api/src/ldap/ldap-config.service.ts | 18 +++---- apps/api/src/ldap/ldap.service.ts | 16 +++---- .../module-registry/module-access.service.ts | 4 +- .../module-registry.service.ts | 8 ++-- .../api/src/prisma/prisma-tenant.extension.ts | 28 ++++++----- apps/api/src/settings/settings.service.ts | 8 ++-- apps/api/src/tenant/tenant.controller.ts | 6 +-- .../src/tenders/tender-digest.scheduler.ts | 4 +- .../tenders/tender-email-config.service.ts | 6 +-- .../src/tenders/tender-matching.service.ts | 8 ++-- .../tender-notification-pref.service.ts | 4 +- .../src/tenders/tender-rss-feed.service.ts | 4 +- .../tenders/tender-saved-search.service.ts | 8 ++-- apps/api/src/tenders/tender-triage.service.ts | 6 +-- apps/api/src/tenders/tenders.controller.ts | 2 +- apps/api/src/user/admin-seed.service.ts | 2 +- apps/api/src/user/user.controller.ts | 10 ++-- apps/api/src/user/user.service.ts | 14 +++--- 24 files changed, 147 insertions(+), 148 deletions(-) diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index ab5301e..405e073 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -115,7 +115,7 @@ export class AuthService { return null; } - const tenantPrisma = forTenant(this.prisma, user.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, user.tenantId); // LDAP users have no local password — authenticate them against the // directory by binding as their OWN DN with the password they entered. @@ -233,7 +233,7 @@ export class AuthService { // Create the reset token record — mandantengebunden, sobald der // Benutzer und damit sein Mandant bekannt sind (WINDOWS #20, Aufgabe 1). - const tenantPrisma = forTenant(this.prisma, user.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, user.tenantId); await tenantPrisma.passwordResetToken.create({ data: { token, @@ -274,7 +274,7 @@ export class AuthService { // Mandant ist ab hier bekannt (aus der Funktion mitgeliefert) — beide // Schreibzugriffe laufen gebunden (WINDOWS #20, Aufgabe 1). - const tenantPrisma = forTenant(this.prisma, resetToken.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, resetToken.tenantId); // Hash the new password and update user const passwordHash = await argon2.hash(newPassword); @@ -308,7 +308,7 @@ export class AuthService { * Zeile. */ async getMe(tenantId: string, userId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const user = await tenantPrisma.user.findUnique({ where: { id: userId }, select: { @@ -353,7 +353,7 @@ export class AuthService { newPassword: string, response: Response, ): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const user = await tenantPrisma.user.findUnique({ where: { id: userId }, }); @@ -417,7 +417,7 @@ export class AuthService { newPassword: string, mustChangePassword: boolean = true, ): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const user = await tenantPrisma.user.findUnique({ where: { id: userId }, }); diff --git a/apps/api/src/bug-reports/bug-reports.service.ts b/apps/api/src/bug-reports/bug-reports.service.ts index a11bfdb..912e431 100644 --- a/apps/api/src/bug-reports/bug-reports.service.ts +++ b/apps/api/src/bug-reports/bug-reports.service.ts @@ -118,7 +118,7 @@ export class BugReportsService { // (4) Benutzerzeile: gebunden an den Sitzungs-Mandanten, nie an Rumpfdaten // (T-M97-06; Zeile in docs/mandantentrennung-zugriffsklassifikation.md). - const tenantPrisma = forTenant(this.prisma, user.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, user.tenantId); const row = await tenantPrisma.user.findUnique({ where: { id: user.id }, select: { username: true, displayName: true, email: true, role: true }, diff --git a/apps/api/src/dkv/dkv.service.ts b/apps/api/src/dkv/dkv.service.ts index 77b06ed..cf0bdc4 100644 --- a/apps/api/src/dkv/dkv.service.ts +++ b/apps/api/src/dkv/dkv.service.ts @@ -118,7 +118,7 @@ export class DkvService { * 260914-eym systemgebunden, eine Zeile je aktivem Mandanten). */ async loadConfig(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.dkvModuleConfig.findUnique({ where: { tenantId }, select: CONFIG_SAFE_SELECT, @@ -153,7 +153,7 @@ export class DkvService { * (T-07-12) — der Planer braucht nur tenantId und pollIntervalMin. */ async loadActiveConfigsForScheduler() { - const systemPrisma = forSystem(this.prisma) as any; + const systemPrisma = forSystem(this.prisma); return systemPrisma.dkvModuleConfig.findMany({ where: { isActive: true }, select: CONFIG_SAFE_SELECT, @@ -171,7 +171,7 @@ export class DkvService { * Methode). */ async getConfigForApi(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const safe = await tenantPrisma.dkvModuleConfig.findUnique({ where: { tenantId }, select: CONFIG_SAFE_SELECT, @@ -214,7 +214,7 @@ export class DkvService { * Bindung als zweite Schicht bestehen. */ async saveConfig(tenantId: string, dto: DkvConfigDto) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); let encryptedInboxCreds: string | undefined; const credChanged = (dto.password && dto.password.length > 0) || @@ -277,7 +277,7 @@ export class DkvService { * Rueckgriff auf die gespeicherten Zugangsdaten. */ async testConnection(tenantId: string, dto: DkvConfigDto): Promise<{ success: boolean; message?: string }> { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); let password: string | undefined = dto.password; // If no password in DTO, fall back to the stored one @@ -362,7 +362,7 @@ export class DkvService { private async _runPipeline(tenantId: string): Promise { // Load raw config (need encryptedInboxCreds for decryption). // Mandantengebunden (260909-mir). - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const config = await tenantPrisma.dkvModuleConfig.findUnique({ where: { tenantId } }); if (!config) { this.logger.warn(`DKV processInbox: no config for tenant ${tenantId}`); @@ -448,7 +448,7 @@ export class DkvService { // Mandantengebunden (260909-mir): EIN gebundener Klient fuer beide // dkvInvoiceHistory.create()-Aufrufe dieser Methode (Erfolgsfall UND // Zerlegungsfehler-Fall). - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); // D-10: Up to 3 parse retries let parseResult: Awaited> | null = null; @@ -546,7 +546,7 @@ export class DkvService { // ─── Vehicle CRUD ──────────────────────────────────────────────────────────── async listVehicles(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.dkvVehicleMaster.findMany({ where: { tenantId }, orderBy: { kennzeichen: 'asc' }, @@ -554,7 +554,7 @@ export class DkvService { } async createVehicle(tenantId: string, dto: CreateVehicleDto) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.dkvVehicleMaster.create({ data: { tenantId, ...dto }, }); @@ -571,7 +571,7 @@ export class DkvService { * Schreibzugriff dahinter waere genau die Luecke, nicht die Loesung. */ async updateVehicle(tenantId: string, id: string, dto: UpdateVehicleDto) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const existing = await tenantPrisma.dkvVehicleMaster.findFirst({ where: { id, tenantId }, }); @@ -581,7 +581,7 @@ export class DkvService { /** Mandantengebunden (260909-mir, Befund G) — siehe updateVehicle() oben. */ async deleteVehicle(tenantId: string, id: string): Promise<{ deleted: boolean }> { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const existing = await tenantPrisma.dkvVehicleMaster.findFirst({ where: { id, tenantId }, }); @@ -611,7 +611,7 @@ export class DkvService { csvText: string, mode: 'merge' | 'replace', ): Promise<{ imported: number; mode: string }> { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const vehicles = _parseVehicleCsv(csvText); if (vehicles.length === 0) { throw new BadRequestException( @@ -661,7 +661,7 @@ export class DkvService { page: number; limit: number; }> { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const skip = (page - 1) * limit; const [items, total] = await Promise.all([ tenantPrisma.dkvInvoiceHistory.findMany({ @@ -714,7 +714,7 @@ export class DkvService { // Stage 2 (NEW, 260909-mir): the ownership gate. A bound read — the // only tenant-scoped statement of who this file belongs to. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const owningHistoryRow = await tenantPrisma.dkvInvoiceHistory.findFirst({ where: { tenantId, exportFilename: filename }, }); @@ -751,8 +751,8 @@ export class DkvService { vehicleFormatString: string, ): Promise<{ lieferdatum: string; fahrzeug: string; fahrer: string; ort: string; kilometerstand: number | null }[]> { // Batch load vehicle master to avoid N+1 queries - const tenantPrisma = forTenant(this.prisma, tenantId) as any; - const masters: any[] = await tenantPrisma.dkvVehicleMaster.findMany({ where: { tenantId } }); + const tenantPrisma = forTenant(this.prisma, tenantId); + const masters = await tenantPrisma.dkvVehicleMaster.findMany({ where: { tenantId } }); // Normalize keys: DKV PDF may omit hyphens or use spaces ("GP JL 740E" vs "GP-JL 740E") const masterMap = new Map(masters.map((m) => [_normalizeKennzeichen(m.kennzeichen), m])); diff --git a/apps/api/src/favorites/favorites.service.ts b/apps/api/src/favorites/favorites.service.ts index 3185102..4a99827 100644 --- a/apps/api/src/favorites/favorites.service.ts +++ b/apps/api/src/favorites/favorites.service.ts @@ -65,7 +65,7 @@ export class FavoritesService { async list(tenantId: string, userId: string, widgetId: string) { if (!widgetId) throw new BadRequestException('widgetId is required'); - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); return tenantPrisma.favoriteLink.findMany({ where: { userId, widgetId }, orderBy: [{ position: 'asc' }, { title: 'asc' }], @@ -79,7 +79,7 @@ export class FavoritesService { * If iconUrl is not provided, triggers server-side icon discovery with SSRF protection. */ async create(tenantId: string, userId: string, dto: CreateFavoriteDto) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); // T-GWH-05: der Fremdschluessel prueft an der Zeilenschutz-Regel von // WidgetInstance vorbei (Aufgabe 1, Pruefung 7) — ohne diesen Riegel @@ -123,7 +123,7 @@ export class FavoritesService { * Accepts null as an explicit value for iconUrl (clears stored icon). */ async update(tenantId: string, id: string, userId: string, dto: UpdateFavoriteDto) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const link = await tenantPrisma.favoriteLink.findUnique({ where: { id } }); if (!link || link.userId !== userId) { @@ -164,7 +164,7 @@ export class FavoritesService { * Verifies userId ownership before deleting (T-08-06). */ async remove(tenantId: string, id: string, userId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const link = await tenantPrisma.favoriteLink.findUnique({ where: { id } }); if (!link || link.userId !== userId) { @@ -210,7 +210,7 @@ export class FavoritesService { throw new BadRequestException('ids must match the favorites of this widget exactly'); } - return withTenantTransaction(this.prisma, tenantId, async (tx: any) => { + return withTenantTransaction(this.prisma, tenantId, async (tx) => { const existing = await tx.favoriteLink.findMany({ where: { userId, widgetId: dto.widgetId }, select: { id: true }, @@ -258,7 +258,7 @@ export class FavoritesService { id: string, userId: string, ): Promise<{ contentType: string; body: Buffer }> { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const link = await tenantPrisma.favoriteLink.findUnique({ where: { id } }); if (!link || link.userId !== userId || !link.iconUrl) { diff --git a/apps/api/src/groups/groups.service.ts b/apps/api/src/groups/groups.service.ts index aa1dd08..030530c 100644 --- a/apps/api/src/groups/groups.service.ts +++ b/apps/api/src/groups/groups.service.ts @@ -53,20 +53,14 @@ export class GroupsService { * Mitgliederzahl. Ein Mandant ohne Gruppen liefert ein leeres Array. */ async listForTenant(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; - // Explizit als any[] annotiert (nicht nur der Rueckgabewert von await): - // ohne diese Array-Verankerung inferiert TypeScript den Rueckgabewert - // dieser Methode als bloss `any` statt `any[]`, und Aufrufer, die auf - // dem Ergebnis `.find()` aufrufen, wuerden TS7006 (impliziter any-Typ - // im Callback-Parameter) melden, obwohl der gebundene Client bewusst - // `any` ist (siehe forTenant()-Aufrufe in dieser Datei). - const groups: any[] = await tenantPrisma.group.findMany({ + const tenantPrisma = forTenant(this.prisma, tenantId); + const groups = await tenantPrisma.group.findMany({ where: { tenantId }, orderBy: { name: 'asc' }, include: { _count: { select: { memberships: true } } }, }); - return groups.map((g: any) => ({ + return groups.map((g) => ({ id: g.id, tenantId: g.tenantId, name: g.name, @@ -92,7 +86,7 @@ export class GroupsService { throw new BadRequestException('Gruppenname darf nicht leer sein'); } - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); try { return await tenantPrisma.group.create({ data: { tenantId, name }, @@ -112,7 +106,7 @@ export class GroupsService { * Mandanten liefert NotFoundException statt eines Treffers. */ private async findOwned(tenantId: string, id: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const group = await tenantPrisma.group.findFirst({ where: { id, tenantId }, }); @@ -188,7 +182,7 @@ export class GroupsService { try { if (data.isDefault === true) { - const updated = await withTenantTransaction(this.prisma, tenantId, async (tx: any) => { + const updated = await withTenantTransaction(this.prisma, tenantId, async (tx) => { await tx.group.updateMany({ where: { tenantId, isDefault: true }, data: { isDefault: false }, @@ -205,7 +199,7 @@ export class GroupsService { updateData.isDefault = false; } - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return await tenantPrisma.group.update({ where: { id }, data: updateData, @@ -227,7 +221,7 @@ export class GroupsService { async getImpact(tenantId: string, id: string) { await this.findOwned(tenantId, id); - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const [memberCount, grantCount] = await Promise.all([ tenantPrisma.groupMembership.count({ where: { groupId: id } }), tenantPrisma.moduleGrant.count({ where: { groupId: id } }), @@ -247,7 +241,7 @@ export class GroupsService { async remove(tenantId: string, id: string) { await this.findOwned(tenantId, id); - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); try { return await tenantPrisma.group.delete({ where: { id } }); } catch (err: any) { @@ -265,7 +259,7 @@ export class GroupsService { async listMembers(tenantId: string, id: string) { await this.findOwned(tenantId, id); - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.groupMembership.findMany({ where: { groupId: id }, include: { @@ -286,12 +280,12 @@ export class GroupsService { async addMembers(tenantId: string, id: string, userIds: string[]) { await this.findOwned(tenantId, id); - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const validUsers = await tenantPrisma.user.findMany({ where: { id: { in: userIds }, tenantId }, select: { id: true }, }); - const validIds = validUsers.map((u: any) => u.id); + const validIds = validUsers.map((u) => u.id); if (validIds.length === 0) { return { added: 0 }; } @@ -316,7 +310,7 @@ export class GroupsService { async removeMember(tenantId: string, id: string, userId: string) { await this.findOwned(tenantId, id); - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); await tenantPrisma.groupMembership.deleteMany({ where: { groupId: id, userId, source: MembershipSource.MANUAL }, }); @@ -354,14 +348,14 @@ export class GroupsService { * propagieren. */ async ensureDefaultGroup(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const existingCount = await tenantPrisma.group.count({ where: { tenantId } }); if (existingCount > 0) { return null; } try { - return await withTenantTransaction(this.prisma, tenantId, async (tx: any) => { + return await withTenantTransaction(this.prisma, tenantId, async (tx) => { const group = await tx.group.create({ data: { tenantId, name: DEFAULT_GROUP_NAME, isDefault: true }, }); @@ -372,6 +366,9 @@ export class GroupsService { }); if (users.length > 0) { await tx.groupMembership.createMany({ + // u: any bleibt (gemessen, Aufgabe 1 260921-m34) - tx ist selbst + // any (siehe Begruendung an withTenantTransaction()), any.map() + // gibt hier keine kontextuelle Typisierung des Parameters. data: users.map((u: any) => ({ groupId: group.id, userId: u.id, @@ -387,6 +384,8 @@ export class GroupsService { }); if (activations.length > 0) { await tx.moduleGrant.createMany({ + // a: any bleibt (gemessen, Aufgabe 1 260921-m34) - selbe Ursache + // wie bei `u` oben: tx ist any. data: activations.map((a: any) => ({ tenantId, moduleId: a.moduleId, @@ -435,7 +434,7 @@ export class GroupsService { * werfen — exakt das Muster aus ensureDefaultGroup(). */ async reassignDefaultBeforeDelete(tenantId: string, groupId: string): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const group = await tenantPrisma.group.findFirst({ where: { id: groupId, tenantId }, @@ -458,7 +457,7 @@ export class GroupsService { } try { - await withTenantTransaction(this.prisma, tenantId, async (tx: any) => { + await withTenantTransaction(this.prisma, tenantId, async (tx) => { await tx.group.updateMany({ where: { tenantId, isDefault: true }, data: { isDefault: false }, @@ -497,7 +496,7 @@ export class GroupsService { * Treffer folgenlos zurückkehren statt zu werfen. */ async addUserToDefaultGroup(tenantId: string, userId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const defaultGroup = await tenantPrisma.group.findFirst({ where: { tenantId, isDefault: true }, }); diff --git a/apps/api/src/groups/module-grants.service.ts b/apps/api/src/groups/module-grants.service.ts index c9dfcf2..8737719 100644 --- a/apps/api/src/groups/module-grants.service.ts +++ b/apps/api/src/groups/module-grants.service.ts @@ -48,7 +48,7 @@ export class ModuleGrantsService { groupId?: string, userId?: string, ): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); if (groupId) { const group = await tenantPrisma.group.findFirst({ where: { id: groupId, tenantId }, @@ -107,7 +107,7 @@ export class ModuleGrantsService { // Datenbank" entfallen. await this.assertTargetBelongsToTenant(tenantId, groupId, userId); - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const activation = await tenantPrisma.tenantModuleActivation.findUnique({ where: { tenantId_moduleId: { tenantId, moduleId } }, }); @@ -166,7 +166,7 @@ export class ModuleGrantsService { const { moduleId, groupId, userId } = data; const target = groupId ? `group=${groupId}` : `user=${userId}`; - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); await tenantPrisma.moduleGrant.deleteMany({ where: { tenantId, @@ -189,7 +189,7 @@ export class ModuleGrantsService { * hinweg stabil. */ async getMatrix(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const [activations, groups, groupGrants] = await Promise.all([ tenantPrisma.tenantModuleActivation.findMany({ where: { tenantId, isActive: true }, @@ -206,18 +206,17 @@ export class ModuleGrantsService { ]); const modules = activations - .map((a: any) => a.module) + .map((a) => a.module) .sort( - (a: any, b: any) => - a.category.localeCompare(b.category) || a.name.localeCompare(b.name), + (a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name), ); return { modules, groups, - grants: groupGrants.map((g: any) => ({ - moduleId: g.moduleId as string, - groupId: g.groupId as string, + grants: groupGrants.map((g) => ({ + moduleId: g.moduleId, + groupId: g.groupId, })), }; } @@ -246,7 +245,7 @@ export class ModuleGrantsService { async getUserAccess(tenantId: string, userId: string) { await this.assertTargetBelongsToTenant(tenantId, undefined, userId); - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const [activations, groupGrants, directGrants, memberships] = await Promise.all([ tenantPrisma.tenantModuleActivation.findMany({ where: { tenantId, isActive: true }, @@ -275,9 +274,9 @@ export class ModuleGrantsService { }), ]); - const directModuleIds = new Set(directGrants.map((g: any) => g.moduleId as string)); + const directModuleIds = new Set(directGrants.map((g) => g.moduleId)); const groupNamesByModule = new Map(); - for (const g of groupGrants as any[]) { + for (const g of groupGrants) { if (!g.group) continue; const names = groupNamesByModule.get(g.moduleId) ?? []; names.push(g.group.internalName ?? g.group.name); @@ -285,13 +284,12 @@ export class ModuleGrantsService { } const modules = activations - .map((a: any) => a.module) + .map((a) => a.module) .sort( - (a: any, b: any) => - a.category.localeCompare(b.category) || a.name.localeCompare(b.name), + (a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name), ); - const groups = (memberships as any[]) + const groups = memberships .filter((m) => m.group) .map((m) => ({ id: m.group.id as string, @@ -302,7 +300,7 @@ export class ModuleGrantsService { return { groups, - modules: modules.map((module: any) => ({ + modules: modules.map((module) => ({ module, viaGroups: groupNamesByModule.get(module.id) ?? [], direct: directModuleIds.has(module.id), diff --git a/apps/api/src/ldap/ldap-config.service.ts b/apps/api/src/ldap/ldap-config.service.ts index 5590ce6..cc23375 100644 --- a/apps/api/src/ldap/ldap-config.service.ts +++ b/apps/api/src/ldap/ldap-config.service.ts @@ -68,7 +68,7 @@ export class LdapConfigService implements OnApplicationBootstrap { */ async onApplicationBootstrap(): Promise { try { - const systemPrisma = forSystem(this.prisma) as any; + const systemPrisma = forSystem(this.prisma); const configs: { id: string; tenantId: string; encryptedBindPassword: string | null }[] = await systemPrisma.ldapConfig.findMany({ select: { id: true, tenantId: true, encryptedBindPassword: true }, @@ -84,7 +84,7 @@ export class LdapConfigService implements OnApplicationBootstrap { for (const config of legacy) { // Schreiben je Altzeile GEBUNDEN an den Mandanten der Zeile — unter // Systemkontext wuerde die Datenbank das Update abweisen (P2025). - const tenantPrisma = forTenant(this.prisma, config.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, config.tenantId); await tenantPrisma.ldapConfig.update({ where: { id: config.id }, data: { @@ -146,7 +146,7 @@ export class LdapConfigService implements OnApplicationBootstrap { * bewusst ueber alle Mandanten liest. */ async getConfig(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const config = await tenantPrisma.ldapConfig.findUnique({ where: { tenantId }, include: { fieldMappings: true }, @@ -166,7 +166,7 @@ export class LdapConfigService implements OnApplicationBootstrap { * gemessen. */ async createConfig(tenantId: string, dto: CreateLdapConfigDto) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const created = await tenantPrisma.ldapConfig.create({ data: { tenantId, @@ -209,7 +209,7 @@ export class LdapConfigService implements OnApplicationBootstrap { * Mandantengebunden (WINDOWS #20 Etappe 2, 260909-ipc). */ async updateConfig(tenantId: string, dto: UpdateLdapConfigDto) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const updated = await tenantPrisma.ldapConfig.update({ where: { tenantId }, data: { @@ -257,7 +257,7 @@ export class LdapConfigService implements OnApplicationBootstrap { configId: string, dto: CreateFieldMappingDto, ) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.ldapFieldMapping.create({ data: { ldapConfigId: configId, @@ -283,7 +283,7 @@ export class LdapConfigService implements OnApplicationBootstrap { * Loeschung. */ async removeFieldMapping(tenantId: string, mappingId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const mapping = await tenantPrisma.ldapFieldMapping.findUnique({ where: { id: mappingId }, }); @@ -319,11 +319,11 @@ export class LdapConfigService implements OnApplicationBootstrap { * den es dann nicht gibt. */ async getAllActiveConfigs() { - const systemPrisma = forSystem(this.prisma) as any; + const systemPrisma = forSystem(this.prisma); const configs = await systemPrisma.ldapConfig.findMany({ where: { isActive: true }, include: { tenant: true, fieldMappings: true }, }); - return configs.map((config: any) => this.withDecryptedPassword(config)); + return configs.map((config) => this.withDecryptedPassword(config)); } } diff --git a/apps/api/src/ldap/ldap.service.ts b/apps/api/src/ldap/ldap.service.ts index 7623adf..fb9b619 100644 --- a/apps/api/src/ldap/ldap.service.ts +++ b/apps/api/src/ldap/ldap.service.ts @@ -298,7 +298,7 @@ export class LdapService { // Mandantengescopter Lesepfad (WINDOWS #20 Etappe 2, 260909-ipc): die // "bereits importiert"-Markierung darf nur die Gruppen DIESES Mandanten // sehen. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); try { await this.bind(client, config.bindDn, config.bindPassword); @@ -471,7 +471,7 @@ export class LdapService { // Mandantengescopter Identitaets-/Schreibpfad (WINDOWS #20 Etappe 2, // 260909-ipc). Nicht zu verwechseln mit resolveEmailForWrite() oben, die // bewusst ungebunden bleibt. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const existingByDn = await tenantPrisma.user.findFirst({ where: { ldapDn: dn, tenantId }, }); @@ -566,7 +566,7 @@ export class LdapService { // Mandantengescopter Lesepfad (WINDOWS #20 Etappe 2, 260909-ipc): die // "bereits importiert"-Markierung darf nur die Konten DIESES Mandanten // sehen. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const first = (v: unknown): string => Array.isArray(v) ? String(v[0] ?? '') : v != null ? String(v) : ''; @@ -663,7 +663,7 @@ export class LdapService { ); // Mandantengescopter Dedup-/Schreibpfad (WINDOWS #20 Etappe 2, // 260909-ipc). - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); try { await this.bind(client, config.bindDn, config.bindPassword); @@ -791,7 +791,7 @@ export class LdapService { ); // Mandantengescopter Schreibpfad (T-16-02): app.current_tenant wird vor // jedem group.create() gesetzt, RLS ist das zweite Netz. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); try { await this.bind(client, config.bindDn, config.bindPassword); @@ -934,7 +934,7 @@ export class LdapService { ); // Create tenant-scoped Prisma client per Pitfall 2 - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); try { // 1. Bind with service account (anonymous when not configured) @@ -1208,7 +1208,7 @@ export class LdapService { tenantId: string, result: LdapSyncResult, ): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const boundGroups: { id: string; name: string; ldapDn: string | null }[] = await tenantPrisma.group.findMany({ @@ -1371,7 +1371,7 @@ export class LdapService { tenantId: string, result: LdapSyncResult, ): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const candidates: { id: string; diff --git a/apps/api/src/module-registry/module-access.service.ts b/apps/api/src/module-registry/module-access.service.ts index da90ae0..e30d33e 100644 --- a/apps/api/src/module-registry/module-access.service.ts +++ b/apps/api/src/module-registry/module-access.service.ts @@ -54,7 +54,7 @@ export class ModuleAccessService { // bestehen: der Schalter ist weiterhin aus (#18), die Datenbankregel // wirkt heute nicht, und die Anwendungspruefung ist bis zum // Scharfschalten der einzige tatsaechliche Schutz. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); if (role === 'ADMIN' || role === 'SUPER_ADMIN') { const activations = await tenantPrisma.tenantModuleActivation.findMany({ @@ -142,7 +142,7 @@ export class ModuleAccessService { // erzeugt ihren EIGENEN Klienten (dieselbe Konvention wie // `module-grants.service.ts`: gebundene Klienten werden nicht zwischen // Methoden weitergereicht). Beide laufen wie bisher nebenlaeufig. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const [activations, accessibleIds] = await Promise.all([ tenantPrisma.tenantModuleActivation.findMany({ where: { tenantId, isActive: true }, diff --git a/apps/api/src/module-registry/module-registry.service.ts b/apps/api/src/module-registry/module-registry.service.ts index 2d870da..31b3e0c 100644 --- a/apps/api/src/module-registry/module-registry.service.ts +++ b/apps/api/src/module-registry/module-registry.service.ts @@ -47,7 +47,7 @@ export class ModuleRegistryService { * Returns all active modules for a given tenant. */ async findActiveForTenant(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const activations = await tenantPrisma.tenantModuleActivation.findMany({ where: { tenantId, @@ -77,7 +77,7 @@ export class ModuleRegistryService { throw new NotFoundException(`Module with id '${moduleId}' not found`); } - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.tenantModuleActivation.upsert({ where: { tenantId_moduleId: { @@ -117,7 +117,7 @@ export class ModuleRegistryService { // EIN gebundener Klient fuer beide Aktivierungszugriffe dieser Methode // (Lesen, Schreiben) — nicht ein Klient je Zugriff (260910-exd, // Aufgabe 3, dieselbe Konvention wie `module-access.service.ts`). - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); // Check if activation record exists const activation = await tenantPrisma.tenantModuleActivation.findUnique({ @@ -172,7 +172,7 @@ export class ModuleRegistryService { return false; } - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const activation = await tenantPrisma.tenantModuleActivation.findUnique({ where: { tenantId_moduleId: { diff --git a/apps/api/src/prisma/prisma-tenant.extension.ts b/apps/api/src/prisma/prisma-tenant.extension.ts index 4dd1fd7..340362a 100644 --- a/apps/api/src/prisma/prisma-tenant.extension.ts +++ b/apps/api/src/prisma/prisma-tenant.extension.ts @@ -197,13 +197,11 @@ import { PrismaClient } from '@prisma/client'; export function forTenant(prisma: PrismaClient, tenantId: string, userId?: string) { return prisma.$extends({ query: { - $allOperations({ args, query }: { args: any; query: (args: any) => any }) { - const setContext = (prisma as any) - .$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true), set_config('app.current_user', ${userId ?? ''}, true), set_config('app.system_context', '', true)`; + $allOperations({ args, query }) { + const setContext = prisma.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true), set_config('app.current_user', ${userId ?? ''}, true), set_config('app.system_context', '', true)`; - return (prisma as any) - .$transaction([setContext, query(args)]) - .then((results: any[]) => results[1]); + return prisma.$transaction([setContext, query(args)]) + .then((results: unknown[]) => results[1]); }, }, }); @@ -226,13 +224,11 @@ export function forTenant(prisma: PrismaClient, tenantId: string, userId?: strin export function forSystem(prisma: PrismaClient) { return prisma.$extends({ query: { - $allOperations({ args, query }: { args: any; query: (args: any) => any }) { - const setContext = (prisma as any) - .$executeRaw`SELECT set_config('app.system_context', 'true', true), set_config('app.current_tenant', '', true), set_config('app.current_user', '', true)`; + $allOperations({ args, query }) { + const setContext = prisma.$executeRaw`SELECT set_config('app.system_context', 'true', true), set_config('app.current_tenant', '', true), set_config('app.current_user', '', true)`; - return (prisma as any) - .$transaction([setContext, query(args)]) - .then((results: any[]) => results[1]); + return prisma.$transaction([setContext, query(args)]) + .then((results: unknown[]) => results[1]); }, }, }); @@ -259,9 +255,15 @@ export function forSystem(prisma: PrismaClient) { export function withTenantTransaction( prisma: PrismaClient, tenantId: string, + // tx bleibt `any` (gemessen, Aufgabe 1 260921-m34): `Prisma.TransactionClient` + // erzwingt an den vier Aufrufstellen (groups.service.ts, favorites.service.ts) + // vollstaendige Prisma-Erzeugungstypen und bricht deren Testdoppel in + // prisma-tenant.extension.spec.ts (TS2322 auf einem absichtlich unvollstaendigen + // Fake-Objekt). Das waere eine Verhaltensaenderung an einer Teststruktur, + // nicht ehrliches Typisieren (D-02/D-03) - bleibt. fn: (tx: any) => Promise, ): Promise { - return (prisma as any).$transaction(async (tx: any) => { + return prisma.$transaction(async (tx: any) => { await tx.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true), set_config('app.system_context', '', true)`; return fn(tx); }); diff --git a/apps/api/src/settings/settings.service.ts b/apps/api/src/settings/settings.service.ts index 27971fd..aa28c11 100644 --- a/apps/api/src/settings/settings.service.ts +++ b/apps/api/src/settings/settings.service.ts @@ -41,7 +41,7 @@ export class SettingsService { * Methode, wie die restlichen Anfragewege dieser Datei. */ async getSmtpConfig(tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.smtpConfig.findUnique({ where: { tenantId }, select: { @@ -61,7 +61,7 @@ export class SettingsService { * Mandantengebunden (260911-gwh): EIN Klient `tenantPrisma`. */ async saveSmtpConfig(tenantId: string, dto: SmtpConfigDto) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); // Determine the encrypted password to store let encryptedPassword: string | undefined; @@ -104,7 +104,7 @@ export class SettingsService { * schmalem `select` — das verschluesselte Kennwort wird hier nie geladen. */ async getBugReportRecipient(tenantId: string): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const row = await tenantPrisma.smtpConfig.findUnique({ where: { tenantId }, select: { bugReportRecipient: true }, @@ -134,7 +134,7 @@ export class SettingsService { fromAddress: string; decryptedPassword: string | null; } | null> { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const config = await tenantPrisma.smtpConfig.findUnique({ where: { tenantId }, }); diff --git a/apps/api/src/tenant/tenant.controller.ts b/apps/api/src/tenant/tenant.controller.ts index e00b176..14cc543 100644 --- a/apps/api/src/tenant/tenant.controller.ts +++ b/apps/api/src/tenant/tenant.controller.ts @@ -68,7 +68,7 @@ export class TenantController { const results: any[] = []; for (const tenant of tenants) { - const tenantPrisma = forTenant(this.prisma, tenant.id) as any; + const tenantPrisma = forTenant(this.prisma, tenant.id); const userCount = await tenantPrisma.user.count({ where: { tenantId: tenant.id }, }); @@ -99,7 +99,7 @@ export class TenantController { throw new NotFoundException('Tenant not found'); } - const tenantPrisma = forTenant(this.prisma, id) as any; + const tenantPrisma = forTenant(this.prisma, id); const userCount = await tenantPrisma.user.count({ where: { tenantId: id }, }); @@ -160,7 +160,7 @@ export class TenantController { throw new NotFoundException('Tenant not found'); } - const tenantPrisma = forTenant(this.prisma, id) as any; + const tenantPrisma = forTenant(this.prisma, id); const activeUserCount = await tenantPrisma.user.count({ where: { tenantId: id, isActive: true }, }); diff --git a/apps/api/src/tenders/tender-digest.scheduler.ts b/apps/api/src/tenders/tender-digest.scheduler.ts index 5c732f9..99b2aca 100644 --- a/apps/api/src/tenders/tender-digest.scheduler.ts +++ b/apps/api/src/tenders/tender-digest.scheduler.ts @@ -121,7 +121,7 @@ export class TenderDigestScheduler implements OnModuleInit { // `distinct(['userId'])` liefert dann nur EINE der moeglichen // tenantId-Werte je Nutzer, welche ist von der internen Zeilenreihenfolge // abhaengig. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md. - const systemPrisma = forSystem(this.prisma) as any; + const systemPrisma = forSystem(this.prisma); const candidates: { userId: string; tenantId: string }[] = await systemPrisma.tenderMatch.findMany({ where: { notifiedAt: null }, select: { userId: true, tenantId: true }, @@ -145,7 +145,7 @@ export class TenderDigestScheduler implements OnModuleInit { // `userId` sieht dieser Zugriff den ganzen Mandanten, exakt wie vor // der Migration. Ein Systemkontext fuer Hintergrunddienste ist // Etappe 3c, nicht Teil dieser Aenderung. - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); const pref = await tenantPrisma.tenderNotificationPref.findUnique({ where: { userId }, diff --git a/apps/api/src/tenders/tender-email-config.service.ts b/apps/api/src/tenders/tender-email-config.service.ts index 578a2e8..39187ca 100644 --- a/apps/api/src/tenders/tender-email-config.service.ts +++ b/apps/api/src/tenders/tender-email-config.service.ts @@ -102,7 +102,7 @@ export class TenderEmailConfigService { * by userId (T-17-01) — a user only ever reads their own mailbox. */ async getConfigForApi(userId: string, tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const safe = await tenantPrisma.tenderEmailConfig.findUnique({ where: { userId }, select: EMAIL_CONFIG_SAFE_SELECT, @@ -152,7 +152,7 @@ export class TenderEmailConfigService { */ async saveConfig(ctx: { userId: string; tenantId: string }, dto: TenderEmailConfigDto) { const { userId, tenantId } = ctx; - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); let encryptedInboxCreds: string | undefined; const credChanged = @@ -240,7 +240,7 @@ export class TenderEmailConfigService { if (!username || !password) { try { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const existing = await tenantPrisma.tenderEmailConfig.findUnique({ where: { userId } }); if (existing?.encryptedInboxCreds) { const stored = JSON.parse(this.crypto.decrypt(existing.encryptedInboxCreds)) as { diff --git a/apps/api/src/tenders/tender-matching.service.ts b/apps/api/src/tenders/tender-matching.service.ts index 42b1142..6ce6c1f 100644 --- a/apps/api/src/tenders/tender-matching.service.ts +++ b/apps/api/src/tenders/tender-matching.service.ts @@ -72,7 +72,7 @@ export class TenderMatchingService { // und wuerde stumm. Treffer-Anlage und Sofortmeldung bleiben je Profil // GEBUNDEN (unten); der Katalog-Lesezugriff (`tender`, D-03) bleibt // ungebunden. Eine LEERE Profilliste ist Nichtstun (keine Treffer). - const systemPrisma = forSystem(this.prisma) as any; + const systemPrisma = forSystem(this.prisma); const savedSearches: Prisma.TenderSavedSearchGetPayload>[] = await systemPrisma.tenderSavedSearch.findMany(); @@ -95,7 +95,7 @@ export class TenderMatchingService { // Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3) // — EIN gebundener Client je Profil, nicht je Treffer, sonst // entstuende pro Zeile eine eigene Transaktion. - const tenantPrisma = forTenant(this.prisma, search.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, search.tenantId); for (const hit of hits) { await tenantPrisma.tenderMatch.upsert({ @@ -134,7 +134,7 @@ export class TenderMatchingService { try { // Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3) // — EIN gebundener Client je Profil. - const tenantPrisma = forTenant(this.prisma, profile.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, profile.tenantId); const fresh = await tenantPrisma.tenderMatch.findMany({ where: { @@ -156,7 +156,7 @@ export class TenderMatchingService { { email: user.email }, profile.tenantId, { name: profile.name }, - fresh.map((match: { tender: unknown }) => match.tender), + fresh.map((match) => match.tender), ); if (sent) { diff --git a/apps/api/src/tenders/tender-notification-pref.service.ts b/apps/api/src/tenders/tender-notification-pref.service.ts index 7a89de4..d591ea9 100644 --- a/apps/api/src/tenders/tender-notification-pref.service.ts +++ b/apps/api/src/tenders/tender-notification-pref.service.ts @@ -45,7 +45,7 @@ export class TenderNotificationPrefService { * autowrite needed to represent "using the default". */ async getForUser(userId: string, tenantId: string): Promise<{ digestInterval: string }> { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const existing = await tenantPrisma.tenderNotificationPref.findUnique({ where: { userId }, }); @@ -63,7 +63,7 @@ export class TenderNotificationPrefService { * than creating a new one. */ async setForUser(userId: string, tenantId: string, digestInterval: string) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); try { return await tenantPrisma.tenderNotificationPref.upsert({ where: { userId }, diff --git a/apps/api/src/tenders/tender-rss-feed.service.ts b/apps/api/src/tenders/tender-rss-feed.service.ts index 364e81a..3f1de28 100644 --- a/apps/api/src/tenders/tender-rss-feed.service.ts +++ b/apps/api/src/tenders/tender-rss-feed.service.ts @@ -69,7 +69,7 @@ export class TenderRssFeedSourceService { * Bindung nicht überflüssig, sondern das zweite Netz. */ async listForUser(userId: string, tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); return tenantPrisma.tenderRssFeedSource.findMany({ where: { OR: [{ userId: null }, { userId }] }, orderBy: { createdAt: 'asc' }, @@ -93,7 +93,7 @@ export class TenderRssFeedSourceService { ) { this.assertUrlAllowed(dto.url); - const tenantPrisma = forTenant(this.prisma, ctx.tenantId, ctx.userId) as any; + const tenantPrisma = forTenant(this.prisma, ctx.tenantId, ctx.userId); const existingCount = await tenantPrisma.tenderRssFeedSource.count({ where: { userId: ctx.userId }, }); diff --git a/apps/api/src/tenders/tender-saved-search.service.ts b/apps/api/src/tenders/tender-saved-search.service.ts index 4cb0a73..a24d7d3 100644 --- a/apps/api/src/tenders/tender-saved-search.service.ts +++ b/apps/api/src/tenders/tender-saved-search.service.ts @@ -44,7 +44,7 @@ export class TenderSavedSearchService { * strictly by userId (V4/IDOR) — a foreign userId sees nothing. */ async list(userId: string, tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); return tenantPrisma.tenderSavedSearch.findMany({ where: { userId }, orderBy: { name: 'asc' }, @@ -58,7 +58,7 @@ export class TenderSavedSearchService { * users, since the uniqueness is scoped per-user. */ async create(userId: string, tenantId: string, dto: CreateSavedSearchDto) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); try { return await tenantPrisma.tenderSavedSearch.create({ data: { @@ -87,7 +87,7 @@ export class TenderSavedSearchService { * leaking whether another user's profile exists). */ async update(id: string, userId: string, tenantId: string, dto: UpdateSavedSearchDto) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const existing = await tenantPrisma.tenderSavedSearch.findUnique({ where: { id }, }); @@ -124,7 +124,7 @@ export class TenderSavedSearchService { * update(). */ async remove(id: string, userId: string, tenantId: string) { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const existing = await tenantPrisma.tenderSavedSearch.findUnique({ where: { id }, }); diff --git a/apps/api/src/tenders/tender-triage.service.ts b/apps/api/src/tenders/tender-triage.service.ts index f1e56ef..2008283 100644 --- a/apps/api/src/tenders/tender-triage.service.ts +++ b/apps/api/src/tenders/tender-triage.service.ts @@ -75,7 +75,7 @@ export class TenderTriageService { update.favoritedAt = dto.isFavorite ? now : null; } - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); try { return await tenantPrisma.tenderTriage.upsert({ where: { userId_tenderId: { userId, tenderId } }, @@ -111,7 +111,7 @@ export class TenderTriageService { */ async listForUser(userId: string, tenantId: string, tenderIds: string[]) { if (!tenderIds.length) return []; - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); return tenantPrisma.tenderTriage.findMany({ where: { userId, tenderId: { in: tenderIds } }, }); @@ -123,7 +123,7 @@ export class TenderTriageService { * tender-query.builder.ts's buildTenderWhere. */ async favoriteIds(userId: string, tenantId: string): Promise { - const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId, userId); const rows = await tenantPrisma.tenderTriage.findMany({ where: { userId, isFavorite: true }, select: { tenderId: true }, diff --git a/apps/api/src/tenders/tenders.controller.ts b/apps/api/src/tenders/tenders.controller.ts index 3b72b88..2e2f60a 100644 --- a/apps/api/src/tenders/tenders.controller.ts +++ b/apps/api/src/tenders/tenders.controller.ts @@ -267,7 +267,7 @@ export class TendersController { const { userId, tenantId } = this.extractTriageContext(req); const feeds = await this.tenderRssFeedSource.listForUser(userId, tenantId); - return feeds.map(({ userId: ownerUserId, ...rest }: any) => ({ + return feeds.map(({ userId: ownerUserId, ...rest }) => ({ ...rest, isPlatformWide: ownerUserId === null, })); diff --git a/apps/api/src/user/admin-seed.service.ts b/apps/api/src/user/admin-seed.service.ts index c8fc304..d8054bc 100644 --- a/apps/api/src/user/admin-seed.service.ts +++ b/apps/api/src/user/admin-seed.service.ts @@ -104,7 +104,7 @@ export class AdminSeedService implements OnApplicationBootstrap { // `user-ungebundenes-einfuegen-abgelehnt`): eine FRISCHE Installation // haette ihren allerersten Administrator gar nicht anlegen koennen. const passwordHash = await argon2.hash(password); - const tenantPrisma = forTenant(this.prisma, tenant.id) as any; + const tenantPrisma = forTenant(this.prisma, tenant.id); try { await tenantPrisma.user.create({ data: { diff --git a/apps/api/src/user/user.controller.ts b/apps/api/src/user/user.controller.ts index b7fa970..8cdcd9c 100644 --- a/apps/api/src/user/user.controller.ts +++ b/apps/api/src/user/user.controller.ts @@ -92,7 +92,7 @@ export class UserController { // Mandantenbedingung im where BLEIBT erhalten -- nicht entfernen mit // dem Argument, das mache jetzt die Datenbank; dieselbe Regel, die die // Bereiche `tenders` und `dkv` aufgestellt haben. - const tenantPrisma = forTenant(this.prisma, currentUser.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, currentUser.tenantId); return tenantPrisma.user.findMany({ where: { tenantId: currentUser.tenantId }, select: { @@ -322,7 +322,7 @@ export class UserController { // Persist relative path (relative to monorepo root) const relativePath = path.join('user-files', 'avatars', filename); - const tenantPrisma = forTenant(this.prisma, currentUser.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, currentUser.tenantId); await tenantPrisma.user.update({ where: { id: currentUser.id }, data: { avatarPath: relativePath }, @@ -337,7 +337,7 @@ export class UserController { */ @Delete('me/avatar') async deleteAvatar(@CurrentUser() currentUser: any) { - const tenantPrisma = forTenant(this.prisma, currentUser.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, currentUser.tenantId); const user = await tenantPrisma.user.findUnique({ where: { id: currentUser.id }, select: { avatarPath: true }, @@ -372,7 +372,7 @@ export class UserController { throw new BadRequestException('Invalid color format. Use hex (#rrggbb).'); } - const tenantPrisma = forTenant(this.prisma, currentUser.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, currentUser.tenantId); await tenantPrisma.user.update({ where: { id: currentUser.id }, data: { accentColor: body.color ?? null }, @@ -391,7 +391,7 @@ export class UserController { @CurrentUser() currentUser: any, @Res() res: Response, ) { - const tenantPrisma = forTenant(this.prisma, currentUser.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, currentUser.tenantId); const user = await tenantPrisma.user.findUnique({ where: { id: currentUser.id }, select: { avatarPath: true }, diff --git a/apps/api/src/user/user.service.ts b/apps/api/src/user/user.service.ts index 37d35da..ad1f9bc 100644 --- a/apps/api/src/user/user.service.ts +++ b/apps/api/src/user/user.service.ts @@ -61,7 +61,7 @@ export class UserService { * (Aufgabe 1, `user-gebunden-nur-eigener-mandant`). */ async findById(tenantId: string, id: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.user.findUnique({ where: { id } }); } @@ -105,7 +105,7 @@ export class UserService { ldapDn?: string; }) { const { password, ...rest } = data; - const tenantPrisma = forTenant(this.prisma, data.tenantId) as any; + const tenantPrisma = forTenant(this.prisma, data.tenantId); let created: any; try { @@ -168,7 +168,7 @@ export class UserService { updateData.passwordHash = await argon2.hash(password); } - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); try { return await tenantPrisma.user.update({ where: { id }, @@ -189,7 +189,7 @@ export class UserService { * Mandanten. */ async deactivate(tenantId: string, id: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.user.update({ where: { id }, data: { isActive: false }, @@ -200,7 +200,7 @@ export class UserService { * Hard delete a user, gebunden an den uebergebenen Mandanten. */ async delete(tenantId: string, id: string) { - const tenantPrisma = forTenant(this.prisma, tenantId) as any; + const tenantPrisma = forTenant(this.prisma, tenantId); return tenantPrisma.user.delete({ where: { id } }); } @@ -233,7 +233,7 @@ export class UserService { const results: any[] = []; for (const tenant of tenants) { - const tenantPrisma = forTenant(this.prisma, tenant.id) as any; + const tenantPrisma = forTenant(this.prisma, tenant.id); const users = await tenantPrisma.user.findMany({ where: { tenantId: tenant.id }, select: { @@ -265,7 +265,7 @@ export class UserService { const tenants = await this.prisma.tenant.findMany({ select: { id: true } }); for (const tenant of tenants) { - const tenantPrisma = forTenant(this.prisma, tenant.id) as any; + const tenantPrisma = forTenant(this.prisma, tenant.id); const user = await tenantPrisma.user.findUnique({ where: { id } }); if (user) { return user;