refactor(quick-260921-m34): Aufgabe 2a - gemeinsamer Aufrufer-Typ, aus den Signierstellen abgeleitet

apps/api/src/auth/types/auth-user.ts angelegt: AuthUser, AuthenticatedRequest,
LocalAuthenticatedRequest, LoginUser, JwtPayload, UploadedFileLike. Jedes Feld
traegt seine Herkunft als Kommentar.

tenantId ist string, hergeleitet und nicht gewaehlt: die Spalte User.tenantId
ist in schema.prisma Pflicht, beide Signierstellen schreiben genau sie, und
der Bestand beschreibt dasselbe Objekt in SessionUser schon so. Der
SUPER_ADMIN-Zweig in TenantGuard spricht nicht dagegen - der Waechter liest
AuthUser gar nicht, und dass es den Zweig gibt, steht als null in
AuthenticatedRequest.tenantId weiter im Typsystem. tenant.guard.ts bleibt
unberuehrt.

role ist die Aufzaehlung Role: schema.prisma deklariert die Spalte so, die
SQL-Funktion auth_lookup_user_by_username gibt sie als "Role" zurueck. Die
Handannotation role: string in AuthLookupUserByUsernameRow war eine zweite
Fassung desselben Wertes und faellt damit weg.

SessionUser und UploadedPng in bug-reports.service.ts sind jetzt Pick<> der
neuen Typen statt eigener Beschreibungen.

Fixtures in auth.controller.spec.ts ergaenzt: sie uebergaben einen Aufrufer
ohne username und ohne mustChangePassword - eine Form, die JwtStrategy nie
erzeugt. Testzahlen unveraendert.

noExplicitAny in apps/api/src: 149 -> 137. type-check 4/4, lint 5/5,
apps/api 72/1143, apps/web 73/531.

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:03:24 +02:00
parent b188946e31
commit f2fc39f51c
10 changed files with 212 additions and 38 deletions
@@ -1,6 +1,7 @@
import { Body, Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import type { AuthUser, UploadedFileLike } from '../auth/types/auth-user';
import { BugReportsService } from './bug-reports.service';
import { BugReportDto } from './dto/bug-report.dto';
@@ -30,9 +31,9 @@ export class BugReportsController {
FileInterceptor('screenshot', { limits: { fileSize: 4 * 1024 * 1024, files: 1 } }),
)
async submit(
@CurrentUser() user: any,
@CurrentUser() user: AuthUser,
@Body() dto: BugReportDto,
@UploadedFile() file?: any,
@UploadedFile() file?: UploadedFileLike,
) {
return this.service.submit(user, dto, file);
}
@@ -4,6 +4,7 @@ import {
ConflictException,
HttpException,
} from '@nestjs/common';
import { Role } from '@prisma/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { forTenant } from '../prisma/prisma-tenant.extension';
import { BugReportsService } from './bug-reports.service';
@@ -37,7 +38,7 @@ const PNG_1x1 = Buffer.from(
'base64',
);
const sessionUser = { id: 'u1', username: 'anna', role: 'USER', tenantId: 't1' };
const sessionUser = { id: 'u1', username: 'anna', role: Role.USER, tenantId: 't1' };
const baseDto = {
page: '/admin/users?tab=x',
+16 -11
View File
@@ -14,6 +14,7 @@ import { forTenant } from '../prisma/prisma-tenant.extension';
import { PrismaService } from '../prisma/prisma.service';
import { SettingsService } from '../settings/settings.service';
import { BugReportDto } from './dto/bug-report.dto';
import type { AuthUser, UploadedFileLike } from '../auth/types/auth-user';
import { describeOrigin } from './origin';
/**
@@ -58,18 +59,22 @@ const WINDOW_MS = 10 * 60 * 1000;
const MAX_PER_WINDOW = 5;
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
interface SessionUser {
id: string;
username: string;
role: string;
tenantId: string;
}
/**
* Der Aufrufer, so weit dieser Dienst ihn liest. Seit quick-260921-m34 aus
* `AuthUser` abgeleitet statt danebengestellt: es soll nicht zwei
* Beschreibungen desselben Sitzungsobjekts geben, die auseinanderlaufen
* koennen. Ausgelassen wird `mustChangePassword` — dieser Dienst liest es
* nicht.
*/
type SessionUser = Pick<AuthUser, 'id' | 'username' | 'role' | 'tenantId'>;
interface UploadedPng {
buffer: Buffer;
size: number;
mimetype?: string;
}
/**
* Das Bildschirmfoto, so weit dieser Dienst es liest — abgeleitet aus
* `UploadedFileLike`, damit die Hochladewege eine gemeinsame Beschreibung
* haben. `originalname` fehlt bewusst: der Anhangname ist hier fest
* vorgegeben (T-M97-04), der eingereichte Name wird nie benutzt.
*/
type UploadedPng = Pick<UploadedFileLike, 'buffer' | 'size' | 'mimetype'>;
@Injectable()
export class BugReportsService {