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;
}
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<void> {
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<void> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any;
const tenantPrisma = forTenant(this.prisma, tenantId);
const user = await tenantPrisma.user.findUnique({
where: { id: userId },
});
@@ -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 },
+16 -16
View File
@@ -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<void> {
// 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<ReturnType<typeof this.parser.parsePdf>> | 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]));
+6 -6
View File
@@ -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) {
+23 -24
View File
@@ -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<boolean> {
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 },
});
+16 -18
View File
@@ -48,7 +48,7 @@ export class ModuleGrantsService {
groupId?: string,
userId?: string,
): Promise<void> {
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<string, string[]>();
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),
+9 -9
View File
@@ -68,7 +68,7 @@ export class LdapConfigService implements OnApplicationBootstrap {
*/
async onApplicationBootstrap(): Promise<void> {
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));
}
}
+8 -8
View File
@@ -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<void> {
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<void> {
const tenantPrisma = forTenant(this.prisma, tenantId) as any;
const tenantPrisma = forTenant(this.prisma, tenantId);
const candidates: {
id: string;
@@ -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 },
@@ -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: {
+15 -13
View File
@@ -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<T>(
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<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)`;
return fn(tx);
});
+4 -4
View File
@@ -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<string | null> {
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 },
});
+3 -3
View File
@@ -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 },
});
@@ -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 },
@@ -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 {
@@ -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<Record<string, never>>[] =
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) {
@@ -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 },
@@ -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 },
});
@@ -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 },
});
@@ -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<string[]> {
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 },
+1 -1
View File
@@ -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,
}));
+1 -1
View File
@@ -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: {
+5 -5
View File
@@ -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 },
+7 -7
View File
@@ -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;