Replaces the fixed 5-value Role enum with a database-driven Role/Permission model: admins can create custom roles with a precise per-resource/action permission set (83-key code-owned catalog), instead of being locked to ADMIN/BEEKEEPER/STAFF/READONLY/API_CLIENT. - New Role/Permission/RolePermission tables; legacy Role enum renamed to LegacyRole and kept as User.legacyRole so all existing @Roles() call sites keep working unchanged during the migration. - New RolesController/RolesService (CRUD, permission-set editing, deactivate/reactivate) with lockout-prevention invariants: system roles (ADMIN) can't be edited or deactivated, and a role still assigned to users can't be deactivated. - New PermissionResolverService (30s-cached, DB-fresh per user) and PermissionsGuard + @RequirePermission(), registered as an additional APP_GUARD alongside the legacy RolesGuard so both systems run side-by-side until controllers are migrated in Phase 2. - PermissionCatalogSyncService seeds any new catalog entries into the DB on boot, so future features only touch the TS catalog. - New GET /auth/me returns the caller's role and effective permissions. - Migration hand-written (destructive column rename) with grants derived from a fresh grep of every existing @Roles() call site, verified against 5 controllers read in full for STAFF's exact write boundary. - test/roles.e2e-spec.ts covers the new endpoints and invariants end to end; test/authorization-coverage.e2e-spec.ts added but skipped until Phase 2 gives every controller a @RequirePermission. Co-Authored-By: Claude Sonnet 5 <[email protected]>
54 lines
1.5 KiB
TypeScript
54 lines
1.5 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Get,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
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 { CurrentUser } from '../auth/decorators/current-user.decorator';
|
|
import type { AuthenticatedUser } from '../auth/types/authenticated-user';
|
|
import { CreateEquipmentDto } from './dto/create-equipment.dto';
|
|
import { UpdateEquipmentDto } from './dto/update-equipment.dto';
|
|
import { EquipmentService } from './equipment.service';
|
|
|
|
@ApiTags('equipment')
|
|
@ApiBearerAuth()
|
|
@Controller('equipment')
|
|
export class EquipmentController {
|
|
constructor(private readonly equipmentService: EquipmentService) {}
|
|
|
|
@Get()
|
|
findAll(@Query('condition') condition?: EquipmentCondition) {
|
|
return this.equipmentService.findAll({ condition });
|
|
}
|
|
|
|
@Get(':id')
|
|
findOne(@Param('id') id: string) {
|
|
return this.equipmentService.findById(id);
|
|
}
|
|
|
|
@Roles(Role.ADMIN, Role.BEEKEEPER)
|
|
@Post()
|
|
create(
|
|
@Body() dto: CreateEquipmentDto,
|
|
@CurrentUser() actor: AuthenticatedUser,
|
|
) {
|
|
return this.equipmentService.create(dto, actor.id);
|
|
}
|
|
|
|
@Roles(Role.ADMIN, Role.BEEKEEPER)
|
|
@Patch(':id')
|
|
update(
|
|
@Param('id') id: string,
|
|
@Body() dto: UpdateEquipmentDto,
|
|
@CurrentUser() actor: AuthenticatedUser,
|
|
) {
|
|
return this.equipmentService.update(id, dto, actor.id);
|
|
}
|
|
}
|