Files
ApiCore/ApiCore-Backend/src/users/users.controller.ts
T
Nick SzaunigandClaude Opus 5 b0e05daa42 feat: open the last locked fields for editing, keeping their history intact
Four fields were still frozen because changing them would have bypassed the
record that makes the change traceable, and a user's login data could not be
corrected at all. Locking a field is the wrong trade: a wrong value stays in
the system forever. The fix is to let the edit write the same history entry the
dedicated endpoint writes.

- A colony's apiary is editable through PATCH and writes the same
  ColonyApiaryHistory entry POST /colonies/:id/move writes, so the location
  history never skips a stand the colony was recorded on.
- A honey batch's storage container is editable through PATCH and writes the
  same HoneyBatchRelocation entry POST /honey-batches/:id/relocate writes.
- A stock item's quantity is editable and books the difference as an ADJUSTMENT
  movement, so the stored level stays the sum of its movements.
- A role's key is editable (system roles were already rejected outright by
  RolesService.update, which keeps the seed's ADMIN lookup intact).
- A user's e-mail - the login identity - is editable. A typo there previously
  locked the account out for good.
- New POST /users/:id/reset-password lets an administrator set someone else's
  password: there is no self-service reset flow, so a forgotten password was
  the one thing no edit could repair. It revokes every refresh token and clears
  the permission cache exactly as a self-service change does, refuses to act on
  your own account (that goes through /profile/password, which proves ownership
  by asking for the current one), and never writes the password to the log.

Frontend:
- Users had no edit UI at all, although PATCH /users/:id existed. New
  EditUserDialog covers name, e-mail, role and an optional new password.
- Colony, honey batch and stock item forms gained the newly editable fields,
  each with a note saying the correction is recorded as a move, a relocation
  or a stock adjustment.
- The role dialog gained the key field.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-19 08:13:00 +02:00

73 lines
2.2 KiB
TypeScript

import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
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 { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ResetUserPasswordDto } from './dto/reset-user-password.dto';
import { UsersService } from './users.service';
@ApiTags('users')
@ApiBearerAuth()
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@RequirePermission('users:read')
@Get()
findAll() {
return this.usersService.findAll();
}
@RequirePermission('users:create')
@Post()
create(@Body() dto: CreateUserDto, @CurrentUser() actor: AuthenticatedUser) {
return this.usersService.create(dto, actor.id);
}
@RequirePermission('users:read')
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findSummaryById(id);
}
@RequirePermission('users:update')
@Patch(':id')
update(
@Param('id') id: string,
@Body() dto: UpdateUserDto,
@CurrentUser() actor: AuthenticatedUser,
) {
return this.usersService.update(id, dto, actor.id);
}
@RequirePermission('users:deactivate')
@Post(':id/deactivate')
deactivate(@Param('id') id: string, @CurrentUser() actor: AuthenticatedUser) {
return this.usersService.deactivate(id, actor.id);
}
@RequirePermission('users:deactivate')
@Post(':id/activate')
activate(@Param('id') id: string, @CurrentUser() actor: AuthenticatedUser) {
return this.usersService.activate(id, actor.id);
}
@RequirePermission('users:update')
@Post(':id/reset-password')
resetPassword(
@Param('id') id: string,
@Body() dto: ResetUserPasswordDto,
@CurrentUser() actor: AuthenticatedUser,
) {
return this.usersService.resetPassword(id, dto, actor.id);
}
@RequirePermission('users:update')
@Post(':id/mfa/reset')
resetMfa(@Param('id') id: string, @CurrentUser() actor: AuthenticatedUser) {
return this.usersService.resetMfa(id, actor.id);
}
}