From c5103048136d94f5483c88840dab4a0eb3dfd8ab Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Wed, 26 Aug 2026 07:05:32 +0200 Subject: [PATCH] feat: RBAC Phase 2c - Ernte & Lager auf @RequirePermission umgestellt Harvests, Extractions, HoneyBatches, Storage, Products, Bottling, Equipment: @Roles(...) durch @RequirePermission(...) ersetzt, ungeschuetzte GET-Routen mit :read-Rechten geschlossen. HoneyBatches behaelt die feine Unterscheidung relocate (auch STAFF) vs. create/measurements (nur BEEKEEPER+); Storage bekommt konsequent keine STAFF-Schreibrechte (deckt sich mit dem bisherigen @Roles-Stand). Verifiziert: tsc, lint, volle e2e-Suite 158/159 gruen (1 getrackter Skip). Co-Authored-By: Claude Sonnet 5 --- .../src/bottling/bottling.controller.ts | 9 +++++---- .../src/equipment/equipment.controller.ts | 10 ++++++---- .../src/extractions/extractions.controller.ts | 9 +++++---- .../src/harvests/harvests.controller.ts | 9 +++++---- .../src/honey-batches/honey-batches.controller.ts | 14 +++++++++----- .../src/products/products.controller.ts | 10 ++++++---- ApiCore-Backend/src/storage/storage.controller.ts | 12 +++++++----- 7 files changed, 43 insertions(+), 30 deletions(-) diff --git a/ApiCore-Backend/src/bottling/bottling.controller.ts b/ApiCore-Backend/src/bottling/bottling.controller.ts index 3a6259f..2bed8f6 100644 --- a/ApiCore-Backend/src/bottling/bottling.controller.ts +++ b/ApiCore-Backend/src/bottling/bottling.controller.ts @@ -8,8 +8,7 @@ import { Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { CreateBottlingBatchDto } from './dto/create-bottling-batch.dto'; @@ -22,17 +21,19 @@ import { BottlingService } from './bottling.service'; export class BottlingController { constructor(private readonly bottlingService: BottlingService) {} + @RequirePermission('bottling:read') @Get() findAll(@Query('honeyBatchId') honeyBatchId?: string) { return this.bottlingService.findAll({ honeyBatchId }); } + @RequirePermission('bottling:read') @Get(':id') findOne(@Param('id') id: string) { return this.bottlingService.findById(id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('bottling:create') @Post() create( @Body() dto: CreateBottlingBatchDto, @@ -41,7 +42,7 @@ export class BottlingController { return this.bottlingService.create(dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('bottling:update') @Patch(':id') update( @Param('id') id: string, diff --git a/ApiCore-Backend/src/equipment/equipment.controller.ts b/ApiCore-Backend/src/equipment/equipment.controller.ts index e40239b..cba8df0 100644 --- a/ApiCore-Backend/src/equipment/equipment.controller.ts +++ b/ApiCore-Backend/src/equipment/equipment.controller.ts @@ -8,8 +8,8 @@ import { Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { EquipmentCondition, LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { EquipmentCondition } from '../../generated/prisma'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { CreateEquipmentDto } from './dto/create-equipment.dto'; @@ -22,17 +22,19 @@ import { EquipmentService } from './equipment.service'; export class EquipmentController { constructor(private readonly equipmentService: EquipmentService) {} + @RequirePermission('equipment:read') @Get() findAll(@Query('condition') condition?: EquipmentCondition) { return this.equipmentService.findAll({ condition }); } + @RequirePermission('equipment:read') @Get(':id') findOne(@Param('id') id: string) { return this.equipmentService.findById(id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('equipment:create') @Post() create( @Body() dto: CreateEquipmentDto, @@ -41,7 +43,7 @@ export class EquipmentController { return this.equipmentService.create(dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('equipment:update') @Patch(':id') update( @Param('id') id: string, diff --git a/ApiCore-Backend/src/extractions/extractions.controller.ts b/ApiCore-Backend/src/extractions/extractions.controller.ts index b5e6c7f..a79d0cc 100644 --- a/ApiCore-Backend/src/extractions/extractions.controller.ts +++ b/ApiCore-Backend/src/extractions/extractions.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { CreateExtractionDto } from './dto/create-extraction.dto'; @@ -14,17 +13,19 @@ import { ExtractionsService } from './extractions.service'; export class ExtractionsController { constructor(private readonly extractionsService: ExtractionsService) {} + @RequirePermission('extractions:read') @Get() findAll() { return this.extractionsService.findAll(); } + @RequirePermission('extractions:read') @Get(':id') findOne(@Param('id') id: string) { return this.extractionsService.findById(id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('extractions:create') @Post() create( @Body() dto: CreateExtractionDto, @@ -33,7 +34,7 @@ export class ExtractionsController { return this.extractionsService.create(dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('extractions:update') @Patch(':id') update( @Param('id') id: string, diff --git a/ApiCore-Backend/src/harvests/harvests.controller.ts b/ApiCore-Backend/src/harvests/harvests.controller.ts index a4cd68d..cd1fcc4 100644 --- a/ApiCore-Backend/src/harvests/harvests.controller.ts +++ b/ApiCore-Backend/src/harvests/harvests.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { CreateHarvestDto } from './dto/create-harvest.dto'; @@ -14,17 +13,19 @@ import { HarvestsService } from './harvests.service'; export class HarvestsController { constructor(private readonly harvestsService: HarvestsService) {} + @RequirePermission('harvests:read') @Get() findAll() { return this.harvestsService.findAll(); } + @RequirePermission('harvests:read') @Get(':id') findOne(@Param('id') id: string) { return this.harvestsService.findById(id); } - @Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF) + @RequirePermission('harvests:create') @Post() create( @Body() dto: CreateHarvestDto, @@ -33,7 +34,7 @@ export class HarvestsController { return this.harvestsService.create(dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF) + @RequirePermission('harvests:update') @Patch(':id') update( @Param('id') id: string, diff --git a/ApiCore-Backend/src/honey-batches/honey-batches.controller.ts b/ApiCore-Backend/src/honey-batches/honey-batches.controller.ts index 90717c6..f755179 100644 --- a/ApiCore-Backend/src/honey-batches/honey-batches.controller.ts +++ b/ApiCore-Backend/src/honey-batches/honey-batches.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { HoneyBatchStatus, LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { HoneyBatchStatus } from '../../generated/prisma'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { CreateHoneyBatchDto } from './dto/create-honey-batch.dto'; @@ -15,27 +15,31 @@ import { HoneyBatchesService } from './honey-batches.service'; export class HoneyBatchesController { constructor(private readonly honeyBatchesService: HoneyBatchesService) {} + @RequirePermission('honey-batches:read') @Get() findAll(@Query('status') status?: HoneyBatchStatus) { return this.honeyBatchesService.findAll({ status }); } + @RequirePermission('honey-batches:read') @Get(':id') findOne(@Param('id') id: string) { return this.honeyBatchesService.findById(id); } + @RequirePermission('honey-batches:read') @Get(':id/traceability') traceability(@Param('id') id: string) { return this.honeyBatchesService.traceability(id); } + @RequirePermission('honey-batches:read') @Get(':id/relocations') findRelocations(@Param('id') id: string) { return this.honeyBatchesService.findRelocations(id); } - @Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF) + @RequirePermission('honey-batches:relocate') @Post(':id/relocate') relocate( @Param('id') id: string, @@ -45,7 +49,7 @@ export class HoneyBatchesController { return this.honeyBatchesService.relocate(id, dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('honey-batches:create') @Post() create( @Body() dto: CreateHoneyBatchDto, @@ -54,7 +58,7 @@ export class HoneyBatchesController { return this.honeyBatchesService.create(dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('honey-batches:measure') @Post(':id/measurements') addMeasurement( @Param('id') id: string, diff --git a/ApiCore-Backend/src/products/products.controller.ts b/ApiCore-Backend/src/products/products.controller.ts index 7c56440..048e8e0 100644 --- a/ApiCore-Backend/src/products/products.controller.ts +++ b/ApiCore-Backend/src/products/products.controller.ts @@ -8,8 +8,8 @@ import { Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { ProductStatus, LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { ProductStatus } from '../../generated/prisma'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { CreateProductDto } from './dto/create-product.dto'; @@ -22,17 +22,19 @@ import { ProductsService } from './products.service'; export class ProductsController { constructor(private readonly productsService: ProductsService) {} + @RequirePermission('products:read') @Get() findAll(@Query('status') status?: ProductStatus) { return this.productsService.findAll({ status }); } + @RequirePermission('products:read') @Get(':id') findOne(@Param('id') id: string) { return this.productsService.findById(id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('products:create') @Post() create( @Body() dto: CreateProductDto, @@ -41,7 +43,7 @@ export class ProductsController { return this.productsService.create(dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('products:update') @Patch(':id') update( @Param('id') id: string, diff --git a/ApiCore-Backend/src/storage/storage.controller.ts b/ApiCore-Backend/src/storage/storage.controller.ts index d8457b0..85188c3 100644 --- a/ApiCore-Backend/src/storage/storage.controller.ts +++ b/ApiCore-Backend/src/storage/storage.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { LegacyRole as Role } from '../../generated/prisma'; -import { Roles } from '../auth/decorators/roles.decorator'; +import { RequirePermission } from '../auth/decorators/require-permission.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import type { AuthenticatedUser } from '../auth/types/authenticated-user'; import { CreateStorageContainerDto } from './dto/create-storage-container.dto'; @@ -15,12 +14,13 @@ import { StorageService } from './storage.service'; export class StorageController { constructor(private readonly storageService: StorageService) {} + @RequirePermission('storage:read') @Get('storage-containers') findAllContainers() { return this.storageService.findAllContainers(); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('storage:create') @Post('storage-containers') createContainer( @Body() dto: CreateStorageContainerDto, @@ -29,17 +29,19 @@ export class StorageController { return this.storageService.createContainer(dto, actor.id); } + @RequirePermission('storage:read') @Get('stock-items') findAllStockItems() { return this.storageService.findAllStockItems(); } + @RequirePermission('storage:read') @Get('stock-items/:id') findStockItem(@Param('id') id: string) { return this.storageService.findStockItemById(id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('storage:create') @Post('stock-items') createStockItem( @Body() dto: CreateStockItemDto, @@ -48,7 +50,7 @@ export class StorageController { return this.storageService.createStockItem(dto, actor.id); } - @Roles(Role.ADMIN, Role.BEEKEEPER) + @RequirePermission('storage:move') @Post('stock-items/:id/movements') addMovement( @Param('id') id: string,