Files
tessera-ctl/apps/api/src/custom-modules/custom-modules.controller.ts
T
schalli c703d87a1c 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>
2026-09-29 10:09:08 +02:00

87 lines
2.7 KiB
TypeScript

import {
Body,
Controller,
Delete,
ForbiddenException,
Get,
Param,
Patch,
Post,
Req,
} from '@nestjs/common';
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, 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`), der Benutzer
* aus dem Token.
*
* ROUTEN-REIHENFOLGE: NestJS bildet Routen in Deklarationsreihenfolge ab.
* Jede kuenftige statische GET-Route MUSS ueber `getOne` (`@Get(':id')`)
* stehen, sonst faengt `:id` sie ab (404-Shadowing); der Controller-Test
* haelt die Reihenfolge von `list` vor `getOne` fest.
*/
@Controller('custom-modules')
export class CustomModulesController {
constructor(private readonly service: CustomModulesService) {}
private requireTenantId(req: AuthenticatedRequest): string {
const tenantId = req.tenantId;
if (!tenantId) {
throw new ForbiddenException('Kein Mandantenkontext');
}
return tenantId;
}
@Get()
async list(@Req() req: AuthenticatedRequest, @CurrentUser() user: AuthUser) {
return this.service.list(this.requireTenantId(req), user);
}
@Get(':id')
async getOne(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Param('id') id: string,
) {
return this.service.getOne(this.requireTenantId(req), user, id);
}
@Post()
async create(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Body() dto: CreateCustomModuleDto,
) {
return this.service.create(this.requireTenantId(req), user, dto);
}
@Patch(':id')
async update(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Param('id') id: string,
@Body() dto: UpdateCustomModuleDto,
) {
return this.service.update(this.requireTenantId(req), user, id, dto);
}
@Delete(':id')
async remove(
@Req() req: AuthenticatedRequest,
@CurrentUser() user: AuthUser,
@Param('id') id: string,
) {
return this.service.remove(this.requireTenantId(req), user, id);
}
}