refactor(quick-260921-m34): Aufgabe 2b - getypte Anfrage in elf Controllern, zwei Befunde gemeldet
(req as any) und @Req() req: any durch AuthenticatedRequest ersetzt in
dashboard, favorites, calendar, groups, module-grants, module-registry,
tenders, dkv, ldap, settings; @CurrentUser() in user.controller auf AuthUser.
Die abwehrenden Pruefungen ("No tenant context", "No user context") bleiben
lebendig, weil user auf dem Anfragetyp wahlfrei ist - genau das beschreibt
den Zustand auf oeffentlichen Wegen.
Nebengewinn ohne neue Zusicherungen: req.tenantId as string | undefined
(dkv, settings), file.buffer as Buffer und file.mimetype as string
(dkv, user) sind weggefallen, weil der Typ sie jetzt traegt.
BEFUND 1 (D-03, gemeldet) dashboard.controller.ts:74 alt: der Handler las
req.user?.role NACH extractContext und gab sie an getWidgets(role: Role)
weiter, das eine Rolle zwingend verlangt. Die Annahme "hier gibt es immer
einen Aufrufer" stimmt - die Pruefung "No user context" erzwingt sie -, aber
sie stand in einer anderen Methode, wo der Compiler sie nicht sehen konnte.
extractContext gibt die Rolle jetzt mit zurueck: keine neue Pruefung, kein
erfundener Wert, gleiche Reihenfolge, gleiche Meldungen.
BEFUND 2 (D-03, gemeldet) tenders.controller.ts:142: resolveRequestingTenantId
erklaerte string | undefined, liest aber req.tenantId, das TenantGuard fuer
einen SUPER_ADMIN ohne Mandanten auf null setzt. Die Erklaerung war also nie
vollstaendig. Erweitert auf string | null | undefined, und buildTenderWhere
nimmt string | null - beides nur Erklaerung, kein Verhalten: die Funktion
entscheidet seit jeher ueber Wahrheitswert und faellt bei beiden zu
(nur global sichtbare Ausschreibungen).
Fixtures in user.controller.spec.ts ergaenzt (username, mustChangePassword,
originalname, size). Testzahlen unveraendert.
noExplicitAny in apps/api/src: 137 -> 66. type-check 4/4, lint 5/5,
apps/api 72/1143, apps/web 73/531, tenant.guard.ts unveraendert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
Req,
|
Req,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Request } from 'express';
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { CalendarService } from './calendar.service';
|
import { CalendarService } from './calendar.service';
|
||||||
import { CreateCalendarSourceDto } from './dto/create-calendar-source.dto';
|
import { CreateCalendarSourceDto } from './dto/create-calendar-source.dto';
|
||||||
import { UpdateCalendarSourceDto } from './dto/update-calendar-source.dto';
|
import { UpdateCalendarSourceDto } from './dto/update-calendar-source.dto';
|
||||||
@@ -45,10 +45,10 @@ import { CalendarEventsQueryDto } from './dto/calendar-events-query.dto';
|
|||||||
export class CalendarController {
|
export class CalendarController {
|
||||||
constructor(private readonly calendarService: CalendarService) {}
|
constructor(private readonly calendarService: CalendarService) {}
|
||||||
|
|
||||||
private extractContext(req: Request) {
|
private extractContext(req: AuthenticatedRequest) {
|
||||||
const userId = (req as any).user?.id;
|
const userId = req.user?.id;
|
||||||
const tenantId =
|
const tenantId =
|
||||||
(req as any).tenantId ?? (req as any).user?.tenantId;
|
req.tenantId ?? req.user?.tenantId;
|
||||||
|
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
@@ -66,7 +66,7 @@ export class CalendarController {
|
|||||||
* T-05-09: Uses Prisma select to exclude credentials; returns hasCredentials boolean.
|
* T-05-09: Uses Prisma select to exclude credentials; returns hasCredentials boolean.
|
||||||
*/
|
*/
|
||||||
@Get('sources')
|
@Get('sources')
|
||||||
async getSources(@Req() req: Request) {
|
async getSources(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.calendarService.getSources(userId, tenantId);
|
return this.calendarService.getSources(userId, tenantId);
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ export class CalendarController {
|
|||||||
*/
|
*/
|
||||||
@Post('sources')
|
@Post('sources')
|
||||||
async addSource(
|
async addSource(
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Body() dto: CreateCalendarSourceDto,
|
@Body() dto: CreateCalendarSourceDto,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
@@ -93,7 +93,7 @@ export class CalendarController {
|
|||||||
@Patch('sources/:id')
|
@Patch('sources/:id')
|
||||||
async updateSource(
|
async updateSource(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Body() dto: UpdateCalendarSourceDto,
|
@Body() dto: UpdateCalendarSourceDto,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
@@ -107,7 +107,7 @@ export class CalendarController {
|
|||||||
@Delete('sources/:id')
|
@Delete('sources/:id')
|
||||||
async deleteSource(
|
async deleteSource(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.calendarService.deleteSource(id, userId, tenantId);
|
return this.calendarService.deleteSource(id, userId, tenantId);
|
||||||
@@ -132,7 +132,7 @@ export class CalendarController {
|
|||||||
@Post('sources/:id/test')
|
@Post('sources/:id/test')
|
||||||
async testSource(
|
async testSource(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.calendarService.testConnection(id, userId, tenantId);
|
return this.calendarService.testConnection(id, userId, tenantId);
|
||||||
@@ -144,7 +144,7 @@ export class CalendarController {
|
|||||||
*/
|
*/
|
||||||
@Get('events')
|
@Get('events')
|
||||||
async getEvents(
|
async getEvents(
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Query() query: CalendarEventsQueryDto,
|
@Query() query: CalendarEventsQueryDto,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Put,
|
Put,
|
||||||
Req,
|
Req,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Request } from 'express';
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { DashboardService } from './dashboard.service';
|
import { DashboardService } from './dashboard.service';
|
||||||
import { CreateSearchProviderDto } from './dto/create-search-provider.dto';
|
import { CreateSearchProviderDto } from './dto/create-search-provider.dto';
|
||||||
import { CreateWidgetDto } from './dto/create-widget.dto';
|
import { CreateWidgetDto } from './dto/create-widget.dto';
|
||||||
@@ -39,42 +39,53 @@ import { UpdateWidgetConfigDto } from './dto/update-widget-config.dto';
|
|||||||
export class DashboardController {
|
export class DashboardController {
|
||||||
constructor(private readonly dashboardService: DashboardService) {}
|
constructor(private readonly dashboardService: DashboardService) {}
|
||||||
|
|
||||||
private extractContext(req: Request) {
|
/**
|
||||||
const userId = (req as any).user?.id;
|
* BEFUND quick-260921-m34 (D-03, gemeldet nicht repariert): `getWidgets`
|
||||||
|
* las die Rolle vorher als `req.user?.role` NACH dieser Pruefung und gab
|
||||||
|
* sie an `DashboardService.getWidgets(role: Role)` weiter, das eine Rolle
|
||||||
|
* zwingend verlangt. Der Bestandscode nahm also an, dass an dieser Stelle
|
||||||
|
* immer ein Aufrufer vorliegt. Die Annahme stimmt — die Pruefung "No user
|
||||||
|
* context" direkt darunter erzwingt sie seit jeher —, aber der Compiler
|
||||||
|
* konnte die beiden Stellen nicht verbinden, weil sie in zwei Methoden
|
||||||
|
* standen. Deshalb gibt diese Methode die Rolle jetzt MIT zurueck: keine
|
||||||
|
* neue Pruefung, kein erfundener Wert, gleiche Reihenfolge, gleiche
|
||||||
|
* Meldungen, gleiches Verhalten — nur sichtbar statt angenommen.
|
||||||
|
*/
|
||||||
|
private extractContext(req: AuthenticatedRequest) {
|
||||||
|
const user = req.user;
|
||||||
const tenantId =
|
const tenantId =
|
||||||
(req as any).tenantId ?? (req as any).user?.tenantId;
|
req.tenantId ?? user?.tenantId;
|
||||||
|
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
}
|
}
|
||||||
if (!userId) {
|
if (!user?.id) {
|
||||||
throw new ForbiddenException('No user context');
|
throw new ForbiddenException('No user context');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { userId, tenantId };
|
return { userId: user.id, tenantId, role: user.role };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('layout')
|
@Get('layout')
|
||||||
async getLayout(@Req() req: Request) {
|
async getLayout(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.dashboardService.getLayout(userId, tenantId);
|
return this.dashboardService.getLayout(userId, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('layout')
|
@Put('layout')
|
||||||
async saveLayout(@Req() req: Request, @Body() dto: SaveLayoutDto) {
|
async saveLayout(@Req() req: AuthenticatedRequest, @Body() dto: SaveLayoutDto) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.dashboardService.saveLayout(userId, tenantId, dto);
|
return this.dashboardService.saveLayout(userId, tenantId, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('widgets')
|
@Get('widgets')
|
||||||
async getWidgets(@Req() req: Request) {
|
async getWidgets(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId, role } = this.extractContext(req);
|
||||||
const role = (req as any).user?.role;
|
|
||||||
return this.dashboardService.getWidgets(userId, tenantId, role);
|
return this.dashboardService.getWidgets(userId, tenantId, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('widgets')
|
@Post('widgets')
|
||||||
async addWidget(@Req() req: Request, @Body() dto: CreateWidgetDto) {
|
async addWidget(@Req() req: AuthenticatedRequest, @Body() dto: CreateWidgetDto) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.dashboardService.addWidget(userId, tenantId, dto);
|
return this.dashboardService.addWidget(userId, tenantId, dto);
|
||||||
}
|
}
|
||||||
@@ -82,7 +93,7 @@ export class DashboardController {
|
|||||||
@Patch('widgets/:id/config')
|
@Patch('widgets/:id/config')
|
||||||
async updateWidgetConfig(
|
async updateWidgetConfig(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Body() dto: UpdateWidgetConfigDto,
|
@Body() dto: UpdateWidgetConfigDto,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
@@ -92,7 +103,7 @@ export class DashboardController {
|
|||||||
@Delete('widgets/:id')
|
@Delete('widgets/:id')
|
||||||
async removeWidget(
|
async removeWidget(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.dashboardService.removeWidget(id, userId, tenantId);
|
return this.dashboardService.removeWidget(id, userId, tenantId);
|
||||||
@@ -101,14 +112,14 @@ export class DashboardController {
|
|||||||
// --- Search Providers (05-02, D-15) ---
|
// --- Search Providers (05-02, D-15) ---
|
||||||
|
|
||||||
@Get('search-providers')
|
@Get('search-providers')
|
||||||
async getSearchProviders(@Req() req: Request) {
|
async getSearchProviders(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.dashboardService.getSearchProviders(userId, tenantId);
|
return this.dashboardService.getSearchProviders(userId, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('search-providers')
|
@Post('search-providers')
|
||||||
async addSearchProvider(
|
async addSearchProvider(
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Body() dto: CreateSearchProviderDto,
|
@Body() dto: CreateSearchProviderDto,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
@@ -118,7 +129,7 @@ export class DashboardController {
|
|||||||
@Delete('search-providers/:id')
|
@Delete('search-providers/:id')
|
||||||
async removeSearchProvider(
|
async removeSearchProvider(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
return this.dashboardService.removeSearchProvider(id, userId, tenantId);
|
return this.dashboardService.removeSearchProvider(id, userId, tenantId);
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import {
|
|||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
|
import type {
|
||||||
|
AuthenticatedRequest,
|
||||||
|
UploadedFileLike,
|
||||||
|
} from '../auth/types/auth-user';
|
||||||
|
import type { Response } from 'express';
|
||||||
import { DkvSchedulerService } from './dkv-scheduler.service';
|
import { DkvSchedulerService } from './dkv-scheduler.service';
|
||||||
import { DkvService } from './dkv.service';
|
import { DkvService } from './dkv.service';
|
||||||
import { DkvConfigDto } from './dto/dkv-config.dto';
|
import { DkvConfigDto } from './dto/dkv-config.dto';
|
||||||
@@ -58,7 +63,7 @@ export class DkvController {
|
|||||||
/** GET /dkv/config — returns module config with username + hasPassword. 404 when not yet configured. */
|
/** GET /dkv/config — returns module config with username + hasPassword. 404 when not yet configured. */
|
||||||
@Get('config')
|
@Get('config')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async getConfig(@Req() req: any) {
|
async getConfig(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
const config = await this.dkvService.getConfigForApi(tenantId);
|
const config = await this.dkvService.getConfigForApi(tenantId);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@@ -75,7 +80,7 @@ export class DkvController {
|
|||||||
*/
|
*/
|
||||||
@Put('config')
|
@Put('config')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async saveConfig(@Req() req: any, @Body() dto: DkvConfigDto) {
|
async saveConfig(@Req() req: AuthenticatedRequest, @Body() dto: DkvConfigDto) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
const result = await this.dkvService.saveConfig(tenantId, dto);
|
const result = await this.dkvService.saveConfig(tenantId, dto);
|
||||||
|
|
||||||
@@ -94,7 +99,7 @@ export class DkvController {
|
|||||||
/** POST /dkv/check-now — immediately run the inbox processing pipeline. */
|
/** POST /dkv/check-now — immediately run the inbox processing pipeline. */
|
||||||
@Post('check-now')
|
@Post('check-now')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async checkNow(@Req() req: any) {
|
async checkNow(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
return this.dkvService.checkNow(tenantId);
|
return this.dkvService.checkNow(tenantId);
|
||||||
}
|
}
|
||||||
@@ -105,7 +110,7 @@ export class DkvController {
|
|||||||
*/
|
*/
|
||||||
@Post('test-connection')
|
@Post('test-connection')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async testConnection(@Req() req: any, @Body() dto: DkvConfigDto) {
|
async testConnection(@Req() req: AuthenticatedRequest, @Body() dto: DkvConfigDto) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
return this.dkvService.testConnection(tenantId, dto);
|
return this.dkvService.testConnection(tenantId, dto);
|
||||||
}
|
}
|
||||||
@@ -118,7 +123,7 @@ export class DkvController {
|
|||||||
*/
|
*/
|
||||||
@Get('history')
|
@Get('history')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async getHistory(@Req() req: any, @Query() query: DkvHistoryQueryDto) {
|
async getHistory(@Req() req: AuthenticatedRequest, @Query() query: DkvHistoryQueryDto) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
const page = query.page ?? 1;
|
const page = query.page ?? 1;
|
||||||
const limit = query.limit ?? 20;
|
const limit = query.limit ?? 20;
|
||||||
@@ -137,9 +142,9 @@ export class DkvController {
|
|||||||
@Get('exports/:filename')
|
@Get('exports/:filename')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async downloadExport(
|
async downloadExport(
|
||||||
@Req() req: any,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Param('filename') filename: string,
|
@Param('filename') filename: string,
|
||||||
@Res() res: any,
|
@Res() res: Response,
|
||||||
) {
|
) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
|
|
||||||
@@ -164,7 +169,7 @@ export class DkvController {
|
|||||||
/** GET /dkv/vehicles — list all vehicle master records for this tenant. */
|
/** GET /dkv/vehicles — list all vehicle master records for this tenant. */
|
||||||
@Get('vehicles')
|
@Get('vehicles')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async listVehicles(@Req() req: any) {
|
async listVehicles(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
return this.dkvService.listVehicles(tenantId);
|
return this.dkvService.listVehicles(tenantId);
|
||||||
}
|
}
|
||||||
@@ -172,7 +177,7 @@ export class DkvController {
|
|||||||
/** POST /dkv/vehicles — create a new vehicle master record. */
|
/** POST /dkv/vehicles — create a new vehicle master record. */
|
||||||
@Post('vehicles')
|
@Post('vehicles')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async createVehicle(@Req() req: any, @Body() dto: CreateVehicleDto) {
|
async createVehicle(@Req() req: AuthenticatedRequest, @Body() dto: CreateVehicleDto) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
return this.dkvService.createVehicle(tenantId, dto);
|
return this.dkvService.createVehicle(tenantId, dto);
|
||||||
}
|
}
|
||||||
@@ -181,7 +186,7 @@ export class DkvController {
|
|||||||
@Put('vehicles/:id')
|
@Put('vehicles/:id')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async updateVehicle(
|
async updateVehicle(
|
||||||
@Req() req: any,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() dto: UpdateVehicleDto,
|
@Body() dto: UpdateVehicleDto,
|
||||||
) {
|
) {
|
||||||
@@ -192,7 +197,7 @@ export class DkvController {
|
|||||||
/** DELETE /dkv/vehicles/:id — delete a vehicle master record. */
|
/** DELETE /dkv/vehicles/:id — delete a vehicle master record. */
|
||||||
@Delete('vehicles/:id')
|
@Delete('vehicles/:id')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async deleteVehicle(@Req() req: any, @Param('id') id: string) {
|
async deleteVehicle(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
return this.dkvService.deleteVehicle(tenantId, id);
|
return this.dkvService.deleteVehicle(tenantId, id);
|
||||||
}
|
}
|
||||||
@@ -213,8 +218,8 @@ export class DkvController {
|
|||||||
limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB — generous for any realistic vehicle list (WR-05)
|
limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB — generous for any realistic vehicle list (WR-05)
|
||||||
}))
|
}))
|
||||||
async importVehicles(
|
async importVehicles(
|
||||||
@Req() req: any,
|
@Req() req: AuthenticatedRequest,
|
||||||
@UploadedFile() file: any,
|
@UploadedFile() file: UploadedFileLike | undefined,
|
||||||
@Body('mode') mode: string,
|
@Body('mode') mode: string,
|
||||||
) {
|
) {
|
||||||
const tenantId = this._requireTenant(req);
|
const tenantId = this._requireTenant(req);
|
||||||
@@ -223,7 +228,7 @@ export class DkvController {
|
|||||||
throw new BadRequestException('No CSV file uploaded (field name must be "file")');
|
throw new BadRequestException('No CSV file uploaded (field name must be "file")');
|
||||||
}
|
}
|
||||||
|
|
||||||
const csvText = (file.buffer as Buffer).toString('utf-8');
|
const csvText = file.buffer.toString('utf-8');
|
||||||
const importMode = mode === 'replace' ? 'replace' : 'merge';
|
const importMode = mode === 'replace' ? 'replace' : 'merge';
|
||||||
|
|
||||||
return this.dkvService.importVehiclesCsv(tenantId, csvText, importMode);
|
return this.dkvService.importVehiclesCsv(tenantId, csvText, importMode);
|
||||||
@@ -232,8 +237,8 @@ export class DkvController {
|
|||||||
// ─── Private helpers ───────────────────────────────────────────────────────
|
// ─── Private helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Extract and validate tenantId from request; throw BadRequestException when absent. */
|
/** Extract and validate tenantId from request; throw BadRequestException when absent. */
|
||||||
private _requireTenant(req: any): string {
|
private _requireTenant(req: AuthenticatedRequest): string {
|
||||||
const tenantId = req.tenantId as string | undefined;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
Req,
|
Req,
|
||||||
Res,
|
Res,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Request, Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { CreateFavoriteDto } from './dto/create-favorite.dto';
|
import { CreateFavoriteDto } from './dto/create-favorite.dto';
|
||||||
import { ReorderFavoritesDto } from './dto/reorder-favorites.dto';
|
import { ReorderFavoritesDto } from './dto/reorder-favorites.dto';
|
||||||
import { UpdateFavoriteDto } from './dto/update-favorite.dto';
|
import { UpdateFavoriteDto } from './dto/update-favorite.dto';
|
||||||
@@ -46,10 +47,10 @@ import { FavoritesService } from './favorites.service';
|
|||||||
export class FavoritesController {
|
export class FavoritesController {
|
||||||
constructor(private readonly favoritesService: FavoritesService) {}
|
constructor(private readonly favoritesService: FavoritesService) {}
|
||||||
|
|
||||||
private extractContext(req: Request) {
|
private extractContext(req: AuthenticatedRequest) {
|
||||||
const userId = (req as any).user?.id;
|
const userId = req.user?.id;
|
||||||
const tenantId =
|
const tenantId =
|
||||||
(req as any).tenantId ?? (req as any).user?.tenantId;
|
req.tenantId ?? req.user?.tenantId;
|
||||||
|
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
@@ -64,7 +65,7 @@ export class FavoritesController {
|
|||||||
@Get()
|
@Get()
|
||||||
async list(
|
async list(
|
||||||
@Query('widgetId', ParseUUIDPipe) widgetId: string,
|
@Query('widgetId', ParseUUIDPipe) widgetId: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ export class FavoritesController {
|
|||||||
@Post()
|
@Post()
|
||||||
async create(
|
async create(
|
||||||
@Body() dto: CreateFavoriteDto,
|
@Body() dto: CreateFavoriteDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ export class FavoritesController {
|
|||||||
@Put('order')
|
@Put('order')
|
||||||
async reorder(
|
async reorder(
|
||||||
@Body() dto: ReorderFavoritesDto,
|
@Body() dto: ReorderFavoritesDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ export class FavoritesController {
|
|||||||
@Get(':id/icon')
|
@Get(':id/icon')
|
||||||
async getIcon(
|
async getIcon(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
@@ -135,7 +136,7 @@ export class FavoritesController {
|
|||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() dto: UpdateFavoriteDto,
|
@Body() dto: UpdateFavoriteDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ export class FavoritesController {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
async remove(
|
async remove(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractContext(req);
|
const { userId, tenantId } = this.extractContext(req);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
import { Request } from 'express';
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||||
import { AddGroupMembersDto } from './dto/add-group-members.dto';
|
import { AddGroupMembersDto } from './dto/add-group-members.dto';
|
||||||
@@ -30,8 +30,8 @@ import { GroupsService } from './groups.service';
|
|||||||
export class GroupsController {
|
export class GroupsController {
|
||||||
constructor(private readonly groupsService: GroupsService) {}
|
constructor(private readonly groupsService: GroupsService) {}
|
||||||
|
|
||||||
private getTenantId(req: Request): string {
|
private getTenantId(req: AuthenticatedRequest): string {
|
||||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ export class GroupsController {
|
|||||||
@Get()
|
@Get()
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async list(@Req() req: Request) {
|
async list(@Req() req: AuthenticatedRequest) {
|
||||||
return this.groupsService.listForTenant(this.getTenantId(req));
|
return this.groupsService.listForTenant(this.getTenantId(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export class GroupsController {
|
|||||||
@Post()
|
@Post()
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async create(@Body() dto: CreateGroupDto, @Req() req: Request) {
|
async create(@Body() dto: CreateGroupDto, @Req() req: AuthenticatedRequest) {
|
||||||
return this.groupsService.create(this.getTenantId(req), dto);
|
return this.groupsService.create(this.getTenantId(req), dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ export class GroupsController {
|
|||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() dto: UpdateGroupDto,
|
@Body() dto: UpdateGroupDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
return this.groupsService.update(this.getTenantId(req), id, dto);
|
return this.groupsService.update(this.getTenantId(req), id, dto);
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ export class GroupsController {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async remove(@Param('id') id: string, @Req() req: Request) {
|
async remove(@Param('id') id: string, @Req() req: AuthenticatedRequest) {
|
||||||
return this.groupsService.remove(this.getTenantId(req), id);
|
return this.groupsService.remove(this.getTenantId(req), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export class GroupsController {
|
|||||||
@Get(':id/impact')
|
@Get(':id/impact')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async impact(@Param('id') id: string, @Req() req: Request) {
|
async impact(@Param('id') id: string, @Req() req: AuthenticatedRequest) {
|
||||||
return this.groupsService.getImpact(this.getTenantId(req), id);
|
return this.groupsService.getImpact(this.getTenantId(req), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ export class GroupsController {
|
|||||||
@Get(':id/members')
|
@Get(':id/members')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async members(@Param('id') id: string, @Req() req: Request) {
|
async members(@Param('id') id: string, @Req() req: AuthenticatedRequest) {
|
||||||
return this.groupsService.listMembers(this.getTenantId(req), id);
|
return this.groupsService.listMembers(this.getTenantId(req), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ export class GroupsController {
|
|||||||
async addMembers(
|
async addMembers(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() dto: AddGroupMembersDto,
|
@Body() dto: AddGroupMembersDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
return this.groupsService.addMembers(this.getTenantId(req), id, dto.userIds);
|
return this.groupsService.addMembers(this.getTenantId(req), id, dto.userIds);
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ export class GroupsController {
|
|||||||
async removeMember(
|
async removeMember(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Param('userId') userId: string,
|
@Param('userId') userId: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
await this.groupsService.removeMember(this.getTenantId(req), id, userId);
|
await this.groupsService.removeMember(this.getTenantId(req), id, userId);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
import { Request } from 'express';
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||||
import { CreateModuleGrantDto } from './dto/create-module-grant.dto';
|
import { CreateModuleGrantDto } from './dto/create-module-grant.dto';
|
||||||
@@ -31,8 +31,8 @@ import { ModuleGrantsService } from './module-grants.service';
|
|||||||
export class ModuleGrantsController {
|
export class ModuleGrantsController {
|
||||||
constructor(private readonly moduleGrantsService: ModuleGrantsService) {}
|
constructor(private readonly moduleGrantsService: ModuleGrantsService) {}
|
||||||
|
|
||||||
private getTenantId(req: Request): string {
|
private getTenantId(req: AuthenticatedRequest): string {
|
||||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ export class ModuleGrantsController {
|
|||||||
@Get('matrix')
|
@Get('matrix')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async matrix(@Req() req: Request) {
|
async matrix(@Req() req: AuthenticatedRequest) {
|
||||||
return this.moduleGrantsService.getMatrix(this.getTenantId(req));
|
return this.moduleGrantsService.getMatrix(this.getTenantId(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ export class ModuleGrantsController {
|
|||||||
@Get('users/:userId')
|
@Get('users/:userId')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async userAccess(@Param('userId') userId: string, @Req() req: Request) {
|
async userAccess(@Param('userId') userId: string, @Req() req: AuthenticatedRequest) {
|
||||||
return this.moduleGrantsService.getUserAccess(this.getTenantId(req), userId);
|
return this.moduleGrantsService.getUserAccess(this.getTenantId(req), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export class ModuleGrantsController {
|
|||||||
@Post()
|
@Post()
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async create(@Body() dto: CreateModuleGrantDto, @Req() req: Request) {
|
async create(@Body() dto: CreateModuleGrantDto, @Req() req: AuthenticatedRequest) {
|
||||||
return this.moduleGrantsService.grant(this.getTenantId(req), dto);
|
return this.moduleGrantsService.grant(this.getTenantId(req), dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ export class ModuleGrantsController {
|
|||||||
@Delete()
|
@Delete()
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async remove(@Body() dto: CreateModuleGrantDto, @Req() req: Request) {
|
async remove(@Body() dto: CreateModuleGrantDto, @Req() req: AuthenticatedRequest) {
|
||||||
await this.moduleGrantsService.revoke(this.getTenantId(req), dto);
|
await this.moduleGrantsService.revoke(this.getTenantId(req), dto);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import {
|
import {
|
||||||
CreateFieldMappingDto,
|
CreateFieldMappingDto,
|
||||||
CreateLdapConfigDto,
|
CreateLdapConfigDto,
|
||||||
@@ -41,7 +42,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Get('config')
|
@Get('config')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async getConfig(@Req() req: any) {
|
async getConfig(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -65,7 +66,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Post('config')
|
@Post('config')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async createConfig(@Req() req: any, @Body() dto: CreateLdapConfigDto) {
|
async createConfig(@Req() req: AuthenticatedRequest, @Body() dto: CreateLdapConfigDto) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -92,7 +93,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Patch('config')
|
@Patch('config')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async updateConfig(@Req() req: any, @Body() dto: UpdateLdapConfigDto) {
|
async updateConfig(@Req() req: AuthenticatedRequest, @Body() dto: UpdateLdapConfigDto) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -123,7 +124,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Post('test-connection')
|
@Post('test-connection')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async testConnection(@Req() req: any, @Body() dto: TestConnectionDto) {
|
async testConnection(@Req() req: AuthenticatedRequest, @Body() dto: TestConnectionDto) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -158,7 +159,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Get('groups')
|
@Get('groups')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async listGroups(@Req() req: any) {
|
async listGroups(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -189,7 +190,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Post('groups/import')
|
@Post('groups/import')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async importGroups(@Req() req: any, @Body() dto: ImportGroupsDto) {
|
async importGroups(@Req() req: AuthenticatedRequest, @Body() dto: ImportGroupsDto) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -225,7 +226,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Get('users/search')
|
@Get('users/search')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async searchUsers(@Req() req: any, @Query('q') q: string) {
|
async searchUsers(@Req() req: AuthenticatedRequest, @Query('q') q: string) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -256,7 +257,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Post('users/import')
|
@Post('users/import')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async importUsers(@Req() req: any, @Body() dto: ImportUsersDto) {
|
async importUsers(@Req() req: AuthenticatedRequest, @Body() dto: ImportUsersDto) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -292,7 +293,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Post('sync')
|
@Post('sync')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async triggerSync(@Req() req: any) {
|
async triggerSync(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -326,7 +327,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Post('config/mappings')
|
@Post('config/mappings')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async addFieldMapping(@Req() req: any, @Body() dto: CreateFieldMappingDto) {
|
async addFieldMapping(@Req() req: AuthenticatedRequest, @Body() dto: CreateFieldMappingDto) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
@@ -351,7 +352,7 @@ export class LdapController {
|
|||||||
*/
|
*/
|
||||||
@Delete('config/mappings/:id')
|
@Delete('config/mappings/:id')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async removeFieldMapping(@Req() req: any, @Param('id') id: string) {
|
async removeFieldMapping(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||||
const tenantId = req.tenantId;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
import { Request } from 'express';
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||||
import { ModuleAccessService } from './module-access.service';
|
import { ModuleAccessService } from './module-access.service';
|
||||||
@@ -53,10 +53,10 @@ export class ModuleRegistryController {
|
|||||||
* catalog (mandantenweite Sicht, kein Benutzerfilter).
|
* catalog (mandantenweite Sicht, kein Benutzerfilter).
|
||||||
*/
|
*/
|
||||||
@Get('active')
|
@Get('active')
|
||||||
async findActive(@Req() req: Request) {
|
async findActive(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||||
const userId = (req as any).user?.id;
|
const userId = req.user?.id;
|
||||||
const role = (req as any).user?.role;
|
const role = req.user?.role;
|
||||||
if (!tenantId || !userId || !role) {
|
if (!tenantId || !userId || !role) {
|
||||||
throw new ForbiddenException('No user context');
|
throw new ForbiddenException('No user context');
|
||||||
}
|
}
|
||||||
@@ -74,10 +74,10 @@ export class ModuleRegistryController {
|
|||||||
* Erreichbar für jeden authentifizierten Benutzer, wie GET /modules.
|
* Erreichbar für jeden authentifizierten Benutzer, wie GET /modules.
|
||||||
*/
|
*/
|
||||||
@Get('catalog')
|
@Get('catalog')
|
||||||
async findCatalog(@Req() req: Request) {
|
async findCatalog(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||||
const userId = (req as any).user?.id;
|
const userId = req.user?.id;
|
||||||
const role = (req as any).user?.role;
|
const role = req.user?.role;
|
||||||
if (!tenantId || !userId || !role) {
|
if (!tenantId || !userId || !role) {
|
||||||
throw new ForbiddenException('No user context');
|
throw new ForbiddenException('No user context');
|
||||||
}
|
}
|
||||||
@@ -105,9 +105,9 @@ export class ModuleRegistryController {
|
|||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async activate(
|
async activate(
|
||||||
@Param('moduleId') moduleId: string,
|
@Param('moduleId') moduleId: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
}
|
}
|
||||||
@@ -124,9 +124,9 @@ export class ModuleRegistryController {
|
|||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async deactivate(
|
async deactivate(
|
||||||
@Param('moduleId') moduleId: string,
|
@Param('moduleId') moduleId: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { SmtpConfigDto } from './dto/smtp-config.dto';
|
import { SmtpConfigDto } from './dto/smtp-config.dto';
|
||||||
import { SettingsService } from './settings.service';
|
import { SettingsService } from './settings.service';
|
||||||
|
|
||||||
@@ -29,8 +30,8 @@ export class SettingsController {
|
|||||||
*/
|
*/
|
||||||
@Get('smtp')
|
@Get('smtp')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async getSmtpConfig(@Req() req: any) {
|
async getSmtpConfig(@Req() req: AuthenticatedRequest) {
|
||||||
const tenantId = req.tenantId as string | undefined;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
}
|
}
|
||||||
@@ -50,8 +51,8 @@ export class SettingsController {
|
|||||||
*/
|
*/
|
||||||
@Put('smtp')
|
@Put('smtp')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async saveSmtpConfig(@Req() req: any, @Body() dto: SmtpConfigDto) {
|
async saveSmtpConfig(@Req() req: AuthenticatedRequest, @Body() dto: SmtpConfigDto) {
|
||||||
const tenantId = req.tenantId as string | undefined;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
}
|
}
|
||||||
@@ -67,8 +68,8 @@ export class SettingsController {
|
|||||||
*/
|
*/
|
||||||
@Post('smtp/test')
|
@Post('smtp/test')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async testSmtpConfig(@Req() req: any, @Body() dto: SmtpConfigDto) {
|
async testSmtpConfig(@Req() req: AuthenticatedRequest, @Body() dto: SmtpConfigDto) {
|
||||||
const tenantId = req.tenantId as string | undefined;
|
const tenantId = req.tenantId;
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new BadRequestException('No tenant context');
|
throw new BadRequestException('No tenant context');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const MAX_FAV_IDS = 500;
|
|||||||
export function buildTenderWhere(
|
export function buildTenderWhere(
|
||||||
dto: TenderQueryDto,
|
dto: TenderQueryDto,
|
||||||
favIds?: string[],
|
favIds?: string[],
|
||||||
ownerTenantId?: string,
|
ownerTenantId?: string | null,
|
||||||
): Prisma.TenderWhereInput {
|
): Prisma.TenderWhereInput {
|
||||||
const where: Prisma.TenderWhereInput = {};
|
const where: Prisma.TenderWhereInput = {};
|
||||||
const AND: Prisma.TenderWhereInput[] = [];
|
const AND: Prisma.TenderWhereInput[] = [];
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Req,
|
Req,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
import { Request } from 'express';
|
import type { AuthenticatedRequest } from '../auth/types/auth-user';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
import { UseModule } from '../module-registry/module.guard';
|
import { UseModule } from '../module-registry/module.guard';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
@@ -115,10 +115,10 @@ export class TendersController {
|
|||||||
* (`roles.guard.ts`) — one single spot in this controller resolves
|
* (`roles.guard.ts`) — one single spot in this controller resolves
|
||||||
* account data from the request.
|
* account data from the request.
|
||||||
*/
|
*/
|
||||||
private extractTriageContext(req: Request) {
|
private extractTriageContext(req: AuthenticatedRequest) {
|
||||||
const userId = (req as any).user?.id;
|
const userId = req.user?.id;
|
||||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||||
const role = (req as any).user?.role;
|
const role = req.user?.role;
|
||||||
|
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw new ForbiddenException('No tenant context');
|
throw new ForbiddenException('No tenant context');
|
||||||
@@ -138,8 +138,10 @@ export class TendersController {
|
|||||||
* only" (fail-closed, tender-query.builder.ts) rather than 403 when no
|
* only" (fail-closed, tender-query.builder.ts) rather than 403 when no
|
||||||
* tenant context is present.
|
* tenant context is present.
|
||||||
*/
|
*/
|
||||||
private resolveRequestingTenantId(req?: Request): string | undefined {
|
private resolveRequestingTenantId(
|
||||||
return (req as any)?.user?.tenantId ?? (req as any)?.tenantId;
|
req?: AuthenticatedRequest,
|
||||||
|
): string | null | undefined {
|
||||||
|
return req?.user?.tenantId ?? req?.tenantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Global read (ModuleGuard-gated, NOT tenant-scoped) ────────────────────
|
// ─── Global read (ModuleGuard-gated, NOT tenant-scoped) ────────────────────
|
||||||
@@ -164,7 +166,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Get()
|
@Get()
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async listTenders(@Query() query: TenderQueryDto, @Req() req?: Request) {
|
async listTenders(@Query() query: TenderQueryDto, @Req() req?: AuthenticatedRequest) {
|
||||||
const page = query.page ?? 1;
|
const page = query.page ?? 1;
|
||||||
const limit = query.limit ?? 20;
|
const limit = query.limit ?? 20;
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
@@ -175,7 +177,7 @@ export class TendersController {
|
|||||||
// optional type only accommodates unit tests that call this method
|
// optional type only accommodates unit tests that call this method
|
||||||
// directly without favOnly set (T-11-10: extractTriageContext
|
// directly without favOnly set (T-11-10: extractTriageContext
|
||||||
// throws ForbiddenException if req/user context is genuinely absent).
|
// throws ForbiddenException if req/user context is genuinely absent).
|
||||||
const { userId, tenantId } = this.extractTriageContext(req as Request);
|
const { userId, tenantId } = this.extractTriageContext(req as AuthenticatedRequest);
|
||||||
favIds = await this.tenderTriage.favoriteIds(userId, tenantId);
|
favIds = await this.tenderTriage.favoriteIds(userId, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +265,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Get('rss-feeds')
|
@Get('rss-feeds')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async listRssFeeds(@Req() req: Request) {
|
async listRssFeeds(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
const feeds = await this.tenderRssFeedSource.listForUser(userId, tenantId);
|
const feeds = await this.tenderRssFeedSource.listForUser(userId, tenantId);
|
||||||
|
|
||||||
@@ -286,7 +288,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Post('rss-feeds')
|
@Post('rss-feeds')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async createRssFeed(@Body() dto: TenderRssFeedDto, @Req() req: Request) {
|
async createRssFeed(@Body() dto: TenderRssFeedDto, @Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId, role } = this.extractTriageContext(req);
|
const { userId, tenantId, role } = this.extractTriageContext(req);
|
||||||
|
|
||||||
if (dto.scope === 'platform') {
|
if (dto.scope === 'platform') {
|
||||||
@@ -319,7 +321,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Delete('rss-feeds/:feedId')
|
@Delete('rss-feeds/:feedId')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async removeRssFeed(@Param('feedId') feedId: string, @Req() req: Request) {
|
async removeRssFeed(@Param('feedId') feedId: string, @Req() req: AuthenticatedRequest) {
|
||||||
const { userId, role } = this.extractTriageContext(req);
|
const { userId, role } = this.extractTriageContext(req);
|
||||||
const isAdmin = role === Role.ADMIN || role === Role.SUPER_ADMIN;
|
const isAdmin = role === Role.ADMIN || role === Role.SUPER_ADMIN;
|
||||||
return this.tenderRssFeedSource.remove(feedId, { userId, isAdmin });
|
return this.tenderRssFeedSource.remove(feedId, { userId, isAdmin });
|
||||||
@@ -342,7 +344,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Get('email-config')
|
@Get('email-config')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async getEmailConfig(@Req() req: Request) {
|
async getEmailConfig(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderEmailConfig.getConfigForApi(userId, tenantId);
|
return this.tenderEmailConfig.getConfigForApi(userId, tenantId);
|
||||||
}
|
}
|
||||||
@@ -356,7 +358,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Put('email-config')
|
@Put('email-config')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async saveEmailConfig(@Body() dto: TenderEmailConfigDto, @Req() req: Request) {
|
async saveEmailConfig(@Body() dto: TenderEmailConfigDto, @Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderEmailConfig.saveConfig({ userId, tenantId }, dto);
|
return this.tenderEmailConfig.saveConfig({ userId, tenantId }, dto);
|
||||||
}
|
}
|
||||||
@@ -381,7 +383,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Post('email-config/test')
|
@Post('email-config/test')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async testEmailConnection(@Body() dto: TenderEmailConfigDto, @Req() req: Request) {
|
async testEmailConnection(@Body() dto: TenderEmailConfigDto, @Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderEmailConfig.testConnection(userId, tenantId, dto);
|
return this.tenderEmailConfig.testConnection(userId, tenantId, dto);
|
||||||
}
|
}
|
||||||
@@ -456,7 +458,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Get('triage')
|
@Get('triage')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async listTriage(@Query('ids') ids: string | undefined, @Req() req: Request) {
|
async listTriage(@Query('ids') ids: string | undefined, @Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
const tenderIds = (ids ?? '')
|
const tenderIds = (ids ?? '')
|
||||||
.split(',')
|
.split(',')
|
||||||
@@ -480,7 +482,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Put('triage')
|
@Put('triage')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async setTriage(@Body() dto: TenderTriageDto, @Req() req: Request) {
|
async setTriage(@Body() dto: TenderTriageDto, @Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
|
|
||||||
return this.tenderTriage.setTriage(userId, tenantId, dto.tenderId, {
|
return this.tenderTriage.setTriage(userId, tenantId, dto.tenderId, {
|
||||||
@@ -502,7 +504,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Get('saved-searches')
|
@Get('saved-searches')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async listSavedSearches(@Req() req: Request) {
|
async listSavedSearches(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderSavedSearch.list(userId, tenantId);
|
return this.tenderSavedSearch.list(userId, tenantId);
|
||||||
}
|
}
|
||||||
@@ -517,7 +519,7 @@ export class TendersController {
|
|||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async createSavedSearch(
|
async createSavedSearch(
|
||||||
@Body() dto: CreateSavedSearchDto,
|
@Body() dto: CreateSavedSearchDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderSavedSearch.create(userId, tenantId, dto);
|
return this.tenderSavedSearch.create(userId, tenantId, dto);
|
||||||
@@ -535,7 +537,7 @@ export class TendersController {
|
|||||||
async updateSavedSearch(
|
async updateSavedSearch(
|
||||||
@Param('searchId') searchId: string,
|
@Param('searchId') searchId: string,
|
||||||
@Body() dto: UpdateSavedSearchDto,
|
@Body() dto: UpdateSavedSearchDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderSavedSearch.update(searchId, userId, tenantId, dto);
|
return this.tenderSavedSearch.update(searchId, userId, tenantId, dto);
|
||||||
@@ -550,7 +552,7 @@ export class TendersController {
|
|||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async removeSavedSearch(
|
async removeSavedSearch(
|
||||||
@Param('searchId') searchId: string,
|
@Param('searchId') searchId: string,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
await this.tenderSavedSearch.remove(searchId, userId, tenantId);
|
await this.tenderSavedSearch.remove(searchId, userId, tenantId);
|
||||||
@@ -571,7 +573,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Get('notification-pref')
|
@Get('notification-pref')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async getNotificationPref(@Req() req: Request) {
|
async getNotificationPref(@Req() req: AuthenticatedRequest) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderNotificationPref.getForUser(userId, tenantId);
|
return this.tenderNotificationPref.getForUser(userId, tenantId);
|
||||||
}
|
}
|
||||||
@@ -586,7 +588,7 @@ export class TendersController {
|
|||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async setNotificationPref(
|
async setNotificationPref(
|
||||||
@Body() dto: UpdateNotificationPrefDto,
|
@Body() dto: UpdateNotificationPrefDto,
|
||||||
@Req() req: Request,
|
@Req() req: AuthenticatedRequest,
|
||||||
) {
|
) {
|
||||||
const { userId, tenantId } = this.extractTriageContext(req);
|
const { userId, tenantId } = this.extractTriageContext(req);
|
||||||
return this.tenderNotificationPref.setForUser(userId, tenantId, dto.digestInterval);
|
return this.tenderNotificationPref.setForUser(userId, tenantId, dto.digestInterval);
|
||||||
@@ -606,7 +608,7 @@ export class TendersController {
|
|||||||
*/
|
*/
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@UseModule('tender-radar')
|
@UseModule('tender-radar')
|
||||||
async getTender(@Param('id') id: string, @Req() req?: Request) {
|
async getTender(@Param('id') id: string, @Req() req?: AuthenticatedRequest) {
|
||||||
const tender = await this.prisma.tender.findUnique({
|
const tender = await this.prisma.tender.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ describe('UserController', () => {
|
|||||||
lastLoginAt: null,
|
lastLoginAt: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await controller.findAll({ role: Role.ADMIN, tenantId: 't1', id: 'admin1' });
|
const result = await controller.findAll({ role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false });
|
||||||
|
|
||||||
expect(result.map((u: any) => u.username)).toEqual(['alice']);
|
expect(result.map((u: any) => u.username)).toEqual(['alice']);
|
||||||
expectBoundCall(prisma, 't1', 'user', 'findMany');
|
expectBoundCall(prisma, 't1', 'user', 'findMany');
|
||||||
@@ -170,6 +170,8 @@ describe('UserController', () => {
|
|||||||
role: Role.SUPER_ADMIN,
|
role: Role.SUPER_ADMIN,
|
||||||
tenantId: 't1',
|
tenantId: 't1',
|
||||||
id: 'super1',
|
id: 'super1',
|
||||||
|
username: 'super1',
|
||||||
|
mustChangePassword: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(expected);
|
expect(result).toBe(expected);
|
||||||
@@ -183,11 +185,11 @@ describe('UserController', () => {
|
|||||||
const targetUser = { id: 'u-x', username: 'x', tenantId: 't1' };
|
const targetUser = { id: 'u-x', username: 'x', tenantId: 't1' };
|
||||||
|
|
||||||
userService.findById.mockResolvedValue(targetUser);
|
userService.findById.mockResolvedValue(targetUser);
|
||||||
await controller.findOne('u-x', { role: Role.ADMIN, tenantId: 't1', id: 'admin1' });
|
await controller.findOne('u-x', { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false });
|
||||||
expect(userService.findById).toHaveBeenCalledWith('t1', 'u-x');
|
expect(userService.findById).toHaveBeenCalledWith('t1', 'u-x');
|
||||||
|
|
||||||
userService.findByIdForPlatformAdmin.mockResolvedValue(targetUser);
|
userService.findByIdForPlatformAdmin.mockResolvedValue(targetUser);
|
||||||
await controller.findOne('u-x', { role: Role.SUPER_ADMIN, tenantId: 't2', id: 'super1' });
|
await controller.findOne('u-x', { role: Role.SUPER_ADMIN, tenantId: 't2', id: 'super1', username: 'super1', mustChangePassword: false });
|
||||||
expect(userService.findByIdForPlatformAdmin).toHaveBeenCalledWith('u-x');
|
expect(userService.findByIdForPlatformAdmin).toHaveBeenCalledWith('u-x');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,14 +197,14 @@ describe('UserController', () => {
|
|||||||
userService.findById.mockResolvedValue(null);
|
userService.findById.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
controller.findOne('u-foreign', { role: Role.ADMIN, tenantId: 't1', id: 'admin1' }),
|
controller.findOne('u-foreign', { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false }),
|
||||||
).rejects.toBeInstanceOf(NotFoundException);
|
).rejects.toBeInstanceOf(NotFoundException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('Test 5: ein Mandanten-Administrator kann weiterhin keine oberste Rolle vergeben, und die Anlage eines Benutzers landet weiterhin im Mandanten des Aufrufers, wenn dieser nicht die oberste Rolle trägt', async () => {
|
it('Test 5: ein Mandanten-Administrator kann weiterhin keine oberste Rolle vergeben, und die Anlage eines Benutzers landet weiterhin im Mandanten des Aufrufers, wenn dieser nicht die oberste Rolle trägt', async () => {
|
||||||
const currentUser = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const currentUser = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
controller.create(
|
controller.create(
|
||||||
@@ -224,7 +226,7 @@ describe('UserController', () => {
|
|||||||
|
|
||||||
describe('remove — Selbstlöschriegel (Befund H)', () => {
|
describe('remove — Selbstlöschriegel (Befund H)', () => {
|
||||||
it('Test 6: der Riegel gegen das Löschen des eigenen Kontos greift', async () => {
|
it('Test 6: der Riegel gegen das Löschen des eigenen Kontos greift', async () => {
|
||||||
const currentUser = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const currentUser = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
userService.findById.mockResolvedValue({ id: 'admin1', tenantId: 't1' });
|
userService.findById.mockResolvedValue({ id: 'admin1', tenantId: 't1' });
|
||||||
|
|
||||||
await expect(controller.remove('admin1', currentUser)).rejects.toBeInstanceOf(
|
await expect(controller.remove('admin1', currentUser)).rejects.toBeInstanceOf(
|
||||||
@@ -235,7 +237,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Selbstbedienungswege (Befund G)', () => {
|
describe('Selbstbedienungswege (Befund G)', () => {
|
||||||
const currentUser = { role: Role.USER, tenantId: 't1', id: 'me' };
|
const currentUser = { role: Role.USER, tenantId: 't1', id: 'me', username: 'me', mustChangePassword: false };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
prisma.__seedUser({
|
prisma.__seedUser({
|
||||||
@@ -247,7 +249,10 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 7: alle fünf Zugriffe der vier Selbstbedienungswege stehen gebunden im Protokoll, mit der Mandantenkennung aus dem Sitzungsnachweis', async () => {
|
it('Test 7: alle fünf Zugriffe der vier Selbstbedienungswege stehen gebunden im Protokoll, mit der Mandantenkennung aus dem Sitzungsnachweis', async () => {
|
||||||
await controller.uploadAvatar({ buffer: Buffer.from('x'), mimetype: 'image/png' }, currentUser);
|
await controller.uploadAvatar(
|
||||||
|
{ buffer: Buffer.from('x'), mimetype: 'image/png', originalname: 'x.png', size: 1 },
|
||||||
|
currentUser,
|
||||||
|
);
|
||||||
await controller.deleteAvatar(currentUser);
|
await controller.deleteAvatar(currentUser);
|
||||||
await controller.updateAccentColor({ color: '#ff00aa' }, currentUser);
|
await controller.updateAccentColor({ color: '#ff00aa' }, currentUser);
|
||||||
|
|
||||||
@@ -280,7 +285,7 @@ describe('UserController', () => {
|
|||||||
|
|
||||||
describe('update/remove — Zielrolle SUPER_ADMIN (WINDOWS #29)', () => {
|
describe('update/remove — Zielrolle SUPER_ADMIN (WINDOWS #29)', () => {
|
||||||
it('Test 9: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten weder übernehmen (Kennwort setzen), noch aussperren (isActive=false), noch herabstufen (role=USER) — alle drei Angriffsformen werden mit der Zielrollen-Ausnahme abgelehnt, und der Dienst wird in keinem der drei Fälle aufgerufen', async () => {
|
it('Test 9: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten weder übernehmen (Kennwort setzen), noch aussperren (isActive=false), noch herabstufen (role=USER) — alle drei Angriffsformen werden mit der Zielrollen-Ausnahme abgelehnt, und der Dienst wird in keinem der drei Fälle aufgerufen', async () => {
|
||||||
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
userService.findById.mockResolvedValue({ id: 'boss', tenantId: 't1', role: Role.SUPER_ADMIN });
|
userService.findById.mockResolvedValue({ id: 'boss', tenantId: 't1', role: Role.SUPER_ADMIN });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
@@ -300,7 +305,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 10: ein SUPER_ADMIN kann einen anderen SUPER_ADMIN weiterhin ändern — der Zielrollen-Riegel gilt nur für Nicht-SUPER_ADMIN-Aufrufer', async () => {
|
it('Test 10: ein SUPER_ADMIN kann einen anderen SUPER_ADMIN weiterhin ändern — der Zielrollen-Riegel gilt nur für Nicht-SUPER_ADMIN-Aufrufer', async () => {
|
||||||
const superAdmin = { role: Role.SUPER_ADMIN, tenantId: 't1', id: 'super1' };
|
const superAdmin = { role: Role.SUPER_ADMIN, tenantId: 't1', id: 'super1', username: 'super1', mustChangePassword: false };
|
||||||
userService.findByIdForPlatformAdmin.mockResolvedValue({
|
userService.findByIdForPlatformAdmin.mockResolvedValue({
|
||||||
id: 'boss',
|
id: 'boss',
|
||||||
tenantId: 't1',
|
tenantId: 't1',
|
||||||
@@ -324,7 +329,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 11: ein Mandanten-Administrator kann einen USER seines Mandanten weiterhin ändern — Regressionsschutz, der Zielrollen-Riegel engt bestehende Wege nicht zusätzlich ein', async () => {
|
it('Test 11: ein Mandanten-Administrator kann einen USER seines Mandanten weiterhin ändern — Regressionsschutz, der Zielrollen-Riegel engt bestehende Wege nicht zusätzlich ein', async () => {
|
||||||
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
userService.findById.mockResolvedValue({ id: 'u1', tenantId: 't1', role: Role.USER });
|
userService.findById.mockResolvedValue({ id: 'u1', tenantId: 't1', role: Role.USER });
|
||||||
userService.update.mockResolvedValue({ id: 'u1', tenantId: 't1', role: Role.USER });
|
userService.update.mockResolvedValue({ id: 'u1', tenantId: 't1', role: Role.USER });
|
||||||
|
|
||||||
@@ -338,7 +343,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 12: die Mandantengrenze wird VOR der Zielrollen-Prüfung geprüft — ein Administrator, der (bei einer fehlerhaften Auflösung) ein Ziel eines fremden Mandanten mit der obersten Rolle erhält, bekommt die Mandanten-Meldung, nicht die Zielrollen-Meldung, und erfährt so nichts über die Rolle des fremden Benutzers', async () => {
|
it('Test 12: die Mandantengrenze wird VOR der Zielrollen-Prüfung geprüft — ein Administrator, der (bei einer fehlerhaften Auflösung) ein Ziel eines fremden Mandanten mit der obersten Rolle erhält, bekommt die Mandanten-Meldung, nicht die Zielrollen-Meldung, und erfährt so nichts über die Rolle des fremden Benutzers', async () => {
|
||||||
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
userService.findById.mockResolvedValue({ id: 'boss2', tenantId: 't2', role: Role.SUPER_ADMIN });
|
userService.findById.mockResolvedValue({ id: 'boss2', tenantId: 't2', role: Role.SUPER_ADMIN });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
@@ -348,7 +353,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 13: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten nicht löschen — die Zielrollen-Ausnahme greift, und der Dienst wird nicht aufgerufen', async () => {
|
it('Test 13: ein Mandanten-Administrator kann den SUPER_ADMIN des eigenen Mandanten nicht löschen — die Zielrollen-Ausnahme greift, und der Dienst wird nicht aufgerufen', async () => {
|
||||||
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
userService.findById.mockResolvedValue({ id: 'boss', tenantId: 't1', role: Role.SUPER_ADMIN });
|
userService.findById.mockResolvedValue({ id: 'boss', tenantId: 't1', role: Role.SUPER_ADMIN });
|
||||||
|
|
||||||
await expect(controller.remove('boss', admin)).rejects.toThrow(
|
await expect(controller.remove('boss', admin)).rejects.toThrow(
|
||||||
@@ -358,7 +363,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 14: ein SUPER_ADMIN kann einen anderen SUPER_ADMIN weiterhin löschen — der Zielrollen-Riegel gilt nur für Nicht-SUPER_ADMIN-Aufrufer', async () => {
|
it('Test 14: ein SUPER_ADMIN kann einen anderen SUPER_ADMIN weiterhin löschen — der Zielrollen-Riegel gilt nur für Nicht-SUPER_ADMIN-Aufrufer', async () => {
|
||||||
const superAdmin = { role: Role.SUPER_ADMIN, tenantId: 't1', id: 'super1' };
|
const superAdmin = { role: Role.SUPER_ADMIN, tenantId: 't1', id: 'super1', username: 'super1', mustChangePassword: false };
|
||||||
userService.findByIdForPlatformAdmin.mockResolvedValue({
|
userService.findByIdForPlatformAdmin.mockResolvedValue({
|
||||||
id: 'boss',
|
id: 'boss',
|
||||||
tenantId: 't1',
|
tenantId: 't1',
|
||||||
@@ -373,7 +378,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 15: ein Mandanten-Administrator kann einen USER seines Mandanten weiterhin löschen — Regressionsschutz', async () => {
|
it('Test 15: ein Mandanten-Administrator kann einen USER seines Mandanten weiterhin löschen — Regressionsschutz', async () => {
|
||||||
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
userService.findById.mockResolvedValue({ id: 'u1', tenantId: 't1', role: Role.USER });
|
userService.findById.mockResolvedValue({ id: 'u1', tenantId: 't1', role: Role.USER });
|
||||||
userService.delete.mockResolvedValue({ message: 'User deleted' });
|
userService.delete.mockResolvedValue({ message: 'User deleted' });
|
||||||
|
|
||||||
@@ -383,7 +388,7 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test 16: die Mandantengrenze wird VOR der Zielrollen-Prüfung geprüft — beim Löschen bekommt ein Administrator mit einem fremdmandantigen Ziel der obersten Rolle die Mandanten-Meldung, nicht die Zielrollen-Meldung', async () => {
|
it('Test 16: die Mandantengrenze wird VOR der Zielrollen-Prüfung geprüft — beim Löschen bekommt ein Administrator mit einem fremdmandantigen Ziel der obersten Rolle die Mandanten-Meldung, nicht die Zielrollen-Meldung', async () => {
|
||||||
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1' };
|
const admin = { role: Role.ADMIN, tenantId: 't1', id: 'admin1', username: 'admin1', mustChangePassword: false };
|
||||||
userService.findById.mockResolvedValue({ id: 'boss2', tenantId: 't2', role: Role.SUPER_ADMIN });
|
userService.findById.mockResolvedValue({ id: 'boss2', tenantId: 't2', role: Role.SUPER_ADMIN });
|
||||||
|
|
||||||
await expect(controller.remove('boss2', admin)).rejects.toThrow(
|
await expect(controller.remove('boss2', admin)).rejects.toThrow(
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import * as path from 'node:path';
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
|
import type { AuthUser, UploadedFileLike } from '../auth/types/auth-user';
|
||||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
@@ -65,7 +66,7 @@ export class UserController {
|
|||||||
* uebergreifende Sicht von der mandantengebundenen unterscheidet, und sie
|
* uebergreifende Sicht von der mandantengebundenen unterscheidet, und sie
|
||||||
* darf nicht eingeebnet werden.
|
* darf nicht eingeebnet werden.
|
||||||
*/
|
*/
|
||||||
private async resolveTargetUser(currentUser: any, id: string) {
|
private async resolveTargetUser(currentUser: AuthUser, id: string) {
|
||||||
if (currentUser.role === Role.SUPER_ADMIN) {
|
if (currentUser.role === Role.SUPER_ADMIN) {
|
||||||
return this.userService.findByIdForPlatformAdmin(id);
|
return this.userService.findByIdForPlatformAdmin(id);
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,7 @@ export class UserController {
|
|||||||
*/
|
*/
|
||||||
@Get()
|
@Get()
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async findAll(@CurrentUser() currentUser: any) {
|
async findAll(@CurrentUser() currentUser: AuthUser) {
|
||||||
if (currentUser.role === Role.SUPER_ADMIN) {
|
if (currentUser.role === Role.SUPER_ADMIN) {
|
||||||
// Plattform-Administratorsicht (Befund F): die bestehende, gewollte
|
// Plattform-Administratorsicht (Befund F): die bestehende, gewollte
|
||||||
// Funktion der obersten Rolle bleibt erhalten, laeuft aber ueber die
|
// Funktion der obersten Rolle bleibt erhalten, laeuft aber ueber die
|
||||||
@@ -115,7 +116,7 @@ export class UserController {
|
|||||||
*/
|
*/
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async findOne(@Param('id') id: string, @CurrentUser() currentUser: any) {
|
async findOne(@Param('id') id: string, @CurrentUser() currentUser: AuthUser) {
|
||||||
const user = await this.resolveTargetUser(currentUser, id);
|
const user = await this.resolveTargetUser(currentUser, id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException('User not found');
|
throw new NotFoundException('User not found');
|
||||||
@@ -139,7 +140,7 @@ export class UserController {
|
|||||||
*/
|
*/
|
||||||
@Post()
|
@Post()
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async create(@Body() dto: CreateUserDto, @CurrentUser() currentUser: any) {
|
async create(@Body() dto: CreateUserDto, @CurrentUser() currentUser: AuthUser) {
|
||||||
// ADMIN can only create users in own tenant
|
// ADMIN can only create users in own tenant
|
||||||
const tenantId =
|
const tenantId =
|
||||||
currentUser.role === Role.SUPER_ADMIN && dto.tenantId
|
currentUser.role === Role.SUPER_ADMIN && dto.tenantId
|
||||||
@@ -173,7 +174,7 @@ export class UserController {
|
|||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() dto: UpdateUserDto,
|
@Body() dto: UpdateUserDto,
|
||||||
@CurrentUser() currentUser: any,
|
@CurrentUser() currentUser: AuthUser,
|
||||||
) {
|
) {
|
||||||
const user = await this.resolveTargetUser(currentUser, id);
|
const user = await this.resolveTargetUser(currentUser, id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -228,7 +229,7 @@ export class UserController {
|
|||||||
*/
|
*/
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||||
async remove(@Param('id') id: string, @CurrentUser() currentUser: any) {
|
async remove(@Param('id') id: string, @CurrentUser() currentUser: AuthUser) {
|
||||||
const user = await this.resolveTargetUser(currentUser, id);
|
const user = await this.resolveTargetUser(currentUser, id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException('User not found');
|
throw new NotFoundException('User not found');
|
||||||
@@ -290,14 +291,14 @@ export class UserController {
|
|||||||
FileInterceptor('file', { limits: { fileSize: 2 * 1024 * 1024 } }),
|
FileInterceptor('file', { limits: { fileSize: 2 * 1024 * 1024 } }),
|
||||||
)
|
)
|
||||||
async uploadAvatar(
|
async uploadAvatar(
|
||||||
@UploadedFile() file: any,
|
@UploadedFile() file: UploadedFileLike | undefined,
|
||||||
@CurrentUser() currentUser: any,
|
@CurrentUser() currentUser: AuthUser,
|
||||||
) {
|
) {
|
||||||
if (!file?.buffer) {
|
if (!file?.buffer) {
|
||||||
throw new BadRequestException('No file provided');
|
throw new BadRequestException('No file provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const ext = AVATAR_MIME_TO_EXT[file.mimetype as string];
|
const ext = AVATAR_MIME_TO_EXT[file.mimetype];
|
||||||
if (!ext) {
|
if (!ext) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
'Invalid file type. Allowed: image/png, image/jpeg, image/webp',
|
'Invalid file type. Allowed: image/png, image/jpeg, image/webp',
|
||||||
@@ -318,7 +319,7 @@ export class UserController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(filePath, file.buffer as Buffer);
|
fs.writeFileSync(filePath, file.buffer);
|
||||||
|
|
||||||
// Persist relative path (relative to monorepo root)
|
// Persist relative path (relative to monorepo root)
|
||||||
const relativePath = path.join('user-files', 'avatars', filename);
|
const relativePath = path.join('user-files', 'avatars', filename);
|
||||||
@@ -336,7 +337,7 @@ export class UserController {
|
|||||||
* Remove the current user's profile picture.
|
* Remove the current user's profile picture.
|
||||||
*/
|
*/
|
||||||
@Delete('me/avatar')
|
@Delete('me/avatar')
|
||||||
async deleteAvatar(@CurrentUser() currentUser: any) {
|
async deleteAvatar(@CurrentUser() currentUser: AuthUser) {
|
||||||
const tenantPrisma = forTenant(this.prisma, currentUser.tenantId);
|
const tenantPrisma = forTenant(this.prisma, currentUser.tenantId);
|
||||||
const user = await tenantPrisma.user.findUnique({
|
const user = await tenantPrisma.user.findUnique({
|
||||||
where: { id: currentUser.id },
|
where: { id: currentUser.id },
|
||||||
@@ -366,7 +367,7 @@ export class UserController {
|
|||||||
@Patch('me/accent-color')
|
@Patch('me/accent-color')
|
||||||
async updateAccentColor(
|
async updateAccentColor(
|
||||||
@Body() body: { color: string | null },
|
@Body() body: { color: string | null },
|
||||||
@CurrentUser() currentUser: any,
|
@CurrentUser() currentUser: AuthUser,
|
||||||
) {
|
) {
|
||||||
if (body.color !== null && body.color !== undefined && !/^#[0-9a-fA-F]{6}$/.test(body.color)) {
|
if (body.color !== null && body.color !== undefined && !/^#[0-9a-fA-F]{6}$/.test(body.color)) {
|
||||||
throw new BadRequestException('Invalid color format. Use hex (#rrggbb).');
|
throw new BadRequestException('Invalid color format. Use hex (#rrggbb).');
|
||||||
@@ -388,7 +389,7 @@ export class UserController {
|
|||||||
*/
|
*/
|
||||||
@Get('me/avatar')
|
@Get('me/avatar')
|
||||||
async getAvatar(
|
async getAvatar(
|
||||||
@CurrentUser() currentUser: any,
|
@CurrentUser() currentUser: AuthUser,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
) {
|
) {
|
||||||
const tenantPrisma = forTenant(this.prisma, currentUser.tenantId);
|
const tenantPrisma = forTenant(this.prisma, currentUser.tenantId);
|
||||||
|
|||||||
Reference in New Issue
Block a user