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:
2026-09-21 17:08:44 +02:00
parent f2fc39f51c
commit 7c9d7c1223
13 changed files with 182 additions and 155 deletions
+10 -10
View File
@@ -10,7 +10,7 @@ import {
Query,
Req,
} from '@nestjs/common';
import { Request } from 'express';
import type { AuthenticatedRequest } from '../auth/types/auth-user';
import { CalendarService } from './calendar.service';
import { CreateCalendarSourceDto } from './dto/create-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 {
constructor(private readonly calendarService: CalendarService) {}
private extractContext(req: Request) {
const userId = (req as any).user?.id;
private extractContext(req: AuthenticatedRequest) {
const userId = req.user?.id;
const tenantId =
(req as any).tenantId ?? (req as any).user?.tenantId;
req.tenantId ?? req.user?.tenantId;
if (!tenantId) {
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.
*/
@Get('sources')
async getSources(@Req() req: Request) {
async getSources(@Req() req: AuthenticatedRequest) {
const { userId, tenantId } = this.extractContext(req);
return this.calendarService.getSources(userId, tenantId);
}
@@ -78,7 +78,7 @@ export class CalendarController {
*/
@Post('sources')
async addSource(
@Req() req: Request,
@Req() req: AuthenticatedRequest,
@Body() dto: CreateCalendarSourceDto,
) {
const { userId, tenantId } = this.extractContext(req);
@@ -93,7 +93,7 @@ export class CalendarController {
@Patch('sources/:id')
async updateSource(
@Param('id') id: string,
@Req() req: Request,
@Req() req: AuthenticatedRequest,
@Body() dto: UpdateCalendarSourceDto,
) {
const { userId, tenantId } = this.extractContext(req);
@@ -107,7 +107,7 @@ export class CalendarController {
@Delete('sources/:id')
async deleteSource(
@Param('id') id: string,
@Req() req: Request,
@Req() req: AuthenticatedRequest,
) {
const { userId, tenantId } = this.extractContext(req);
return this.calendarService.deleteSource(id, userId, tenantId);
@@ -132,7 +132,7 @@ export class CalendarController {
@Post('sources/:id/test')
async testSource(
@Param('id') id: string,
@Req() req: Request,
@Req() req: AuthenticatedRequest,
) {
const { userId, tenantId } = this.extractContext(req);
return this.calendarService.testConnection(id, userId, tenantId);
@@ -144,7 +144,7 @@ export class CalendarController {
*/
@Get('events')
async getEvents(
@Req() req: Request,
@Req() req: AuthenticatedRequest,
@Query() query: CalendarEventsQueryDto,
) {
const { userId, tenantId } = this.extractContext(req);