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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 16:19:16 +02:00
parent 8d845e732e
commit b188946e31
24 changed files with 147 additions and 148 deletions
+6 -6
View File
@@ -115,7 +115,7 @@ export class AuthService {
return null; 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 // LDAP users have no local password — authenticate them against the
// directory by binding as their OWN DN with the password they entered. // 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 // Create the reset token record — mandantengebunden, sobald der
// Benutzer und damit sein Mandant bekannt sind (WINDOWS #20, Aufgabe 1). // 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({ await tenantPrisma.passwordResetToken.create({
data: { data: {
token, token,
@@ -274,7 +274,7 @@ export class AuthService {
// Mandant ist ab hier bekannt (aus der Funktion mitgeliefert) — beide // Mandant ist ab hier bekannt (aus der Funktion mitgeliefert) — beide
// Schreibzugriffe laufen gebunden (WINDOWS #20, Aufgabe 1). // 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 // Hash the new password and update user
const passwordHash = await argon2.hash(newPassword); const passwordHash = await argon2.hash(newPassword);
@@ -308,7 +308,7 @@ export class AuthService {
* Zeile. * Zeile.
*/ */
async getMe(tenantId: string, userId: string) { 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({ const user = await tenantPrisma.user.findUnique({
where: { id: userId }, where: { id: userId },
select: { select: {
@@ -353,7 +353,7 @@ export class AuthService {
newPassword: string, newPassword: string,
response: Response, response: Response,
): Promise<void> { ): Promise<void> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const user = await tenantPrisma.user.findUnique({ const user = await tenantPrisma.user.findUnique({
where: { id: userId }, where: { id: userId },
}); });
@@ -417,7 +417,7 @@ export class AuthService {
newPassword: string, newPassword: string,
mustChangePassword: boolean = true, mustChangePassword: boolean = true,
): Promise<void> { ): Promise<void> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const user = await tenantPrisma.user.findUnique({ const user = await tenantPrisma.user.findUnique({
where: { id: userId }, where: { id: userId },
}); });
@@ -118,7 +118,7 @@ export class BugReportsService {
// (4) Benutzerzeile: gebunden an den Sitzungs-Mandanten, nie an Rumpfdaten // (4) Benutzerzeile: gebunden an den Sitzungs-Mandanten, nie an Rumpfdaten
// (T-M97-06; Zeile in docs/mandantentrennung-zugriffsklassifikation.md). // (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({ const row = await tenantPrisma.user.findUnique({
where: { id: user.id }, where: { id: user.id },
select: { username: true, displayName: true, email: true, role: true }, select: { username: true, displayName: true, email: true, role: true },
+16 -16
View File
@@ -118,7 +118,7 @@ export class DkvService {
* 260914-eym systemgebunden, eine Zeile je aktivem Mandanten). * 260914-eym systemgebunden, eine Zeile je aktivem Mandanten).
*/ */
async loadConfig(tenantId: string) { async loadConfig(tenantId: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return tenantPrisma.dkvModuleConfig.findUnique({ return tenantPrisma.dkvModuleConfig.findUnique({
where: { tenantId }, where: { tenantId },
select: CONFIG_SAFE_SELECT, select: CONFIG_SAFE_SELECT,
@@ -153,7 +153,7 @@ export class DkvService {
* (T-07-12) — der Planer braucht nur tenantId und pollIntervalMin. * (T-07-12) — der Planer braucht nur tenantId und pollIntervalMin.
*/ */
async loadActiveConfigsForScheduler() { async loadActiveConfigsForScheduler() {
const systemPrisma = forSystem(this.prisma) as any; const systemPrisma = forSystem(this.prisma);
return systemPrisma.dkvModuleConfig.findMany({ return systemPrisma.dkvModuleConfig.findMany({
where: { isActive: true }, where: { isActive: true },
select: CONFIG_SAFE_SELECT, select: CONFIG_SAFE_SELECT,
@@ -171,7 +171,7 @@ export class DkvService {
* Methode). * Methode).
*/ */
async getConfigForApi(tenantId: string) { async getConfigForApi(tenantId: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const safe = await tenantPrisma.dkvModuleConfig.findUnique({ const safe = await tenantPrisma.dkvModuleConfig.findUnique({
where: { tenantId }, where: { tenantId },
select: CONFIG_SAFE_SELECT, select: CONFIG_SAFE_SELECT,
@@ -214,7 +214,7 @@ export class DkvService {
* Bindung als zweite Schicht bestehen. * Bindung als zweite Schicht bestehen.
*/ */
async saveConfig(tenantId: string, dto: DkvConfigDto) { async saveConfig(tenantId: string, dto: DkvConfigDto) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
let encryptedInboxCreds: string | undefined; let encryptedInboxCreds: string | undefined;
const credChanged = (dto.password && dto.password.length > 0) || const credChanged = (dto.password && dto.password.length > 0) ||
@@ -277,7 +277,7 @@ export class DkvService {
* Rueckgriff auf die gespeicherten Zugangsdaten. * Rueckgriff auf die gespeicherten Zugangsdaten.
*/ */
async testConnection(tenantId: string, dto: DkvConfigDto): Promise<{ success: boolean; message?: string }> { 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; let password: string | undefined = dto.password;
// If no password in DTO, fall back to the stored one // If no password in DTO, fall back to the stored one
@@ -362,7 +362,7 @@ export class DkvService {
private async _runPipeline(tenantId: string): Promise<void> { private async _runPipeline(tenantId: string): Promise<void> {
// Load raw config (need encryptedInboxCreds for decryption). // Load raw config (need encryptedInboxCreds for decryption).
// Mandantengebunden (260909-mir). // 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 } }); const config = await tenantPrisma.dkvModuleConfig.findUnique({ where: { tenantId } });
if (!config) { if (!config) {
this.logger.warn(`DKV processInbox: no config for tenant ${tenantId}`); 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 // Mandantengebunden (260909-mir): EIN gebundener Klient fuer beide
// dkvInvoiceHistory.create()-Aufrufe dieser Methode (Erfolgsfall UND // dkvInvoiceHistory.create()-Aufrufe dieser Methode (Erfolgsfall UND
// Zerlegungsfehler-Fall). // Zerlegungsfehler-Fall).
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
// D-10: Up to 3 parse retries // D-10: Up to 3 parse retries
let parseResult: Awaited<ReturnType<typeof this.parser.parsePdf>> | null = null; let parseResult: Awaited<ReturnType<typeof this.parser.parsePdf>> | null = null;
@@ -546,7 +546,7 @@ export class DkvService {
// ─── Vehicle CRUD ──────────────────────────────────────────────────────────── // ─── Vehicle CRUD ────────────────────────────────────────────────────────────
async listVehicles(tenantId: string) { async listVehicles(tenantId: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return tenantPrisma.dkvVehicleMaster.findMany({ return tenantPrisma.dkvVehicleMaster.findMany({
where: { tenantId }, where: { tenantId },
orderBy: { kennzeichen: 'asc' }, orderBy: { kennzeichen: 'asc' },
@@ -554,7 +554,7 @@ export class DkvService {
} }
async createVehicle(tenantId: string, dto: CreateVehicleDto) { async createVehicle(tenantId: string, dto: CreateVehicleDto) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return tenantPrisma.dkvVehicleMaster.create({ return tenantPrisma.dkvVehicleMaster.create({
data: { tenantId, ...dto }, data: { tenantId, ...dto },
}); });
@@ -571,7 +571,7 @@ export class DkvService {
* Schreibzugriff dahinter waere genau die Luecke, nicht die Loesung. * Schreibzugriff dahinter waere genau die Luecke, nicht die Loesung.
*/ */
async updateVehicle(tenantId: string, id: string, dto: UpdateVehicleDto) { 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({ const existing = await tenantPrisma.dkvVehicleMaster.findFirst({
where: { id, tenantId }, where: { id, tenantId },
}); });
@@ -581,7 +581,7 @@ export class DkvService {
/** Mandantengebunden (260909-mir, Befund G) — siehe updateVehicle() oben. */ /** Mandantengebunden (260909-mir, Befund G) — siehe updateVehicle() oben. */
async deleteVehicle(tenantId: string, id: string): Promise<{ deleted: boolean }> { 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({ const existing = await tenantPrisma.dkvVehicleMaster.findFirst({
where: { id, tenantId }, where: { id, tenantId },
}); });
@@ -611,7 +611,7 @@ export class DkvService {
csvText: string, csvText: string,
mode: 'merge' | 'replace', mode: 'merge' | 'replace',
): Promise<{ imported: number; mode: string }> { ): Promise<{ imported: number; mode: string }> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const vehicles = _parseVehicleCsv(csvText); const vehicles = _parseVehicleCsv(csvText);
if (vehicles.length === 0) { if (vehicles.length === 0) {
throw new BadRequestException( throw new BadRequestException(
@@ -661,7 +661,7 @@ export class DkvService {
page: number; page: number;
limit: number; limit: number;
}> { }> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const skip = (page - 1) * limit; const skip = (page - 1) * limit;
const [items, total] = await Promise.all([ const [items, total] = await Promise.all([
tenantPrisma.dkvInvoiceHistory.findMany({ tenantPrisma.dkvInvoiceHistory.findMany({
@@ -714,7 +714,7 @@ export class DkvService {
// Stage 2 (NEW, 260909-mir): the ownership gate. A bound read — the // Stage 2 (NEW, 260909-mir): the ownership gate. A bound read — the
// only tenant-scoped statement of who this file belongs to. // 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({ const owningHistoryRow = await tenantPrisma.dkvInvoiceHistory.findFirst({
where: { tenantId, exportFilename: filename }, where: { tenantId, exportFilename: filename },
}); });
@@ -751,8 +751,8 @@ export class DkvService {
vehicleFormatString: string, vehicleFormatString: string,
): Promise<{ lieferdatum: string; fahrzeug: string; fahrer: string; ort: string; kilometerstand: number | null }[]> { ): Promise<{ lieferdatum: string; fahrzeug: string; fahrer: string; ort: string; kilometerstand: number | null }[]> {
// Batch load vehicle master to avoid N+1 queries // Batch load vehicle master to avoid N+1 queries
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const masters: any[] = await tenantPrisma.dkvVehicleMaster.findMany({ where: { 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") // 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])); const masterMap = new Map(masters.map((m) => [_normalizeKennzeichen(m.kennzeichen), m]));
+6 -6
View File
@@ -65,7 +65,7 @@ export class FavoritesService {
async list(tenantId: string, userId: string, widgetId: string) { async list(tenantId: string, userId: string, widgetId: string) {
if (!widgetId) throw new BadRequestException('widgetId is required'); 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({ return tenantPrisma.favoriteLink.findMany({
where: { userId, widgetId }, where: { userId, widgetId },
orderBy: [{ position: 'asc' }, { title: 'asc' }], 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. * If iconUrl is not provided, triggers server-side icon discovery with SSRF protection.
*/ */
async create(tenantId: string, userId: string, dto: CreateFavoriteDto) { 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 // T-GWH-05: der Fremdschluessel prueft an der Zeilenschutz-Regel von
// WidgetInstance vorbei (Aufgabe 1, Pruefung 7) — ohne diesen Riegel // 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). * Accepts null as an explicit value for iconUrl (clears stored icon).
*/ */
async update(tenantId: string, id: string, userId: string, dto: UpdateFavoriteDto) { 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 } }); const link = await tenantPrisma.favoriteLink.findUnique({ where: { id } });
if (!link || link.userId !== userId) { if (!link || link.userId !== userId) {
@@ -164,7 +164,7 @@ export class FavoritesService {
* Verifies userId ownership before deleting (T-08-06). * Verifies userId ownership before deleting (T-08-06).
*/ */
async remove(tenantId: string, id: string, userId: string) { 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 } }); const link = await tenantPrisma.favoriteLink.findUnique({ where: { id } });
if (!link || link.userId !== userId) { if (!link || link.userId !== userId) {
@@ -210,7 +210,7 @@ export class FavoritesService {
throw new BadRequestException('ids must match the favorites of this widget exactly'); 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({ const existing = await tx.favoriteLink.findMany({
where: { userId, widgetId: dto.widgetId }, where: { userId, widgetId: dto.widgetId },
select: { id: true }, select: { id: true },
@@ -258,7 +258,7 @@ export class FavoritesService {
id: string, id: string,
userId: string, userId: string,
): Promise<{ contentType: string; body: Buffer }> { ): 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 } }); const link = await tenantPrisma.favoriteLink.findUnique({ where: { id } });
if (!link || link.userId !== userId || !link.iconUrl) { if (!link || link.userId !== userId || !link.iconUrl) {
+23 -24
View File
@@ -53,20 +53,14 @@ export class GroupsService {
* Mitgliederzahl. Ein Mandant ohne Gruppen liefert ein leeres Array. * Mitgliederzahl. Ein Mandant ohne Gruppen liefert ein leeres Array.
*/ */
async listForTenant(tenantId: string) { async listForTenant(tenantId: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
// Explizit als any[] annotiert (nicht nur der Rueckgabewert von await): const groups = await tenantPrisma.group.findMany({
// 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({
where: { tenantId }, where: { tenantId },
orderBy: { name: 'asc' }, orderBy: { name: 'asc' },
include: { _count: { select: { memberships: true } } }, include: { _count: { select: { memberships: true } } },
}); });
return groups.map((g: any) => ({ return groups.map((g) => ({
id: g.id, id: g.id,
tenantId: g.tenantId, tenantId: g.tenantId,
name: g.name, name: g.name,
@@ -92,7 +86,7 @@ export class GroupsService {
throw new BadRequestException('Gruppenname darf nicht leer sein'); throw new BadRequestException('Gruppenname darf nicht leer sein');
} }
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
try { try {
return await tenantPrisma.group.create({ return await tenantPrisma.group.create({
data: { tenantId, name }, data: { tenantId, name },
@@ -112,7 +106,7 @@ export class GroupsService {
* Mandanten liefert NotFoundException statt eines Treffers. * Mandanten liefert NotFoundException statt eines Treffers.
*/ */
private async findOwned(tenantId: string, id: string) { 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({ const group = await tenantPrisma.group.findFirst({
where: { id, tenantId }, where: { id, tenantId },
}); });
@@ -188,7 +182,7 @@ export class GroupsService {
try { try {
if (data.isDefault === true) { 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({ await tx.group.updateMany({
where: { tenantId, isDefault: true }, where: { tenantId, isDefault: true },
data: { isDefault: false }, data: { isDefault: false },
@@ -205,7 +199,7 @@ export class GroupsService {
updateData.isDefault = false; updateData.isDefault = false;
} }
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return await tenantPrisma.group.update({ return await tenantPrisma.group.update({
where: { id }, where: { id },
data: updateData, data: updateData,
@@ -227,7 +221,7 @@ export class GroupsService {
async getImpact(tenantId: string, id: string) { async getImpact(tenantId: string, id: string) {
await this.findOwned(tenantId, id); 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([ const [memberCount, grantCount] = await Promise.all([
tenantPrisma.groupMembership.count({ where: { groupId: id } }), tenantPrisma.groupMembership.count({ where: { groupId: id } }),
tenantPrisma.moduleGrant.count({ where: { groupId: id } }), tenantPrisma.moduleGrant.count({ where: { groupId: id } }),
@@ -247,7 +241,7 @@ export class GroupsService {
async remove(tenantId: string, id: string) { async remove(tenantId: string, id: string) {
await this.findOwned(tenantId, id); await this.findOwned(tenantId, id);
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
try { try {
return await tenantPrisma.group.delete({ where: { id } }); return await tenantPrisma.group.delete({ where: { id } });
} catch (err: any) { } catch (err: any) {
@@ -265,7 +259,7 @@ export class GroupsService {
async listMembers(tenantId: string, id: string) { async listMembers(tenantId: string, id: string) {
await this.findOwned(tenantId, id); await this.findOwned(tenantId, id);
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return tenantPrisma.groupMembership.findMany({ return tenantPrisma.groupMembership.findMany({
where: { groupId: id }, where: { groupId: id },
include: { include: {
@@ -286,12 +280,12 @@ export class GroupsService {
async addMembers(tenantId: string, id: string, userIds: string[]) { async addMembers(tenantId: string, id: string, userIds: string[]) {
await this.findOwned(tenantId, id); 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({ const validUsers = await tenantPrisma.user.findMany({
where: { id: { in: userIds }, tenantId }, where: { id: { in: userIds }, tenantId },
select: { id: true }, select: { id: true },
}); });
const validIds = validUsers.map((u: any) => u.id); const validIds = validUsers.map((u) => u.id);
if (validIds.length === 0) { if (validIds.length === 0) {
return { added: 0 }; return { added: 0 };
} }
@@ -316,7 +310,7 @@ export class GroupsService {
async removeMember(tenantId: string, id: string, userId: string) { async removeMember(tenantId: string, id: string, userId: string) {
await this.findOwned(tenantId, id); await this.findOwned(tenantId, id);
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
await tenantPrisma.groupMembership.deleteMany({ await tenantPrisma.groupMembership.deleteMany({
where: { groupId: id, userId, source: MembershipSource.MANUAL }, where: { groupId: id, userId, source: MembershipSource.MANUAL },
}); });
@@ -354,14 +348,14 @@ export class GroupsService {
* propagieren. * propagieren.
*/ */
async ensureDefaultGroup(tenantId: string) { 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 } }); const existingCount = await tenantPrisma.group.count({ where: { tenantId } });
if (existingCount > 0) { if (existingCount > 0) {
return null; return null;
} }
try { try {
return await withTenantTransaction(this.prisma, tenantId, async (tx: any) => { return await withTenantTransaction(this.prisma, tenantId, async (tx) => {
const group = await tx.group.create({ const group = await tx.group.create({
data: { tenantId, name: DEFAULT_GROUP_NAME, isDefault: true }, data: { tenantId, name: DEFAULT_GROUP_NAME, isDefault: true },
}); });
@@ -372,6 +366,9 @@ export class GroupsService {
}); });
if (users.length > 0) { if (users.length > 0) {
await tx.groupMembership.createMany({ 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) => ({ data: users.map((u: any) => ({
groupId: group.id, groupId: group.id,
userId: u.id, userId: u.id,
@@ -387,6 +384,8 @@ export class GroupsService {
}); });
if (activations.length > 0) { if (activations.length > 0) {
await tx.moduleGrant.createMany({ 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) => ({ data: activations.map((a: any) => ({
tenantId, tenantId,
moduleId: a.moduleId, moduleId: a.moduleId,
@@ -435,7 +434,7 @@ export class GroupsService {
* werfen — exakt das Muster aus ensureDefaultGroup(). * werfen — exakt das Muster aus ensureDefaultGroup().
*/ */
async reassignDefaultBeforeDelete(tenantId: string, groupId: string): Promise<boolean> { async reassignDefaultBeforeDelete(tenantId: string, groupId: string): Promise<boolean> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const group = await tenantPrisma.group.findFirst({ const group = await tenantPrisma.group.findFirst({
where: { id: groupId, tenantId }, where: { id: groupId, tenantId },
@@ -458,7 +457,7 @@ export class GroupsService {
} }
try { try {
await withTenantTransaction(this.prisma, tenantId, async (tx: any) => { await withTenantTransaction(this.prisma, tenantId, async (tx) => {
await tx.group.updateMany({ await tx.group.updateMany({
where: { tenantId, isDefault: true }, where: { tenantId, isDefault: true },
data: { isDefault: false }, data: { isDefault: false },
@@ -497,7 +496,7 @@ export class GroupsService {
* Treffer folgenlos zurückkehren statt zu werfen. * Treffer folgenlos zurückkehren statt zu werfen.
*/ */
async addUserToDefaultGroup(tenantId: string, userId: string) { 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({ const defaultGroup = await tenantPrisma.group.findFirst({
where: { tenantId, isDefault: true }, where: { tenantId, isDefault: true },
}); });
+16 -18
View File
@@ -48,7 +48,7 @@ export class ModuleGrantsService {
groupId?: string, groupId?: string,
userId?: string, userId?: string,
): Promise<void> { ): Promise<void> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
if (groupId) { if (groupId) {
const group = await tenantPrisma.group.findFirst({ const group = await tenantPrisma.group.findFirst({
where: { id: groupId, tenantId }, where: { id: groupId, tenantId },
@@ -107,7 +107,7 @@ export class ModuleGrantsService {
// Datenbank" entfallen. // Datenbank" entfallen.
await this.assertTargetBelongsToTenant(tenantId, groupId, userId); 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({ const activation = await tenantPrisma.tenantModuleActivation.findUnique({
where: { tenantId_moduleId: { tenantId, moduleId } }, where: { tenantId_moduleId: { tenantId, moduleId } },
}); });
@@ -166,7 +166,7 @@ export class ModuleGrantsService {
const { moduleId, groupId, userId } = data; const { moduleId, groupId, userId } = data;
const target = groupId ? `group=${groupId}` : `user=${userId}`; 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({ await tenantPrisma.moduleGrant.deleteMany({
where: { where: {
tenantId, tenantId,
@@ -189,7 +189,7 @@ export class ModuleGrantsService {
* hinweg stabil. * hinweg stabil.
*/ */
async getMatrix(tenantId: string) { 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([ const [activations, groups, groupGrants] = await Promise.all([
tenantPrisma.tenantModuleActivation.findMany({ tenantPrisma.tenantModuleActivation.findMany({
where: { tenantId, isActive: true }, where: { tenantId, isActive: true },
@@ -206,18 +206,17 @@ export class ModuleGrantsService {
]); ]);
const modules = activations const modules = activations
.map((a: any) => a.module) .map((a) => a.module)
.sort( .sort(
(a: any, b: any) => (a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name),
a.category.localeCompare(b.category) || a.name.localeCompare(b.name),
); );
return { return {
modules, modules,
groups, groups,
grants: groupGrants.map((g: any) => ({ grants: groupGrants.map((g) => ({
moduleId: g.moduleId as string, moduleId: g.moduleId,
groupId: g.groupId as string, groupId: g.groupId,
})), })),
}; };
} }
@@ -246,7 +245,7 @@ export class ModuleGrantsService {
async getUserAccess(tenantId: string, userId: string) { async getUserAccess(tenantId: string, userId: string) {
await this.assertTargetBelongsToTenant(tenantId, undefined, userId); 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([ const [activations, groupGrants, directGrants, memberships] = await Promise.all([
tenantPrisma.tenantModuleActivation.findMany({ tenantPrisma.tenantModuleActivation.findMany({
where: { tenantId, isActive: true }, 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<string, string[]>(); const groupNamesByModule = new Map<string, string[]>();
for (const g of groupGrants as any[]) { for (const g of groupGrants) {
if (!g.group) continue; if (!g.group) continue;
const names = groupNamesByModule.get(g.moduleId) ?? []; const names = groupNamesByModule.get(g.moduleId) ?? [];
names.push(g.group.internalName ?? g.group.name); names.push(g.group.internalName ?? g.group.name);
@@ -285,13 +284,12 @@ export class ModuleGrantsService {
} }
const modules = activations const modules = activations
.map((a: any) => a.module) .map((a) => a.module)
.sort( .sort(
(a: any, b: any) => (a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name),
a.category.localeCompare(b.category) || a.name.localeCompare(b.name),
); );
const groups = (memberships as any[]) const groups = memberships
.filter((m) => m.group) .filter((m) => m.group)
.map((m) => ({ .map((m) => ({
id: m.group.id as string, id: m.group.id as string,
@@ -302,7 +300,7 @@ export class ModuleGrantsService {
return { return {
groups, groups,
modules: modules.map((module: any) => ({ modules: modules.map((module) => ({
module, module,
viaGroups: groupNamesByModule.get(module.id) ?? [], viaGroups: groupNamesByModule.get(module.id) ?? [],
direct: directModuleIds.has(module.id), direct: directModuleIds.has(module.id),
+9 -9
View File
@@ -68,7 +68,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
*/ */
async onApplicationBootstrap(): Promise<void> { async onApplicationBootstrap(): Promise<void> {
try { try {
const systemPrisma = forSystem(this.prisma) as any; const systemPrisma = forSystem(this.prisma);
const configs: { id: string; tenantId: string; encryptedBindPassword: string | null }[] = const configs: { id: string; tenantId: string; encryptedBindPassword: string | null }[] =
await systemPrisma.ldapConfig.findMany({ await systemPrisma.ldapConfig.findMany({
select: { id: true, tenantId: true, encryptedBindPassword: true }, select: { id: true, tenantId: true, encryptedBindPassword: true },
@@ -84,7 +84,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
for (const config of legacy) { for (const config of legacy) {
// Schreiben je Altzeile GEBUNDEN an den Mandanten der Zeile — unter // Schreiben je Altzeile GEBUNDEN an den Mandanten der Zeile — unter
// Systemkontext wuerde die Datenbank das Update abweisen (P2025). // 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({ await tenantPrisma.ldapConfig.update({
where: { id: config.id }, where: { id: config.id },
data: { data: {
@@ -146,7 +146,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
* bewusst ueber alle Mandanten liest. * bewusst ueber alle Mandanten liest.
*/ */
async getConfig(tenantId: string) { async getConfig(tenantId: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const config = await tenantPrisma.ldapConfig.findUnique({ const config = await tenantPrisma.ldapConfig.findUnique({
where: { tenantId }, where: { tenantId },
include: { fieldMappings: true }, include: { fieldMappings: true },
@@ -166,7 +166,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
* gemessen. * gemessen.
*/ */
async createConfig(tenantId: string, dto: CreateLdapConfigDto) { 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({ const created = await tenantPrisma.ldapConfig.create({
data: { data: {
tenantId, tenantId,
@@ -209,7 +209,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
* Mandantengebunden (WINDOWS #20 Etappe 2, 260909-ipc). * Mandantengebunden (WINDOWS #20 Etappe 2, 260909-ipc).
*/ */
async updateConfig(tenantId: string, dto: UpdateLdapConfigDto) { 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({ const updated = await tenantPrisma.ldapConfig.update({
where: { tenantId }, where: { tenantId },
data: { data: {
@@ -257,7 +257,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
configId: string, configId: string,
dto: CreateFieldMappingDto, dto: CreateFieldMappingDto,
) { ) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return tenantPrisma.ldapFieldMapping.create({ return tenantPrisma.ldapFieldMapping.create({
data: { data: {
ldapConfigId: configId, ldapConfigId: configId,
@@ -283,7 +283,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
* Loeschung. * Loeschung.
*/ */
async removeFieldMapping(tenantId: string, mappingId: string) { 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({ const mapping = await tenantPrisma.ldapFieldMapping.findUnique({
where: { id: mappingId }, where: { id: mappingId },
}); });
@@ -319,11 +319,11 @@ export class LdapConfigService implements OnApplicationBootstrap {
* den es dann nicht gibt. * den es dann nicht gibt.
*/ */
async getAllActiveConfigs() { async getAllActiveConfigs() {
const systemPrisma = forSystem(this.prisma) as any; const systemPrisma = forSystem(this.prisma);
const configs = await systemPrisma.ldapConfig.findMany({ const configs = await systemPrisma.ldapConfig.findMany({
where: { isActive: true }, where: { isActive: true },
include: { tenant: true, fieldMappings: true }, include: { tenant: true, fieldMappings: true },
}); });
return configs.map((config: any) => this.withDecryptedPassword(config)); return configs.map((config) => this.withDecryptedPassword(config));
} }
} }
+8 -8
View File
@@ -298,7 +298,7 @@ export class LdapService {
// Mandantengescopter Lesepfad (WINDOWS #20 Etappe 2, 260909-ipc): die // Mandantengescopter Lesepfad (WINDOWS #20 Etappe 2, 260909-ipc): die
// "bereits importiert"-Markierung darf nur die Gruppen DIESES Mandanten // "bereits importiert"-Markierung darf nur die Gruppen DIESES Mandanten
// sehen. // sehen.
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
try { try {
await this.bind(client, config.bindDn, config.bindPassword); await this.bind(client, config.bindDn, config.bindPassword);
@@ -471,7 +471,7 @@ export class LdapService {
// Mandantengescopter Identitaets-/Schreibpfad (WINDOWS #20 Etappe 2, // Mandantengescopter Identitaets-/Schreibpfad (WINDOWS #20 Etappe 2,
// 260909-ipc). Nicht zu verwechseln mit resolveEmailForWrite() oben, die // 260909-ipc). Nicht zu verwechseln mit resolveEmailForWrite() oben, die
// bewusst ungebunden bleibt. // bewusst ungebunden bleibt.
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const existingByDn = await tenantPrisma.user.findFirst({ const existingByDn = await tenantPrisma.user.findFirst({
where: { ldapDn: dn, tenantId }, where: { ldapDn: dn, tenantId },
}); });
@@ -566,7 +566,7 @@ export class LdapService {
// Mandantengescopter Lesepfad (WINDOWS #20 Etappe 2, 260909-ipc): die // Mandantengescopter Lesepfad (WINDOWS #20 Etappe 2, 260909-ipc): die
// "bereits importiert"-Markierung darf nur die Konten DIESES Mandanten // "bereits importiert"-Markierung darf nur die Konten DIESES Mandanten
// sehen. // sehen.
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const first = (v: unknown): string => const first = (v: unknown): string =>
Array.isArray(v) ? String(v[0] ?? '') : v != null ? String(v) : ''; Array.isArray(v) ? String(v[0] ?? '') : v != null ? String(v) : '';
@@ -663,7 +663,7 @@ export class LdapService {
); );
// Mandantengescopter Dedup-/Schreibpfad (WINDOWS #20 Etappe 2, // Mandantengescopter Dedup-/Schreibpfad (WINDOWS #20 Etappe 2,
// 260909-ipc). // 260909-ipc).
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
try { try {
await this.bind(client, config.bindDn, config.bindPassword); 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 // Mandantengescopter Schreibpfad (T-16-02): app.current_tenant wird vor
// jedem group.create() gesetzt, RLS ist das zweite Netz. // jedem group.create() gesetzt, RLS ist das zweite Netz.
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
try { try {
await this.bind(client, config.bindDn, config.bindPassword); await this.bind(client, config.bindDn, config.bindPassword);
@@ -934,7 +934,7 @@ export class LdapService {
); );
// Create tenant-scoped Prisma client per Pitfall 2 // Create tenant-scoped Prisma client per Pitfall 2
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
try { try {
// 1. Bind with service account (anonymous when not configured) // 1. Bind with service account (anonymous when not configured)
@@ -1208,7 +1208,7 @@ export class LdapService {
tenantId: string, tenantId: string,
result: LdapSyncResult, result: LdapSyncResult,
): Promise<void> { ): Promise<void> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const boundGroups: { id: string; name: string; ldapDn: string | null }[] = const boundGroups: { id: string; name: string; ldapDn: string | null }[] =
await tenantPrisma.group.findMany({ await tenantPrisma.group.findMany({
@@ -1371,7 +1371,7 @@ export class LdapService {
tenantId: string, tenantId: string,
result: LdapSyncResult, result: LdapSyncResult,
): Promise<void> { ): Promise<void> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const candidates: { const candidates: {
id: string; id: string;
@@ -54,7 +54,7 @@ export class ModuleAccessService {
// bestehen: der Schalter ist weiterhin aus (#18), die Datenbankregel // bestehen: der Schalter ist weiterhin aus (#18), die Datenbankregel
// wirkt heute nicht, und die Anwendungspruefung ist bis zum // wirkt heute nicht, und die Anwendungspruefung ist bis zum
// Scharfschalten der einzige tatsaechliche Schutz. // 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') { if (role === 'ADMIN' || role === 'SUPER_ADMIN') {
const activations = await tenantPrisma.tenantModuleActivation.findMany({ const activations = await tenantPrisma.tenantModuleActivation.findMany({
@@ -142,7 +142,7 @@ export class ModuleAccessService {
// erzeugt ihren EIGENEN Klienten (dieselbe Konvention wie // erzeugt ihren EIGENEN Klienten (dieselbe Konvention wie
// `module-grants.service.ts`: gebundene Klienten werden nicht zwischen // `module-grants.service.ts`: gebundene Klienten werden nicht zwischen
// Methoden weitergereicht). Beide laufen wie bisher nebenlaeufig. // 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([ const [activations, accessibleIds] = await Promise.all([
tenantPrisma.tenantModuleActivation.findMany({ tenantPrisma.tenantModuleActivation.findMany({
where: { tenantId, isActive: true }, where: { tenantId, isActive: true },
@@ -47,7 +47,7 @@ export class ModuleRegistryService {
* Returns all active modules for a given tenant. * Returns all active modules for a given tenant.
*/ */
async findActiveForTenant(tenantId: string) { async findActiveForTenant(tenantId: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const activations = await tenantPrisma.tenantModuleActivation.findMany({ const activations = await tenantPrisma.tenantModuleActivation.findMany({
where: { where: {
tenantId, tenantId,
@@ -77,7 +77,7 @@ export class ModuleRegistryService {
throw new NotFoundException(`Module with id '${moduleId}' not found`); 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({ return tenantPrisma.tenantModuleActivation.upsert({
where: { where: {
tenantId_moduleId: { tenantId_moduleId: {
@@ -117,7 +117,7 @@ export class ModuleRegistryService {
// EIN gebundener Klient fuer beide Aktivierungszugriffe dieser Methode // EIN gebundener Klient fuer beide Aktivierungszugriffe dieser Methode
// (Lesen, Schreiben) — nicht ein Klient je Zugriff (260910-exd, // (Lesen, Schreiben) — nicht ein Klient je Zugriff (260910-exd,
// Aufgabe 3, dieselbe Konvention wie `module-access.service.ts`). // 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 // Check if activation record exists
const activation = await tenantPrisma.tenantModuleActivation.findUnique({ const activation = await tenantPrisma.tenantModuleActivation.findUnique({
@@ -172,7 +172,7 @@ export class ModuleRegistryService {
return false; return false;
} }
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const activation = await tenantPrisma.tenantModuleActivation.findUnique({ const activation = await tenantPrisma.tenantModuleActivation.findUnique({
where: { where: {
tenantId_moduleId: { tenantId_moduleId: {
+15 -13
View File
@@ -197,13 +197,11 @@ import { PrismaClient } from '@prisma/client';
export function forTenant(prisma: PrismaClient, tenantId: string, userId?: string) { export function forTenant(prisma: PrismaClient, tenantId: string, userId?: string) {
return prisma.$extends({ return prisma.$extends({
query: { query: {
$allOperations({ args, query }: { args: any; query: (args: any) => any }) { $allOperations({ args, query }) {
const setContext = (prisma as any) 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)`;
.$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) return prisma.$transaction([setContext, query(args)])
.$transaction([setContext, query(args)]) .then((results: unknown[]) => results[1]);
.then((results: any[]) => results[1]);
}, },
}, },
}); });
@@ -226,13 +224,11 @@ export function forTenant(prisma: PrismaClient, tenantId: string, userId?: strin
export function forSystem(prisma: PrismaClient) { export function forSystem(prisma: PrismaClient) {
return prisma.$extends({ return prisma.$extends({
query: { query: {
$allOperations({ args, query }: { args: any; query: (args: any) => any }) { $allOperations({ args, query }) {
const setContext = (prisma as any) const setContext = prisma.$executeRaw`SELECT set_config('app.system_context', 'true', true), set_config('app.current_tenant', '', true), set_config('app.current_user', '', true)`;
.$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) return prisma.$transaction([setContext, query(args)])
.$transaction([setContext, query(args)]) .then((results: unknown[]) => results[1]);
.then((results: any[]) => results[1]);
}, },
}, },
}); });
@@ -259,9 +255,15 @@ export function forSystem(prisma: PrismaClient) {
export function withTenantTransaction<T>( export function withTenantTransaction<T>(
prisma: PrismaClient, prisma: PrismaClient,
tenantId: string, 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<T>, fn: (tx: any) => Promise<T>,
): Promise<T> { ): Promise<T> {
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)`; await tx.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true), set_config('app.system_context', '', true)`;
return fn(tx); return fn(tx);
}); });
+4 -4
View File
@@ -41,7 +41,7 @@ export class SettingsService {
* Methode, wie die restlichen Anfragewege dieser Datei. * Methode, wie die restlichen Anfragewege dieser Datei.
*/ */
async getSmtpConfig(tenantId: string) { async getSmtpConfig(tenantId: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return tenantPrisma.smtpConfig.findUnique({ return tenantPrisma.smtpConfig.findUnique({
where: { tenantId }, where: { tenantId },
select: { select: {
@@ -61,7 +61,7 @@ export class SettingsService {
* Mandantengebunden (260911-gwh): EIN Klient `tenantPrisma`. * Mandantengebunden (260911-gwh): EIN Klient `tenantPrisma`.
*/ */
async saveSmtpConfig(tenantId: string, dto: SmtpConfigDto) { 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 // Determine the encrypted password to store
let encryptedPassword: string | undefined; let encryptedPassword: string | undefined;
@@ -104,7 +104,7 @@ export class SettingsService {
* schmalem `select` — das verschluesselte Kennwort wird hier nie geladen. * schmalem `select` — das verschluesselte Kennwort wird hier nie geladen.
*/ */
async getBugReportRecipient(tenantId: string): Promise<string | null> { async getBugReportRecipient(tenantId: string): Promise<string | null> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const row = await tenantPrisma.smtpConfig.findUnique({ const row = await tenantPrisma.smtpConfig.findUnique({
where: { tenantId }, where: { tenantId },
select: { bugReportRecipient: true }, select: { bugReportRecipient: true },
@@ -134,7 +134,7 @@ export class SettingsService {
fromAddress: string; fromAddress: string;
decryptedPassword: string | null; decryptedPassword: string | null;
} | null> { } | null> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
const config = await tenantPrisma.smtpConfig.findUnique({ const config = await tenantPrisma.smtpConfig.findUnique({
where: { tenantId }, where: { tenantId },
}); });
+3 -3
View File
@@ -68,7 +68,7 @@ export class TenantController {
const results: any[] = []; const results: any[] = [];
for (const tenant of tenants) { 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({ const userCount = await tenantPrisma.user.count({
where: { tenantId: tenant.id }, where: { tenantId: tenant.id },
}); });
@@ -99,7 +99,7 @@ export class TenantController {
throw new NotFoundException('Tenant not found'); 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({ const userCount = await tenantPrisma.user.count({
where: { tenantId: id }, where: { tenantId: id },
}); });
@@ -160,7 +160,7 @@ export class TenantController {
throw new NotFoundException('Tenant not found'); 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({ const activeUserCount = await tenantPrisma.user.count({
where: { tenantId: id, isActive: true }, where: { tenantId: id, isActive: true },
}); });
@@ -121,7 +121,7 @@ export class TenderDigestScheduler implements OnModuleInit {
// `distinct(['userId'])` liefert dann nur EINE der moeglichen // `distinct(['userId'])` liefert dann nur EINE der moeglichen
// tenantId-Werte je Nutzer, welche ist von der internen Zeilenreihenfolge // tenantId-Werte je Nutzer, welche ist von der internen Zeilenreihenfolge
// abhaengig. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md. // 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({ const candidates: { userId: string; tenantId: string }[] = await systemPrisma.tenderMatch.findMany({
where: { notifiedAt: null }, where: { notifiedAt: null },
select: { userId: true, tenantId: true }, select: { userId: true, tenantId: true },
@@ -145,7 +145,7 @@ export class TenderDigestScheduler implements OnModuleInit {
// `userId` sieht dieser Zugriff den ganzen Mandanten, exakt wie vor // `userId` sieht dieser Zugriff den ganzen Mandanten, exakt wie vor
// der Migration. Ein Systemkontext fuer Hintergrunddienste ist // der Migration. Ein Systemkontext fuer Hintergrunddienste ist
// Etappe 3c, nicht Teil dieser Aenderung. // 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({ const pref = await tenantPrisma.tenderNotificationPref.findUnique({
where: { userId }, where: { userId },
@@ -102,7 +102,7 @@ export class TenderEmailConfigService {
* by userId (T-17-01) — a user only ever reads their own mailbox. * by userId (T-17-01) — a user only ever reads their own mailbox.
*/ */
async getConfigForApi(userId: string, tenantId: string) { 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({ const safe = await tenantPrisma.tenderEmailConfig.findUnique({
where: { userId }, where: { userId },
select: EMAIL_CONFIG_SAFE_SELECT, select: EMAIL_CONFIG_SAFE_SELECT,
@@ -152,7 +152,7 @@ export class TenderEmailConfigService {
*/ */
async saveConfig(ctx: { userId: string; tenantId: string }, dto: TenderEmailConfigDto) { async saveConfig(ctx: { userId: string; tenantId: string }, dto: TenderEmailConfigDto) {
const { userId, tenantId } = ctx; const { userId, tenantId } = ctx;
const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; const tenantPrisma = forTenant(this.prisma, tenantId, userId);
let encryptedInboxCreds: string | undefined; let encryptedInboxCreds: string | undefined;
const credChanged = const credChanged =
@@ -240,7 +240,7 @@ export class TenderEmailConfigService {
if (!username || !password) { if (!username || !password) {
try { 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 } }); const existing = await tenantPrisma.tenderEmailConfig.findUnique({ where: { userId } });
if (existing?.encryptedInboxCreds) { if (existing?.encryptedInboxCreds) {
const stored = JSON.parse(this.crypto.decrypt(existing.encryptedInboxCreds)) as { const stored = JSON.parse(this.crypto.decrypt(existing.encryptedInboxCreds)) as {
@@ -72,7 +72,7 @@ export class TenderMatchingService {
// und wuerde stumm. Treffer-Anlage und Sofortmeldung bleiben je Profil // und wuerde stumm. Treffer-Anlage und Sofortmeldung bleiben je Profil
// GEBUNDEN (unten); der Katalog-Lesezugriff (`tender`, D-03) bleibt // GEBUNDEN (unten); der Katalog-Lesezugriff (`tender`, D-03) bleibt
// ungebunden. Eine LEERE Profilliste ist Nichtstun (keine Treffer). // ungebunden. Eine LEERE Profilliste ist Nichtstun (keine Treffer).
const systemPrisma = forSystem(this.prisma) as any; const systemPrisma = forSystem(this.prisma);
const savedSearches: Prisma.TenderSavedSearchGetPayload<Record<string, never>>[] = const savedSearches: Prisma.TenderSavedSearchGetPayload<Record<string, never>>[] =
await systemPrisma.tenderSavedSearch.findMany(); await systemPrisma.tenderSavedSearch.findMany();
@@ -95,7 +95,7 @@ export class TenderMatchingService {
// Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3) // Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3)
// — EIN gebundener Client je Profil, nicht je Treffer, sonst // — EIN gebundener Client je Profil, nicht je Treffer, sonst
// entstuende pro Zeile eine eigene Transaktion. // 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) { for (const hit of hits) {
await tenantPrisma.tenderMatch.upsert({ await tenantPrisma.tenderMatch.upsert({
@@ -134,7 +134,7 @@ export class TenderMatchingService {
try { try {
// Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3) // Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3)
// — EIN gebundener Client je Profil. // — 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({ const fresh = await tenantPrisma.tenderMatch.findMany({
where: { where: {
@@ -156,7 +156,7 @@ export class TenderMatchingService {
{ email: user.email }, { email: user.email },
profile.tenantId, profile.tenantId,
{ name: profile.name }, { name: profile.name },
fresh.map((match: { tender: unknown }) => match.tender), fresh.map((match) => match.tender),
); );
if (sent) { if (sent) {
@@ -45,7 +45,7 @@ export class TenderNotificationPrefService {
* autowrite needed to represent "using the default". * autowrite needed to represent "using the default".
*/ */
async getForUser(userId: string, tenantId: string): Promise<{ digestInterval: string }> { 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({ const existing = await tenantPrisma.tenderNotificationPref.findUnique({
where: { userId }, where: { userId },
}); });
@@ -63,7 +63,7 @@ export class TenderNotificationPrefService {
* than creating a new one. * than creating a new one.
*/ */
async setForUser(userId: string, tenantId: string, digestInterval: string) { 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 { try {
return await tenantPrisma.tenderNotificationPref.upsert({ return await tenantPrisma.tenderNotificationPref.upsert({
where: { userId }, where: { userId },
@@ -69,7 +69,7 @@ export class TenderRssFeedSourceService {
* Bindung nicht überflüssig, sondern das zweite Netz. * Bindung nicht überflüssig, sondern das zweite Netz.
*/ */
async listForUser(userId: string, tenantId: string) { 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({ return tenantPrisma.tenderRssFeedSource.findMany({
where: { OR: [{ userId: null }, { userId }] }, where: { OR: [{ userId: null }, { userId }] },
orderBy: { createdAt: 'asc' }, orderBy: { createdAt: 'asc' },
@@ -93,7 +93,7 @@ export class TenderRssFeedSourceService {
) { ) {
this.assertUrlAllowed(dto.url); 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({ const existingCount = await tenantPrisma.tenderRssFeedSource.count({
where: { userId: ctx.userId }, where: { userId: ctx.userId },
}); });
@@ -44,7 +44,7 @@ export class TenderSavedSearchService {
* strictly by userId (V4/IDOR) — a foreign userId sees nothing. * strictly by userId (V4/IDOR) — a foreign userId sees nothing.
*/ */
async list(userId: string, tenantId: string) { 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({ return tenantPrisma.tenderSavedSearch.findMany({
where: { userId }, where: { userId },
orderBy: { name: 'asc' }, orderBy: { name: 'asc' },
@@ -58,7 +58,7 @@ export class TenderSavedSearchService {
* users, since the uniqueness is scoped per-user. * users, since the uniqueness is scoped per-user.
*/ */
async create(userId: string, tenantId: string, dto: CreateSavedSearchDto) { 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 { try {
return await tenantPrisma.tenderSavedSearch.create({ return await tenantPrisma.tenderSavedSearch.create({
data: { data: {
@@ -87,7 +87,7 @@ export class TenderSavedSearchService {
* leaking whether another user's profile exists). * leaking whether another user's profile exists).
*/ */
async update(id: string, userId: string, tenantId: string, dto: UpdateSavedSearchDto) { 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({ const existing = await tenantPrisma.tenderSavedSearch.findUnique({
where: { id }, where: { id },
}); });
@@ -124,7 +124,7 @@ export class TenderSavedSearchService {
* update(). * update().
*/ */
async remove(id: string, userId: string, tenantId: string) { 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({ const existing = await tenantPrisma.tenderSavedSearch.findUnique({
where: { id }, where: { id },
}); });
@@ -75,7 +75,7 @@ export class TenderTriageService {
update.favoritedAt = dto.isFavorite ? now : null; update.favoritedAt = dto.isFavorite ? now : null;
} }
const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; const tenantPrisma = forTenant(this.prisma, tenantId, userId);
try { try {
return await tenantPrisma.tenderTriage.upsert({ return await tenantPrisma.tenderTriage.upsert({
where: { userId_tenderId: { userId, tenderId } }, where: { userId_tenderId: { userId, tenderId } },
@@ -111,7 +111,7 @@ export class TenderTriageService {
*/ */
async listForUser(userId: string, tenantId: string, tenderIds: string[]) { async listForUser(userId: string, tenantId: string, tenderIds: string[]) {
if (!tenderIds.length) return []; if (!tenderIds.length) return [];
const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; const tenantPrisma = forTenant(this.prisma, tenantId, userId);
return tenantPrisma.tenderTriage.findMany({ return tenantPrisma.tenderTriage.findMany({
where: { userId, tenderId: { in: tenderIds } }, where: { userId, tenderId: { in: tenderIds } },
}); });
@@ -123,7 +123,7 @@ export class TenderTriageService {
* tender-query.builder.ts's buildTenderWhere. * tender-query.builder.ts's buildTenderWhere.
*/ */
async favoriteIds(userId: string, tenantId: string): Promise<string[]> { async favoriteIds(userId: string, tenantId: string): Promise<string[]> {
const tenantPrisma = forTenant(this.prisma, tenantId, userId) as any; const tenantPrisma = forTenant(this.prisma, tenantId, userId);
const rows = await tenantPrisma.tenderTriage.findMany({ const rows = await tenantPrisma.tenderTriage.findMany({
where: { userId, isFavorite: true }, where: { userId, isFavorite: true },
select: { tenderId: true }, select: { tenderId: true },
+1 -1
View File
@@ -267,7 +267,7 @@ export class TendersController {
const { userId, tenantId } = this.extractTriageContext(req); const { userId, tenantId } = this.extractTriageContext(req);
const feeds = await this.tenderRssFeedSource.listForUser(userId, tenantId); const feeds = await this.tenderRssFeedSource.listForUser(userId, tenantId);
return feeds.map(({ userId: ownerUserId, ...rest }: any) => ({ return feeds.map(({ userId: ownerUserId, ...rest }) => ({
...rest, ...rest,
isPlatformWide: ownerUserId === null, isPlatformWide: ownerUserId === null,
})); }));
+1 -1
View File
@@ -104,7 +104,7 @@ export class AdminSeedService implements OnApplicationBootstrap {
// `user-ungebundenes-einfuegen-abgelehnt`): eine FRISCHE Installation // `user-ungebundenes-einfuegen-abgelehnt`): eine FRISCHE Installation
// haette ihren allerersten Administrator gar nicht anlegen koennen. // haette ihren allerersten Administrator gar nicht anlegen koennen.
const passwordHash = await argon2.hash(password); const passwordHash = await argon2.hash(password);
const tenantPrisma = forTenant(this.prisma, tenant.id) as any; const tenantPrisma = forTenant(this.prisma, tenant.id);
try { try {
await tenantPrisma.user.create({ await tenantPrisma.user.create({
data: { data: {
+5 -5
View File
@@ -92,7 +92,7 @@ export class UserController {
// Mandantenbedingung im where BLEIBT erhalten -- nicht entfernen mit // Mandantenbedingung im where BLEIBT erhalten -- nicht entfernen mit
// dem Argument, das mache jetzt die Datenbank; dieselbe Regel, die die // dem Argument, das mache jetzt die Datenbank; dieselbe Regel, die die
// Bereiche `tenders` und `dkv` aufgestellt haben. // 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({ return tenantPrisma.user.findMany({
where: { tenantId: currentUser.tenantId }, where: { tenantId: currentUser.tenantId },
select: { select: {
@@ -322,7 +322,7 @@ export class UserController {
// Persist relative path (relative to monorepo root) // Persist relative path (relative to monorepo root)
const relativePath = path.join('user-files', 'avatars', filename); 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({ await tenantPrisma.user.update({
where: { id: currentUser.id }, where: { id: currentUser.id },
data: { avatarPath: relativePath }, data: { avatarPath: relativePath },
@@ -337,7 +337,7 @@ export class UserController {
*/ */
@Delete('me/avatar') @Delete('me/avatar')
async deleteAvatar(@CurrentUser() currentUser: any) { 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({ const user = await tenantPrisma.user.findUnique({
where: { id: currentUser.id }, where: { id: currentUser.id },
select: { avatarPath: true }, select: { avatarPath: true },
@@ -372,7 +372,7 @@ export class UserController {
throw new BadRequestException('Invalid color format. Use hex (#rrggbb).'); 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({ await tenantPrisma.user.update({
where: { id: currentUser.id }, where: { id: currentUser.id },
data: { accentColor: body.color ?? null }, data: { accentColor: body.color ?? null },
@@ -391,7 +391,7 @@ export class UserController {
@CurrentUser() currentUser: any, @CurrentUser() currentUser: any,
@Res() res: Response, @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({ const user = await tenantPrisma.user.findUnique({
where: { id: currentUser.id }, where: { id: currentUser.id },
select: { avatarPath: true }, select: { avatarPath: true },
+7 -7
View File
@@ -61,7 +61,7 @@ export class UserService {
* (Aufgabe 1, `user-gebunden-nur-eigener-mandant`). * (Aufgabe 1, `user-gebunden-nur-eigener-mandant`).
*/ */
async findById(tenantId: string, id: string) { 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 } }); return tenantPrisma.user.findUnique({ where: { id } });
} }
@@ -105,7 +105,7 @@ export class UserService {
ldapDn?: string; ldapDn?: string;
}) { }) {
const { password, ...rest } = data; const { password, ...rest } = data;
const tenantPrisma = forTenant(this.prisma, data.tenantId) as any; const tenantPrisma = forTenant(this.prisma, data.tenantId);
let created: any; let created: any;
try { try {
@@ -168,7 +168,7 @@ export class UserService {
updateData.passwordHash = await argon2.hash(password); updateData.passwordHash = await argon2.hash(password);
} }
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
try { try {
return await tenantPrisma.user.update({ return await tenantPrisma.user.update({
where: { id }, where: { id },
@@ -189,7 +189,7 @@ export class UserService {
* Mandanten. * Mandanten.
*/ */
async deactivate(tenantId: string, id: string) { async deactivate(tenantId: string, id: string) {
const tenantPrisma = forTenant(this.prisma, tenantId) as any; const tenantPrisma = forTenant(this.prisma, tenantId);
return tenantPrisma.user.update({ return tenantPrisma.user.update({
where: { id }, where: { id },
data: { isActive: false }, data: { isActive: false },
@@ -200,7 +200,7 @@ export class UserService {
* Hard delete a user, gebunden an den uebergebenen Mandanten. * Hard delete a user, gebunden an den uebergebenen Mandanten.
*/ */
async delete(tenantId: string, id: string) { 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 } }); return tenantPrisma.user.delete({ where: { id } });
} }
@@ -233,7 +233,7 @@ export class UserService {
const results: any[] = []; const results: any[] = [];
for (const tenant of tenants) { 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({ const users = await tenantPrisma.user.findMany({
where: { tenantId: tenant.id }, where: { tenantId: tenant.id },
select: { select: {
@@ -265,7 +265,7 @@ export class UserService {
const tenants = await this.prisma.tenant.findMany({ select: { id: true } }); const tenants = await this.prisma.tenant.findMany({ select: { id: true } });
for (const tenant of tenants) { 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 } }); const user = await tenantPrisma.user.findUnique({ where: { id } });
if (user) { if (user) {
return user; return user;