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:
@@ -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 },
|
||||
|
||||
@@ -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]));
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user