feat(260929-dzu): persoenliche eigene Module je Benutzer (API, Migration, Zeilenschutz)

- ownerUserId (NULL = gemeinsam, sonst persoenlich) mit Zeilenschutz nach Muster SearchProvider
- GET nur gemeinsame + eigene, fremde persoenliche Eintraege 404
- POST fuer jeden Benutzer, shared nur fuer Administratoren (403)
- PATCH/DELETE: persoenlich nur Besitzer, gemeinsam nur Administrator
- Zugriffsklassifikation nachgemessen: 61/223/6

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 10:09:08 +02:00
parent 76a923450f
commit c703d87a1c
8 changed files with 533 additions and 150 deletions
@@ -9,18 +9,21 @@ import {
Post,
Req,
} from '@nestjs/common';
import { Role } from '@prisma/client';
import { Roles } from '../auth/decorators/roles.decorator';
import type { AuthenticatedRequest } from '../auth/types/auth-user';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import type { AuthenticatedRequest, AuthUser } from '../auth/types/auth-user';
import { CustomModulesService } from './custom-modules.service';
import { CreateCustomModuleDto, UpdateCustomModuleDto } from './dto/custom-module.dto';
/**
* Eigene Module (quick-260929-9wc). Lesen (`GET`, `GET :id`) steht jedem
* angemeldeten Benutzer offen — die Eintraege sind fuer alle sichtbar (D-01);
* Schreiben nur `@Roles(ADMIN, SUPER_ADMIN)` (T-9WC-01). Kein `@UseModule`:
* Eigene Module (quick-260929-9wc, erweitert in quick-260929-dzu). Jeder
* angemeldete Benutzer darf lesen und eigene (persoenliche) Eintraege anlegen,
* aendern und loeschen; gemeinsame Eintraege (`shared: true`) darf nur ein
* Administrator anlegen, aendern und loeschen — diese Rollenentscheidung trifft
* der Dienst, weil sie vom Eintrag abhaengt (gemeinsam oder persoenlich), nicht
* von der Route. Deshalb tragen die Routen kein `@Roles`. Kein `@UseModule`:
* eigene Module haengen an keiner Modul-Aktivierung. `tenantId` kommt
* ausschliesslich aus `req.tenantId` (gesetzt vom `TenantGuard`).
* ausschliesslich aus `req.tenantId` (gesetzt vom `TenantGuard`), der Benutzer
* aus dem Token.
*
* ROUTEN-REIHENFOLGE: NestJS bildet Routen in Deklarationsreihenfolge ab.
* Jede kuenftige statische GET-Route MUSS ueber `getOne` (`@Get(':id')`)
@@ -40,34 +43,44 @@ export class CustomModulesController {
}
@Get()
async list(@Req() req: AuthenticatedRequest) {
return this.service.list(this.requireTenantId(req));
async list(@Req() req: AuthenticatedRequest, @CurrentUser() user: AuthUser) {
return this.service.list(this.requireTenantId(req), user);
}
@Get(':id')
async getOne(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
return this.service.getOne(this.requireTenantId(req), id);
async getOne(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Param('id') id: string,
) {
return this.service.getOne(this.requireTenantId(req), user, id);
}
@Post()
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async create(@Req() req: AuthenticatedRequest, @Body() dto: CreateCustomModuleDto) {
return this.service.create(this.requireTenantId(req), dto);
async create(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Body() dto: CreateCustomModuleDto,
) {
return this.service.create(this.requireTenantId(req), user, dto);
}
@Patch(':id')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async update(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Param('id') id: string,
@Body() dto: UpdateCustomModuleDto,
) {
return this.service.update(this.requireTenantId(req), id, dto);
return this.service.update(this.requireTenantId(req), user, id, dto);
}
@Delete(':id')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async remove(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
return this.service.remove(this.requireTenantId(req), id);
async remove(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Param('id') id: string,
) {
return this.service.remove(this.requireTenantId(req), user, id);
}
}