Files
friendolls-server/src/users/users.controller.ts

268 lines
7.0 KiB
TypeScript

import {
Controller,
Get,
Put,
Delete,
Param,
Body,
HttpCode,
UseGuards,
Logger,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiParam,
ApiBearerAuth,
ApiUnauthorizedResponse,
ApiForbiddenResponse,
} from '@nestjs/swagger';
import { UsersService } from './users.service';
import { User } from './users.entity';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import {
CurrentUser,
type AuthenticatedUser,
} from '../auth/decorators/current-user.decorator';
import { AuthService } from '../auth/auth.service';
/**
* Users Controller
*
* Handles user-related HTTP endpoints.
* All endpoints require authentication via Keycloak JWT token.
*
* Note: User creation is handled automatically during authentication flow.
* Users cannot be created directly via API - they must authenticate via Keycloak.
*/
@ApiTags('users')
@Controller('users')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class UsersController {
private readonly logger = new Logger(UsersController.name);
constructor(
private readonly usersService: UsersService,
private readonly authService: AuthService,
) {}
/**
* Get current authenticated user's profile.
* This endpoint syncs the user from Keycloak token on each request.
*/
@Get('me')
@ApiOperation({
summary: 'Get current user profile',
description:
'Returns the authenticated user profile. Automatically syncs data from Keycloak token.',
})
@ApiResponse({
status: 200,
description: 'Current user profile',
type: User,
})
@ApiUnauthorizedResponse({
description: 'Invalid or missing JWT token',
})
async getCurrentUser(
@CurrentUser() authUser: AuthenticatedUser,
): Promise<User> {
this.logger.debug(`Get current user: ${authUser.keycloakSub}`);
// Sync user from token (creates if doesn't exist, updates if exists)
const user = await this.authService.syncUserFromToken(authUser);
return user;
}
/**
* Update current authenticated user's profile.
*/
@Put('me')
@ApiOperation({
summary: 'Update current user profile',
description:
'Updates the authenticated user profile. Users can only update their own profile.',
})
@ApiResponse({
status: 200,
description: 'User profile updated successfully',
type: User,
})
@ApiResponse({
status: 400,
description: 'Invalid request data',
})
@ApiUnauthorizedResponse({
description: 'Invalid or missing JWT token',
})
async updateCurrentUser(
@CurrentUser() authUser: AuthenticatedUser,
@Body() updateUserDto: UpdateUserDto,
): Promise<User> {
this.logger.log(`Update current user: ${authUser.keycloakSub}`);
// First ensure user exists in our system
const user = await this.authService.syncUserFromToken(authUser);
// Update the user's profile
return Promise.resolve(
this.usersService.update(user.id, updateUserDto, authUser.keycloakSub),
);
}
/**
* Get a user by their ID.
* Currently allows any authenticated user to view other users.
* Consider adding additional authorization if needed.
*/
@Get(':id')
@ApiOperation({
summary: 'Get a user by ID',
description: 'Retrieves a user profile by their internal ID.',
})
@ApiParam({
name: 'id',
description: 'User internal UUID',
example: '550e8400-e29b-41d4-a716-446655440000',
})
@ApiResponse({
status: 200,
description: 'User found',
type: User,
})
@ApiResponse({
status: 404,
description: 'User not found',
})
@ApiUnauthorizedResponse({
description: 'Invalid or missing JWT token',
})
async findOne(
@Param('id') id: string,
@CurrentUser() authUser: AuthenticatedUser,
): Promise<User> {
this.logger.debug(
`Get user by ID: ${id} (requested by ${authUser.keycloakSub})`,
);
return Promise.resolve(this.usersService.findOne(id));
}
/**
* Update a user by their ID.
* Users can only update their own profile (enforced by service layer).
*/
@Put(':id')
@ApiOperation({
summary: 'Update a user by ID',
description:
'Updates a user profile. Users can only update their own profile.',
})
@ApiParam({
name: 'id',
description: 'User internal UUID',
example: '550e8400-e29b-41d4-a716-446655440000',
})
@ApiResponse({
status: 200,
description: 'User updated successfully',
type: User,
})
@ApiResponse({
status: 400,
description: 'Invalid request data',
})
@ApiResponse({
status: 404,
description: 'User not found',
})
@ApiForbiddenResponse({
description: 'Cannot update another user profile',
})
@ApiUnauthorizedResponse({
description: 'Invalid or missing JWT token',
})
async update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
@CurrentUser() authUser: AuthenticatedUser,
): Promise<User> {
this.logger.log(`Update user ${id} (requested by ${authUser.keycloakSub})`);
return Promise.resolve(
this.usersService.update(id, updateUserDto, authUser.keycloakSub),
);
}
/**
* Delete current authenticated user's account.
* Note: This only deletes the local user record.
* The user still exists in Keycloak and can re-authenticate.
*/
@Delete('me')
@ApiOperation({
summary: 'Delete current user account',
description:
'Deletes the authenticated user account. Only removes local data; user still exists in Keycloak.',
})
@ApiResponse({
status: 204,
description: 'User account deleted successfully',
})
@ApiUnauthorizedResponse({
description: 'Invalid or missing JWT token',
})
@HttpCode(204)
async deleteCurrentUser(
@CurrentUser() authUser: AuthenticatedUser,
): Promise<void> {
this.logger.log(`Delete current user: ${authUser.keycloakSub}`);
// First ensure user exists in our system
const user = await this.authService.syncUserFromToken(authUser);
// Delete the user's account
this.usersService.delete(user.id, authUser.keycloakSub);
}
/**
* Delete a user by their ID.
* Users can only delete their own account (enforced by service layer).
*/
@Delete(':id')
@ApiOperation({
summary: 'Delete a user by ID',
description:
'Deletes a user account. Users can only delete their own account. Only removes local data; user still exists in Keycloak.',
})
@ApiParam({
name: 'id',
description: 'User internal UUID',
example: '550e8400-e29b-41d4-a716-446655440000',
})
@ApiResponse({
status: 204,
description: 'User deleted successfully',
})
@ApiResponse({
status: 404,
description: 'User not found',
})
@ApiForbiddenResponse({
description: 'Cannot delete another user account',
})
@ApiUnauthorizedResponse({
description: 'Invalid or missing JWT token',
})
@HttpCode(204)
delete(
@Param('id') id: string,
@CurrentUser() authUser: AuthenticatedUser,
): void {
this.logger.log(`Delete user ${id} (requested by ${authUser.keycloakSub})`);
this.usersService.delete(id, authUser.keycloakSub);
}
}