import { BadRequestException, Body, Controller, Delete, ForbiddenException, NotFoundException, Get, Param, Post, Put, Request, UseGuards, Query, } from '@nestjs/common'; import { UserService } from './user.service'; import { CombinedAuthGuard } from '../auth/combined-auth.guard'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { I18nService } from '../i18n/i18n.service'; import { UserRole } from './user-role.enum'; import { UserSettingService } from './user-setting.service'; import { Permission } from '../auth/permission/permission.decorator'; import { PermissionsGuard } from '../auth/permission/permission.guard'; @Controller('users') @UseGuards(CombinedAuthGuard) export class UserController { constructor( private readonly userService: UserService, private readonly i18nService: I18nService, private readonly userSettingService: UserSettingService, ) {} // --- API Key Management --- @Get('api-key') async getApiKey(@Request() req) { const apiKey = await this.userService.getOrCreateApiKey(req.user.id); return { apiKey }; } @Post('api-key/rotate') async rotateApiKey(@Request() req) { const apiKey = await this.userService.regenerateApiKey(req.user.id); return { apiKey }; } // --- Personal Settings --- @Get('settings') async getSettings(@Request() req) { return this.userSettingService.getByUser(req.user.id); } @Put('settings/language') async updateLanguage(@Request() req, @Body() body: { language: string }) { if (!body.language) throw new BadRequestException('language is required'); return this.userSettingService.update(req.user.id, body.language); } // --- Profile --- @Get('profile') async getProfile(@Request() req: any) { return this.userService.findOneById(req.user.id); } @Get('tenants') async getMyTenants(@Request() req: any) { return this.userService.getUserTenants(req.user.id); } @Get('me') async getMe(@Request() req) { const user = await this.userService.findOneById(req.user.id); if (!user) throw new NotFoundException(); let isNotebookEnabled = true; if (user.tenantId) { const settings = await this.userService.getTenantSettings(user.tenantId); isNotebookEnabled = settings?.isNotebookEnabled ?? true; } const tenantName = user.tenantMembers?.[0]?.tenant?.name || 'Default'; return { id: user.id, username: user.username, displayName: user.displayName, role: user.isAdmin ? UserRole.SUPER_ADMIN : UserRole.USER, tenantId: user.tenantId, tenantName, isAdmin: user.isAdmin, isNotebookEnabled, }; } @Get(':id') @UseGuards(PermissionsGuard) @Permission('user:view') async findOne(@Param('id') id: string) { const user = await this.userService.findOneById(id); if (!user) throw new NotFoundException(this.i18nService.getErrorMessage('userNotFound')); return user; } @Get() @UseGuards(PermissionsGuard) @Permission('user:view') async findAll( @Request() req, @Query('page') page?: string, @Query('limit') limit?: string, ) { const p = page ? parseInt(page) : undefined; const l = limit ? parseInt(limit) : undefined; if (req.user.role === UserRole.SUPER_ADMIN) { return this.userService.findAll(p, l); } else { return this.userService.findByTenantId(req.user.tenantId, p, l); } } @Put('password') async changePassword( @Request() req, @Body() body: { currentPassword: string; newPassword: string }, ) { const { currentPassword, newPassword } = body; if (!currentPassword || !newPassword) { throw new BadRequestException( this.i18nService.getErrorMessage('passwordsRequired'), ); } if (newPassword.length < 6) { throw new BadRequestException( this.i18nService.getErrorMessage('newPasswordMinLength'), ); } return this.userService.changePassword( req.user.id, currentPassword, newPassword, ); } @Post() @UseGuards(PermissionsGuard) @Permission('user:create') async createUser(@Request() req, @Body() body: CreateUserDto) { const { username, password } = body; if (!username || !password) { throw new BadRequestException( this.i18nService.getErrorMessage('usernamePasswordRequired'), ); } if (password.length < 6) { throw new BadRequestException( this.i18nService.getErrorMessage('passwordMinLength'), ); } // All new users default to non-admin. let isAdmin = false; // Pass the calculated params to the service return this.userService.createUser( username, password, isAdmin, req.user.tenantId, body.displayName, ); } @Put(':id') @UseGuards(PermissionsGuard) @Permission('user:edit') async updateUser( @Request() req, @Body() body: UpdateUserDto, @Param('id') id: string, ) { const callerRole = req.user.role; // Get user info to update const userToUpdate = await this.userService.findOneById(id); if (!userToUpdate) { throw new NotFoundException( this.i18nService.getErrorMessage('userNotFound'), ); } if ( callerRole === 'TENANT_ADMIN' && userToUpdate.tenantId !== req.user.tenantId ) { throw new ForbiddenException('Cannot modify users outside your tenant'); } // Prevent modifying the builtin admin account if (userToUpdate.username === 'admin') { throw new ForbiddenException( this.i18nService.getErrorMessage('cannotModifyBuiltinAdmin'), ); } // Role modification is now obsolete on global level. if (body.isAdmin !== undefined && userToUpdate.isAdmin !== body.isAdmin) { if (callerRole !== UserRole.SUPER_ADMIN) { throw new ForbiddenException( 'Only Super Admins can change user admin status.', ); } } // Validate password length if provided if (body.password && body.password.length < 6) { throw new BadRequestException( this.i18nService.getErrorMessage('passwordMinLength'), ); } return this.userService.updateUser(id, body); } @Delete(':id') @UseGuards(PermissionsGuard) @Permission('user:delete') async deleteUser(@Request() req, @Param('id') id: string) { const callerRole = req.user.role; // Prevent admin from deleting themselves if (req.user.id === id) { throw new BadRequestException( this.i18nService.getErrorMessage('cannotDeleteSelf'), ); } // Get user info to delete const userToDelete = await this.userService.findOneById(id); if (!userToDelete) { throw new NotFoundException( this.i18nService.getErrorMessage('userNotFound'), ); } if ( callerRole === 'TENANT_ADMIN' && userToDelete.tenantId !== req.user.tenantId ) { throw new ForbiddenException('Cannot delete users outside your tenant'); } // Block deletion of built-in admin account if (userToDelete.username === 'admin') { throw new ForbiddenException( this.i18nService.getErrorMessage('cannotDeleteBuiltinAdmin'), ); } return this.userService.deleteUser(id); } }