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:
@@ -44,7 +44,7 @@ const MAX_FAV_IDS = 500;
|
||||
export function buildTenderWhere(
|
||||
dto: TenderQueryDto,
|
||||
favIds?: string[],
|
||||
ownerTenantId?: string,
|
||||
ownerTenantId?: string | null,
|
||||
): Prisma.TenderWhereInput {
|
||||
const where: Prisma.TenderWhereInput = {};
|
||||
const AND: Prisma.TenderWhereInput[] = [];
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
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 { UseModule } from '../module-registry/module.guard';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
@@ -115,10 +115,10 @@ export class TendersController {
|
||||
* (`roles.guard.ts`) — one single spot in this controller resolves
|
||||
* account data from the request.
|
||||
*/
|
||||
private extractTriageContext(req: Request) {
|
||||
const userId = (req as any).user?.id;
|
||||
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
|
||||
const role = (req as any).user?.role;
|
||||
private extractTriageContext(req: AuthenticatedRequest) {
|
||||
const userId = req.user?.id;
|
||||
const tenantId = req.tenantId ?? req.user?.tenantId;
|
||||
const role = req.user?.role;
|
||||
|
||||
if (!tenantId) {
|
||||
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
|
||||
* tenant context is present.
|
||||
*/
|
||||
private resolveRequestingTenantId(req?: Request): string | undefined {
|
||||
return (req as any)?.user?.tenantId ?? (req as any)?.tenantId;
|
||||
private resolveRequestingTenantId(
|
||||
req?: AuthenticatedRequest,
|
||||
): string | null | undefined {
|
||||
return req?.user?.tenantId ?? req?.tenantId;
|
||||
}
|
||||
|
||||
// ─── Global read (ModuleGuard-gated, NOT tenant-scoped) ────────────────────
|
||||
@@ -164,7 +166,7 @@ export class TendersController {
|
||||
*/
|
||||
@Get()
|
||||
@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 limit = query.limit ?? 20;
|
||||
const skip = (page - 1) * limit;
|
||||
@@ -175,7 +177,7 @@ export class TendersController {
|
||||
// optional type only accommodates unit tests that call this method
|
||||
// directly without favOnly set (T-11-10: extractTriageContext
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -263,7 +265,7 @@ export class TendersController {
|
||||
*/
|
||||
@Get('rss-feeds')
|
||||
@UseModule('tender-radar')
|
||||
async listRssFeeds(@Req() req: Request) {
|
||||
async listRssFeeds(@Req() req: AuthenticatedRequest) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
const feeds = await this.tenderRssFeedSource.listForUser(userId, tenantId);
|
||||
|
||||
@@ -286,7 +288,7 @@ export class TendersController {
|
||||
*/
|
||||
@Post('rss-feeds')
|
||||
@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);
|
||||
|
||||
if (dto.scope === 'platform') {
|
||||
@@ -319,7 +321,7 @@ export class TendersController {
|
||||
*/
|
||||
@Delete('rss-feeds/:feedId')
|
||||
@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 isAdmin = role === Role.ADMIN || role === Role.SUPER_ADMIN;
|
||||
return this.tenderRssFeedSource.remove(feedId, { userId, isAdmin });
|
||||
@@ -342,7 +344,7 @@ export class TendersController {
|
||||
*/
|
||||
@Get('email-config')
|
||||
@UseModule('tender-radar')
|
||||
async getEmailConfig(@Req() req: Request) {
|
||||
async getEmailConfig(@Req() req: AuthenticatedRequest) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
return this.tenderEmailConfig.getConfigForApi(userId, tenantId);
|
||||
}
|
||||
@@ -356,7 +358,7 @@ export class TendersController {
|
||||
*/
|
||||
@Put('email-config')
|
||||
@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);
|
||||
return this.tenderEmailConfig.saveConfig({ userId, tenantId }, dto);
|
||||
}
|
||||
@@ -381,7 +383,7 @@ export class TendersController {
|
||||
*/
|
||||
@Post('email-config/test')
|
||||
@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);
|
||||
return this.tenderEmailConfig.testConnection(userId, tenantId, dto);
|
||||
}
|
||||
@@ -456,7 +458,7 @@ export class TendersController {
|
||||
*/
|
||||
@Get('triage')
|
||||
@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 tenderIds = (ids ?? '')
|
||||
.split(',')
|
||||
@@ -480,7 +482,7 @@ export class TendersController {
|
||||
*/
|
||||
@Put('triage')
|
||||
@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);
|
||||
|
||||
return this.tenderTriage.setTriage(userId, tenantId, dto.tenderId, {
|
||||
@@ -502,7 +504,7 @@ export class TendersController {
|
||||
*/
|
||||
@Get('saved-searches')
|
||||
@UseModule('tender-radar')
|
||||
async listSavedSearches(@Req() req: Request) {
|
||||
async listSavedSearches(@Req() req: AuthenticatedRequest) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
return this.tenderSavedSearch.list(userId, tenantId);
|
||||
}
|
||||
@@ -517,7 +519,7 @@ export class TendersController {
|
||||
@UseModule('tender-radar')
|
||||
async createSavedSearch(
|
||||
@Body() dto: CreateSavedSearchDto,
|
||||
@Req() req: Request,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
return this.tenderSavedSearch.create(userId, tenantId, dto);
|
||||
@@ -535,7 +537,7 @@ export class TendersController {
|
||||
async updateSavedSearch(
|
||||
@Param('searchId') searchId: string,
|
||||
@Body() dto: UpdateSavedSearchDto,
|
||||
@Req() req: Request,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
return this.tenderSavedSearch.update(searchId, userId, tenantId, dto);
|
||||
@@ -550,7 +552,7 @@ export class TendersController {
|
||||
@UseModule('tender-radar')
|
||||
async removeSavedSearch(
|
||||
@Param('searchId') searchId: string,
|
||||
@Req() req: Request,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
await this.tenderSavedSearch.remove(searchId, userId, tenantId);
|
||||
@@ -571,7 +573,7 @@ export class TendersController {
|
||||
*/
|
||||
@Get('notification-pref')
|
||||
@UseModule('tender-radar')
|
||||
async getNotificationPref(@Req() req: Request) {
|
||||
async getNotificationPref(@Req() req: AuthenticatedRequest) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
return this.tenderNotificationPref.getForUser(userId, tenantId);
|
||||
}
|
||||
@@ -586,7 +588,7 @@ export class TendersController {
|
||||
@UseModule('tender-radar')
|
||||
async setNotificationPref(
|
||||
@Body() dto: UpdateNotificationPrefDto,
|
||||
@Req() req: Request,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
return this.tenderNotificationPref.setForUser(userId, tenantId, dto.digestInterval);
|
||||
@@ -606,7 +608,7 @@ export class TendersController {
|
||||
*/
|
||||
@Get(':id')
|
||||
@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({
|
||||
where: { id },
|
||||
include: {
|
||||
|
||||
Reference in New Issue
Block a user