Files
mybabyguess/apps/api/src/users/users.controller.ts
T
2026-05-03 21:53:59 +02:00

188 lines
5.3 KiB
TypeScript

import {
BadRequestException,
Body,
Controller,
Delete,
Get,
MaxFileSizeValidator,
Param,
Patch,
Req,
Post,
ParseFilePipe,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { UserRole } from '@prisma/client';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname, join } from 'path';
import { existsSync, mkdirSync, readdirSync } from 'fs';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import type { AuthenticatedRequest } from '../auth/jwt-auth.guard';
import { Roles } from '../auth/roles.decorator';
import { RolesGuard } from '../auth/roles.guard';
import { CreateUserDto } from './dto/create-user.dto';
import { AssignUserProjectDto } from './dto/assign-user-project.dto';
import { UpdateMyProfileDto } from './dto/update-my-profile.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UsersService } from './users.service';
const uploadsDir = join(process.cwd(), 'uploads');
const storage = diskStorage({
destination: (_req, _file, callback) => {
if (!existsSync(uploadsDir)) {
mkdirSync(uploadsDir, { recursive: true });
}
callback(null, uploadsDir);
},
filename: (_req, file, callback) => {
const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
callback(null, `profile-${uniqueSuffix}${extname(file.originalname)}`);
},
});
@Controller('users')
export class UsersController {
private readonly defaultAvatars: string[];
constructor(private readonly usersService: UsersService) {
const avatarsDir = join(process.cwd(), 'public', 'default-avatars');
const imageExts = ['.png', '.jpg', '.jpeg', '.webp'];
try {
this.defaultAvatars = readdirSync(avatarsDir)
.filter((f) => imageExts.includes(extname(f).toLowerCase()))
.sort()
.map((f) => `/default-avatars/${f}`);
} catch {
this.defaultAvatars = [];
}
}
@Get('default-avatars')
listDefaultAvatars() {
return this.defaultAvatars;
}
@Get('me')
@UseGuards(JwtAuthGuard)
me(@Req() request: AuthenticatedRequest) {
return this.usersService.getProfile(request.user!.sub);
}
@Patch('me')
@UseGuards(JwtAuthGuard)
updateMe(
@Req() request: AuthenticatedRequest,
@Body() updateMyProfileDto: UpdateMyProfileDto,
) {
return this.usersService.updateMyProfile(
request.user!.sub,
updateMyProfileDto.displayName,
);
}
@Post('me/photo')
@UseGuards(JwtAuthGuard)
@UseInterceptors(FileInterceptor('file', { storage }))
uploadPhoto(
@Req() request: AuthenticatedRequest,
@Body() body: { bgColor?: string },
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }),
],
fileIsRequired: false,
}),
)
file?: Express.Multer.File,
) {
if (file) {
const allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp'];
if (!allowedMimeTypes.includes(file.mimetype)) {
throw new BadRequestException('Format non supporté. Utilisez PNG, JPG ou WEBP.');
}
}
const profileImageUrl = file ? `/uploads/${file.filename}` : body.bgColor && !file ? undefined : undefined;
const bgColor = body.bgColor ?? undefined;
if (!file && !bgColor) {
throw new BadRequestException('Envoyez une image ou une couleur de fond.');
}
return this.usersService.updateProfileImage(
request.user!.sub,
file ? `/uploads/${file.filename}` : undefined,
bgColor,
);
}
@Delete('me/photo')
@UseGuards(JwtAuthGuard)
deletePhoto(@Req() request: AuthenticatedRequest) {
return this.usersService.updateProfileImage(request.user!.sub, null, null);
}
@Patch('me/avatar')
@UseGuards(JwtAuthGuard)
setDefaultAvatar(
@Req() request: AuthenticatedRequest,
@Body() body: { avatarUrl: string; bgColor?: string },
) {
if (!body.avatarUrl || !this.defaultAvatars.includes(body.avatarUrl)) {
throw new BadRequestException('Avatar par defaut invalide.');
}
return this.usersService.updateProfileImage(
request.user!.sub,
body.avatarUrl,
body.bgColor,
);
}
@Post()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
create(@Req() request: AuthenticatedRequest, @Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto, request.user?.sub);
}
@Get()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
list() {
return this.usersService.list();
}
@Patch(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
@Req() request: AuthenticatedRequest,
) {
return this.usersService.updateById(id, updateUserDto, request.user?.sub);
}
@Patch(':id/project')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
assignProject(
@Param('id') id: string,
@Body() dto: AssignUserProjectDto,
@Req() request: AuthenticatedRequest,
) {
return this.usersService.assignProjectById(id, dto.projectId, request.user?.sub);
}
@Delete(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
remove(@Param('id') id: string, @Req() request: AuthenticatedRequest) {
return this.usersService.removeById(id, request.user?.sub);
}
}