From 7eefd4ee27f1e9395cd42cd24df631394de80e04 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Mon, 15 Dec 2025 12:08:02 +0100 Subject: [PATCH 01/20] feat: add api auth to routes for swagger --- src/auth/auth.service.ts | 6 +++++- src/campuses/campuses.controller.ts | 3 ++- src/categories/categories.controller.ts | 3 ++- src/channels/channels.controller.ts | 3 ++- src/courses/courses.controller.ts | 2 ++ src/discord-users/discord-users.controller.ts | 3 ++- src/guilds-templates/guilds-templates.controller.ts | 3 ++- src/guilds/guilds.controller.ts | 3 ++- src/members-informations/members-informations.controller.ts | 3 ++- src/members/members.controller.ts | 2 ++ src/promotions/promotions.controller.ts | 3 ++- src/roles/roles.controller.ts | 3 ++- 12 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 338cff91..7b20e2e9 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -210,7 +210,11 @@ export class AuthService { guildId: this.allowedGuildId, }; - return this.jwtService.sign(payload); + const token = this.jwtService.sign(payload); + + console.log('JWT Token', token) + + return token; } /** diff --git a/src/campuses/campuses.controller.ts b/src/campuses/campuses.controller.ts index b78fa29b..fda53ed1 100644 --- a/src/campuses/campuses.controller.ts +++ b/src/campuses/campuses.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } fr import { CampusesService } from './campuses.service'; import { CreateCampusDto } from './dto/create-campus.dto'; import { UpdateCampusDto } from './dto/update-campus.dto'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { Campus } from './entities/campus.entity'; @ApiTags('campuses') +@ApiBearerAuth('JWT-auth') @Controller('campuses') export class CampusesController { constructor(private readonly campusService: CampusesService) {} diff --git a/src/categories/categories.controller.ts b/src/categories/categories.controller.ts index 68c74764..75a5d1ca 100644 --- a/src/categories/categories.controller.ts +++ b/src/categories/categories.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } fr import { CategoriesService } from './categories.service'; import { CreateCategoryDto } from './dto/create-category.dto'; import { UpdateCategoryDto } from './dto/update-category.dto'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { Category } from './entities/category.entity'; @ApiTags('categories') +@ApiBearerAuth('JWT-auth') @Controller('categories') export class CategoriesController { constructor(private readonly categoriesService: CategoriesService) {} diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index 73b3fff7..ae683a06 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } fr import { ChannelsService } from './channels.service'; import { CreateChannelDto } from './dto/create-channel.dto'; import { UpdateChannelDto } from './dto/update-channel.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBearerAuth } from '@nestjs/swagger'; import { Channel } from './entities/channel.entity'; @ApiTags('channels') +@ApiBearerAuth('JWT-auth') @Controller('channels') export class ChannelsController { constructor(private readonly channelService: ChannelsService) {} diff --git a/src/courses/courses.controller.ts b/src/courses/courses.controller.ts index ba8ce850..6eea05e6 100644 --- a/src/courses/courses.controller.ts +++ b/src/courses/courses.controller.ts @@ -18,9 +18,11 @@ import { ApiResponse, ApiTags, ApiBody, + ApiBearerAuth } from '@nestjs/swagger'; @ApiTags('courses') +@ApiBearerAuth('JWT-auth') @Controller('courses') export class CoursesController { constructor(private readonly coursesService: CoursesService) {} diff --git a/src/discord-users/discord-users.controller.ts b/src/discord-users/discord-users.controller.ts index ed7e35d3..55098199 100644 --- a/src/discord-users/discord-users.controller.ts +++ b/src/discord-users/discord-users.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } fr import { DiscordUsersService } from './discord-users.service'; import { CreateDiscordUserDto } from './dto/create-discord-user.dto'; import { UpdateDiscordUserDto } from './dto/update-discord-user.dto'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { DiscordUser } from './entities/discord-user.entity'; @ApiTags('discord-users') +@ApiBearerAuth('JWT-auth') @Controller('discord-users') export class DiscordUsersController { constructor(private readonly discordUsersService: DiscordUsersService) {} diff --git a/src/guilds-templates/guilds-templates.controller.ts b/src/guilds-templates/guilds-templates.controller.ts index 1705a1eb..8787cdf4 100644 --- a/src/guilds-templates/guilds-templates.controller.ts +++ b/src/guilds-templates/guilds-templates.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } fr import { GuildsTemplatesService } from './guilds-templates.service'; import { CreateGuildTemplateDto } from './dto/create-guild-template.dto'; import { UpdateGuildTemplateDto } from './dto/update-guild-template.dto'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { GuildTemplate } from './entities/guild-template.entity'; @ApiTags('guilds-templates') +@ApiBearerAuth('JWT-auth') @Controller('guilds-templates') export class GuildsTemplatesController { constructor(private readonly guildsTemplatesService: GuildsTemplatesService) {} diff --git a/src/guilds/guilds.controller.ts b/src/guilds/guilds.controller.ts index 9d8a591b..faf07938 100644 --- a/src/guilds/guilds.controller.ts +++ b/src/guilds/guilds.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } fr import { GuildsService } from './guilds.service'; import { CreateGuildDto } from './dto/create-guild.dto'; import { UpdateGuildDto } from './dto/update-guild.dto'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { Guild } from './entities/guild.entity'; @ApiTags('guilds') +@ApiBearerAuth('JWT-auth') @Controller('guilds') export class GuildsController { constructor(private readonly guildService: GuildsService) {} diff --git a/src/members-informations/members-informations.controller.ts b/src/members-informations/members-informations.controller.ts index 91b894dc..ec47b118 100644 --- a/src/members-informations/members-informations.controller.ts +++ b/src/members-informations/members-informations.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } fr import { MembersInformationsService } from './members-informations.service'; import { CreateMemberInformationsDto } from './dto/create-member-informations.dto'; import { UpdateMemberInformationsDto } from './dto/update-member-informations.dto'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { MemberInformation } from './entities/member-information.entity'; @ApiTags('members-informations') +@ApiBearerAuth('JWT-auth') @Controller('members-informations') export class MembersInformationsController { constructor(private readonly membersInformationsService: MembersInformationsService) {} diff --git a/src/members/members.controller.ts b/src/members/members.controller.ts index 862c7cd9..6f6f2e48 100644 --- a/src/members/members.controller.ts +++ b/src/members/members.controller.ts @@ -20,12 +20,14 @@ import { ApiResponse, ApiParam, ApiBody, + ApiBearerAuth } from '@nestjs/swagger'; import { Member } from './entities/member.entity'; import { Role } from '../roles/entities/role.entity'; import { Promotion } from '../promotions/entities/promotion.entity'; @ApiTags('members') +@ApiBearerAuth('JWT-auth') @Controller('members') export class MembersController { constructor(private readonly membersService: MembersService) {} diff --git a/src/promotions/promotions.controller.ts b/src/promotions/promotions.controller.ts index e3e5cc75..e4b18a83 100644 --- a/src/promotions/promotions.controller.ts +++ b/src/promotions/promotions.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException, Htt import { PromotionsService } from './promotions.service'; import { CreatePromotionDto } from './dto/create-promotion.dto'; import { UpdatePromotionDto } from './dto/update-promotion.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiParam } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiParam, ApiBearerAuth } from '@nestjs/swagger'; import { Promotion } from './entities/promotion.entity'; @ApiTags('promotions') +@ApiBearerAuth('JWT-auth') @Controller('promotions') export class PromotionsController { constructor( diff --git a/src/roles/roles.controller.ts b/src/roles/roles.controller.ts index 3b97336f..37b4969c 100644 --- a/src/roles/roles.controller.ts +++ b/src/roles/roles.controller.ts @@ -2,10 +2,11 @@ import { Controller, Get, Post, Body, Patch, Param, Delete, HttpStatus } from '@ import { RolesService } from './roles.service'; import { CreateRoleDto } from './dto/create-role.dto'; import { UpdateRoleDto } from './dto/update-role.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody, ApiBearerAuth } from '@nestjs/swagger'; import { Role } from './entities/role.entity'; @ApiTags('roles') +@ApiBearerAuth('JWT-auth') @Controller('roles') export class RolesController { constructor(private readonly rolesService: RolesService) {} From 9ac3d8aaa50c4bed2cf3a4a80ee6c4dd09c0958a Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Mon, 15 Dec 2025 17:27:20 +0100 Subject: [PATCH 02/20] feat: comment differences with MCD --- src/campuses/dto/create-campus.dto.ts | 2 +- src/categories/dto/create-category.dto.ts | 6 +++--- src/channels/dto/create-channel.dto.ts | 6 +++--- src/courses/dto/create-course.dto.ts | 2 +- src/discord-users/dto/create-discord-user.dto.ts | 2 +- src/guilds-templates/dto/create-guild-template.dto.ts | 2 +- src/guilds/dto/create-guild.dto.ts | 6 +++--- .../dto/create-identification-request.dto.ts | 6 +++--- .../dto/create-member-informations.dto.ts | 8 ++++---- src/members/dto/create-member.dto.ts | 2 +- src/promotions/dto/create-promotion.dto.ts | 2 +- src/roles/dto/create-role.dto.ts | 8 +++++--- 12 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/campuses/dto/create-campus.dto.ts b/src/campuses/dto/create-campus.dto.ts index 2e627993..0098fa0a 100644 --- a/src/campuses/dto/create-campus.dto.ts +++ b/src/campuses/dto/create-campus.dto.ts @@ -3,7 +3,7 @@ import { PickableDtoFields } from 'src/utils/pickable-dto-fields'; import { PickableDiscordUUIDFields } from 'src/utils/pickable-discord-uuid-fields'; export class CreateCampusDto extends PickType(IntersectionType(PickableDtoFields, PickableDiscordUUIDFields), [ - 'name', + 'name', //name_campus dans le MCD 'uuidRole', 'uuidGuild' ]) { diff --git a/src/categories/dto/create-category.dto.ts b/src/categories/dto/create-category.dto.ts index b7c7e3cf..78259724 100644 --- a/src/categories/dto/create-category.dto.ts +++ b/src/categories/dto/create-category.dto.ts @@ -8,7 +8,7 @@ export class CreateCategoryDto { }) @IsString() @Length(17, 19) - uuid: string; + uuid: string; //uuid_category dans le MCD @ApiProperty({ description: 'ID Discord du serveur', @@ -24,7 +24,7 @@ export class CreateCategoryDto { }) @IsString() @MaxLength(50) - name: string; + name: string; //name_category dans le MCD @ApiProperty({ description: 'Position de la catégorie dans le serveur', @@ -32,5 +32,5 @@ export class CreateCategoryDto { }) @IsInt() @Min(0) - position: number; + position: number; // position_category dans le MCD } diff --git a/src/channels/dto/create-channel.dto.ts b/src/channels/dto/create-channel.dto.ts index df039334..2cc0282e 100644 --- a/src/channels/dto/create-channel.dto.ts +++ b/src/channels/dto/create-channel.dto.ts @@ -18,7 +18,7 @@ export class CreateChannelDto extends PickType(PickableDiscordUUIDFields, [ }) @IsString() @Length(17, 19) - uuid: string; + uuid: string; // uuid_channel dans le MCD @ApiProperty({ description: 'Le nom du channel', @@ -27,7 +27,7 @@ export class CreateChannelDto extends PickType(PickableDiscordUUIDFields, [ }) @IsString() @MaxLength(100) - name: string; + name: string; // name_channel dans le MCD @ApiProperty({ description: 'Le type de channel', @@ -45,7 +45,7 @@ export class CreateChannelDto extends PickType(PickableDiscordUUIDFields, [ }) @IsInt() @Min(0) - channelPosition: number; + channelPosition: number; // position_channel dans le MCD uuidGuild: string; uuidCategory: string; diff --git a/src/courses/dto/create-course.dto.ts b/src/courses/dto/create-course.dto.ts index 551d66ec..c13faa89 100644 --- a/src/courses/dto/create-course.dto.ts +++ b/src/courses/dto/create-course.dto.ts @@ -10,7 +10,7 @@ export class CreateCourseDto extends PickType(IntersectionType(PickableDiscordUU 'uuidCategory' ]) { @MinLength(3) - name: string; + name: string; //name_course dans le MCD @ApiProperty({ description: 'Si la formation est certifiante', diff --git a/src/discord-users/dto/create-discord-user.dto.ts b/src/discord-users/dto/create-discord-user.dto.ts index bcab7dcc..e6903e3c 100644 --- a/src/discord-users/dto/create-discord-user.dto.ts +++ b/src/discord-users/dto/create-discord-user.dto.ts @@ -11,7 +11,7 @@ export class CreateDiscordUserDto extends PickType(PickableDiscordUUIDFields, [ }) @IsString() @Length(2, 50) - discordUsername: string; + discordUsername: string; //name_discord_user dans le MCD @ApiProperty({ description: 'Discriminateur Discord', diff --git a/src/guilds-templates/dto/create-guild-template.dto.ts b/src/guilds-templates/dto/create-guild-template.dto.ts index b816df80..f32692cc 100644 --- a/src/guilds-templates/dto/create-guild-template.dto.ts +++ b/src/guilds-templates/dto/create-guild-template.dto.ts @@ -14,7 +14,7 @@ export class CreateGuildTemplateDto extends PickType(IntersectionType(PickableDi }) @IsString() @Length(17, 19) - uuid: string; + uuid: string; // uuid_guild_template dans le MCD @ApiProperty({ description: 'Nom du template', diff --git a/src/guilds/dto/create-guild.dto.ts b/src/guilds/dto/create-guild.dto.ts index 8a2eccde..3f68645a 100644 --- a/src/guilds/dto/create-guild.dto.ts +++ b/src/guilds/dto/create-guild.dto.ts @@ -8,7 +8,7 @@ export class CreateGuildDto { }) @IsString() @Length(17, 19) - uuid: string; + uuid: string; // uuid_guild dans le MCD @ApiProperty({ description: 'Nom du serveur', @@ -16,7 +16,7 @@ export class CreateGuildDto { }) @IsString() @MaxLength(50) - name: string; + name: string; // name_guild dans le MCD @ApiProperty({ description: 'Nombre de membres dans le serveur', @@ -24,7 +24,7 @@ export class CreateGuildDto { }) @IsString() @MaxLength(50) - memberCount: string; + memberCount: string; // member_count_guild dans le MCD @ApiProperty({ description: 'Configuration du serveur', diff --git a/src/identification-requests/dto/create-identification-request.dto.ts b/src/identification-requests/dto/create-identification-request.dto.ts index a31469d9..51182efb 100644 --- a/src/identification-requests/dto/create-identification-request.dto.ts +++ b/src/identification-requests/dto/create-identification-request.dto.ts @@ -8,14 +8,14 @@ export class CreateIdentificationRequestDto extends PickType(PickableInternUUIDF @IsString() @Length(2, 50) - firstname: string; + firstname: string; // fistname_identification_request dans le MCD @IsString() @Length(2, 50) - lastname: string; + lastname: string; // lastname_identification_request dans le MCD @IsEmail() - email: string; + email: string; // email_identification_request dans le MCD uuidMember: string; diff --git a/src/members-informations/dto/create-member-informations.dto.ts b/src/members-informations/dto/create-member-informations.dto.ts index 6c7b8ec0..e41eee3d 100644 --- a/src/members-informations/dto/create-member-informations.dto.ts +++ b/src/members-informations/dto/create-member-informations.dto.ts @@ -3,7 +3,7 @@ import { ApiProperty, PickType } from '@nestjs/swagger'; import { PickableInternUUIDFields } from 'src/utils/pickable-intern-uuid-fields'; export class CreateMemberInformationsDto extends PickType(PickableInternUUIDFields, [ - 'uuidMember' + 'uuidMember' // uuid_member_info dans le MCD ]) { @ApiProperty({ @@ -12,7 +12,7 @@ export class CreateMemberInformationsDto extends PickType(PickableInternUUIDFiel }) @IsString() @MaxLength(50) - firstName: string; + firstName: string; // firstname_member_info dans le MCD @ApiProperty({ description: 'Nom de famille du membre', @@ -20,7 +20,7 @@ export class CreateMemberInformationsDto extends PickType(PickableInternUUIDFiel }) @IsString() @MaxLength(50) - lastName: string; + lastName: string; // lastname_member_info dans le MCD @ApiProperty({ description: 'Adresse email du membre', @@ -28,7 +28,7 @@ export class CreateMemberInformationsDto extends PickType(PickableInternUUIDFiel }) @IsEmail() @MaxLength(100) - email: string; + email: string; // email_member_info dans le MCD uuidMember: string; } diff --git a/src/members/dto/create-member.dto.ts b/src/members/dto/create-member.dto.ts index 872cc25b..aaf7a84b 100644 --- a/src/members/dto/create-member.dto.ts +++ b/src/members/dto/create-member.dto.ts @@ -13,7 +13,7 @@ export class CreateMemberDto extends PickType(PickableDiscordUUIDFields, [ }) @IsString() @MaxLength(50) - guildUsername: string; + guildUsername: string; //name_member dans le MCD @ApiProperty({ description: 'Points d\'expérience du membre', diff --git a/src/promotions/dto/create-promotion.dto.ts b/src/promotions/dto/create-promotion.dto.ts index a3bccb98..72de77f0 100644 --- a/src/promotions/dto/create-promotion.dto.ts +++ b/src/promotions/dto/create-promotion.dto.ts @@ -6,7 +6,7 @@ import { PickableDiscordUUIDFields } from 'src/utils/pickable-discord-uuid-field import { PickableInternUUIDFields } from 'src/utils/pickable-intern-uuid-fields'; export class CreatePromotionDto extends PickType(IntersectionType(PickableDtoFields, PickableDiscordUUIDFields, PickableInternUUIDFields), [ - 'name', + 'name', // name_promotion dans le MCD 'uuidRole', 'uuidGuild', 'uuidCourse', diff --git a/src/roles/dto/create-role.dto.ts b/src/roles/dto/create-role.dto.ts index 8db2a4f1..aba3b681 100644 --- a/src/roles/dto/create-role.dto.ts +++ b/src/roles/dto/create-role.dto.ts @@ -4,7 +4,9 @@ import { PickableDtoFields } from 'src/utils/pickable-dto-fields'; import { PickableDiscordUUIDFields } from 'src/utils/pickable-discord-uuid-fields'; export class CreateRoleDto extends PickType(IntersectionType(PickableDtoFields, PickableDiscordUUIDFields), [ - 'uuidRole', 'name', 'uuidGuild' + 'uuidRole', + 'name', // name_role dans le MCD + 'uuidGuild' ]) { @ApiProperty({ @@ -15,7 +17,7 @@ export class CreateRoleDto extends PickType(IntersectionType(PickableDtoFields, @IsString() @MaxLength(50) @Matches(/^\d+$/, { message: 'memberCount doit être une chaîne numérique' }) - memberCount: string = '0'; + memberCount: string = '0'; // member_count_role dans le MCD @ApiProperty({ description: 'Position du rôle dans la hiérarchie', @@ -24,7 +26,7 @@ export class CreateRoleDto extends PickType(IntersectionType(PickableDtoFields, @IsString() @MaxLength(50) @Matches(/^\d+$/, { message: 'rolePosition doit être une chaîne numérique' }) - rolePosition: string; + rolePosition: string; // position_role dans le MCD @ApiProperty({ description: 'Indique si le rôle est affiché séparément dans la liste des membres', From 3fba8a8a649d80e2036bfc8e14f6054c589e5bb3 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:34:57 +0200 Subject: [PATCH 03/20] refactor(86c9kt142): implement interface for campuses module --- src/campuses/campuses.controller.ts | 63 +++++++++++++++++---- src/campuses/campuses.module.ts | 9 ++- src/campuses/campuses.service.ts | 21 ++++--- src/campuses/interfaces/campus.interface.ts | 13 +++++ 4 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 src/campuses/interfaces/campus.interface.ts diff --git a/src/campuses/campuses.controller.ts b/src/campuses/campuses.controller.ts index fda53ed1..47d752bc 100644 --- a/src/campuses/campuses.controller.ts +++ b/src/campuses/campuses.controller.ts @@ -1,19 +1,44 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { CampusesService } from './campuses.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateCampusDto } from './dto/create-campus.dto'; import { UpdateCampusDto } from './dto/update-campus.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; import { Campus } from './entities/campus.entity'; +import { + ICampusesServiceToken, + ICampusesService, +} from './interfaces/campus.interface'; @ApiTags('campuses') @ApiBearerAuth('JWT-auth') @Controller('campuses') export class CampusesController { - constructor(private readonly campusService: CampusesService) {} + constructor( + @Inject(ICampusesServiceToken) + private readonly campusService: ICampusesService, + ) {} @Post() @ApiOperation({ summary: 'Créer un nouveau campus' }) - @ApiResponse({ status: 201, description: 'Le campus a été créé avec succès.', type: Campus }) + @ApiResponse({ + status: 201, + description: 'Le campus a été créé avec succès.', + type: Campus, + }) @ApiResponse({ status: 400, description: 'Requête invalide' }) create(@Body() createCampusDto: CreateCampusDto) { return this.campusService.create(createCampusDto); @@ -21,14 +46,22 @@ export class CampusesController { @Get() @ApiOperation({ summary: 'Récupérer tous les campus' }) - @ApiResponse({ status: 200, description: 'Liste des campus récupérée avec succès.', type: [Campus] }) + @ApiResponse({ + status: 200, + description: 'Liste des campus récupérée avec succès.', + type: [Campus], + }) findAll() { return this.campusService.findAll(); } @Get(':uuid') @ApiOperation({ summary: 'Récupérer un campus par son UUID' }) - @ApiResponse({ status: 200, description: 'Le campus a été trouvé.', type: Campus }) + @ApiResponse({ + status: 200, + description: 'Le campus a été trouvé.', + type: Campus, + }) @ApiResponse({ status: 404, description: 'Campus non trouvé' }) findOne(@Param('uuid') uuid: string) { return this.campusService.findOne(uuid); @@ -36,9 +69,16 @@ export class CampusesController { @Put(':uuid') @ApiOperation({ summary: 'Mettre à jour un campus' }) - @ApiResponse({ status: 200, description: 'Le campus a été mis à jour avec succès.', type: Campus }) + @ApiResponse({ + status: 200, + description: 'Le campus a été mis à jour avec succès.', + type: Campus, + }) @ApiResponse({ status: 404, description: 'Campus non trouvé' }) - async update(@Param('uuid') uuid: string, @Body() updateCampusDto: UpdateCampusDto) { + async update( + @Param('uuid') uuid: string, + @Body() updateCampusDto: UpdateCampusDto, + ) { const campus = await this.campusService.update(uuid, updateCampusDto); if (!campus) { throw new NotFoundException(`Campus with UUID "${uuid}" not found`); @@ -48,7 +88,10 @@ export class CampusesController { @Delete(':uuid') @ApiOperation({ summary: 'Supprimer un campus' }) - @ApiResponse({ status: 200, description: 'Le campus a été supprimé avec succès.' }) + @ApiResponse({ + status: 200, + description: 'Le campus a été supprimé avec succès.', + }) @ApiResponse({ status: 404, description: 'Campus non trouvé' }) remove(@Param('uuid') uuid: string) { return this.campusService.remove(uuid); diff --git a/src/campuses/campuses.module.ts b/src/campuses/campuses.module.ts index a64b7dc3..3049f5c8 100644 --- a/src/campuses/campuses.module.ts +++ b/src/campuses/campuses.module.ts @@ -4,10 +4,17 @@ import { CampusesService } from './campuses.service'; import { CampusesController } from './campuses.controller'; import { Campus } from './entities/campus.entity'; import { RolesModule } from 'src/roles/roles.module'; +import { ICampusesServiceToken } from './interfaces/campus.interface'; @Module({ imports: [TypeOrmModule.forFeature([Campus]), RolesModule], controllers: [CampusesController], - providers: [CampusesService], + providers: [ + { + provide: ICampusesServiceToken, + useClass: CampusesService, + }, + ], + exports: [ICampusesServiceToken], }) export class CampusesModule {} diff --git a/src/campuses/campuses.service.ts b/src/campuses/campuses.service.ts index ff64d822..3e91515e 100644 --- a/src/campuses/campuses.service.ts +++ b/src/campuses/campuses.service.ts @@ -1,13 +1,18 @@ -import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common'; +import { + Injectable, + BadRequestException, + NotFoundException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateCampusDto } from './dto/create-campus.dto'; import { UpdateCampusDto } from './dto/update-campus.dto'; -import { Campus } from './entities/campus.entity'; import { Role } from 'src/roles/entities/role.entity'; +import { Campus } from './entities/campus.entity'; +import { ICampusesService } from './interfaces/campus.interface'; @Injectable() -export class CampusesService { +export class CampusesService implements ICampusesService { constructor( @InjectRepository(Campus) private campusRepository: Repository, @@ -19,13 +24,13 @@ export class CampusesService { async create(createCampusDto: CreateCampusDto): Promise { try { const newRole = this.roleRepository.create({ - uuidRole: createCampusDto.uuidRole, + uuidRole: createCampusDto.uuidRole, uuidGuild: createCampusDto.uuidGuild, name: createCampusDto.name, memberCount: 0, rolePosition: 0, hoist: false, - color: "#000000", + color: '#000000', }); const savedRole = await this.roleRepository.save(newRole); @@ -37,11 +42,13 @@ export class CampusesService { return await this.campusRepository.save(newCampus); } catch (error) { - throw new BadRequestException('Erreur lors de la création du campus: ' + error.message); + throw new BadRequestException( + 'Erreur lors de la création du campus: ' + error.message, + ); } } - findAll() { + findAll(): Promise { return this.campusRepository.find(); } diff --git a/src/campuses/interfaces/campus.interface.ts b/src/campuses/interfaces/campus.interface.ts new file mode 100644 index 00000000..72465874 --- /dev/null +++ b/src/campuses/interfaces/campus.interface.ts @@ -0,0 +1,13 @@ +import { CreateCampusDto } from '../dto/create-campus.dto'; +import { UpdateCampusDto } from '../dto/update-campus.dto'; +import { Campus } from '../entities/campus.entity'; + +export interface ICampusesService { + create(createCampusDto: CreateCampusDto): Promise; + findAll(): Promise; + findOne(uuid: string); + update(uuid: string, updateCampusDto: UpdateCampusDto): Promise; + remove(uuid: string); +} + +export const ICampusesServiceToken = Symbol('ICampusesService'); From 1d9fc9ad5f444e34c12e5cfa1590f08c01729df7 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:39:36 +0200 Subject: [PATCH 04/20] refactor(86c9kt186): implement interface for categories module --- src/categories/categories.controller.ts | 68 ++++++++++++++++--- src/categories/categories.module.ts | 9 ++- src/categories/categories.service.ts | 19 +++--- .../interfaces/category.interface.ts | 12 ++++ 4 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 src/categories/interfaces/category.interface.ts diff --git a/src/categories/categories.controller.ts b/src/categories/categories.controller.ts index 75a5d1ca..a62d2fa2 100644 --- a/src/categories/categories.controller.ts +++ b/src/categories/categories.controller.ts @@ -1,19 +1,44 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { CategoriesService } from './categories.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateCategoryDto } from './dto/create-category.dto'; import { UpdateCategoryDto } from './dto/update-category.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; import { Category } from './entities/category.entity'; +import { + ICategoriesServiceToken, + ICategoriesService, +} from './interfaces/category.interface'; @ApiTags('categories') @ApiBearerAuth('JWT-auth') @Controller('categories') export class CategoriesController { - constructor(private readonly categoriesService: CategoriesService) {} + constructor( + @Inject(ICategoriesServiceToken) + private readonly categoriesService: ICategoriesService, + ) {} @Post() @ApiOperation({ summary: 'Créer une nouvelle catégorie' }) - @ApiResponse({ status: 201, description: 'La catégorie a été créée avec succès.', type: Category }) + @ApiResponse({ + status: 201, + description: 'La catégorie a été créée avec succès.', + type: Category, + }) @ApiResponse({ status: 400, description: 'Requête invalide' }) create(@Body() createCategoryDto: CreateCategoryDto) { return this.categoriesService.create(createCategoryDto); @@ -21,14 +46,22 @@ export class CategoriesController { @Get() @ApiOperation({ summary: 'Récupérer toutes les catégories' }) - @ApiResponse({ status: 200, description: 'Liste des catégories récupérée avec succès.', type: [Category] }) + @ApiResponse({ + status: 200, + description: 'Liste des catégories récupérée avec succès.', + type: [Category], + }) findAll() { return this.categoriesService.findAll(); } @Get(':uuid') @ApiOperation({ summary: 'Récupérer une catégorie par son UUID' }) - @ApiResponse({ status: 200, description: 'La catégorie a été trouvée.', type: Category }) + @ApiResponse({ + status: 200, + description: 'La catégorie a été trouvée.', + type: Category, + }) @ApiResponse({ status: 404, description: 'Catégorie non trouvée' }) findOne(@Param('uuid') uuid: string) { return this.categoriesService.findOne(uuid); @@ -36,10 +69,20 @@ export class CategoriesController { @Put(':uuid') @ApiOperation({ summary: 'Mettre à jour une catégorie' }) - @ApiResponse({ status: 200, description: 'La catégorie a été mise à jour avec succès.', type: Category }) + @ApiResponse({ + status: 200, + description: 'La catégorie a été mise à jour avec succès.', + type: Category, + }) @ApiResponse({ status: 404, description: 'Catégorie non trouvée' }) - async update(@Param('uuid') uuid: string, @Body() updateCategoryDto: UpdateCategoryDto) { - const category = await this.categoriesService.update(uuid, updateCategoryDto); + async update( + @Param('uuid') uuid: string, + @Body() updateCategoryDto: UpdateCategoryDto, + ) { + const category = await this.categoriesService.update( + uuid, + updateCategoryDto, + ); if (!category) { throw new NotFoundException(`Category with UUID "${uuid}" not found`); } @@ -48,7 +91,10 @@ export class CategoriesController { @Delete(':uuid') @ApiOperation({ summary: 'Supprimer une catégorie' }) - @ApiResponse({ status: 200, description: 'La catégorie a été supprimée avec succès.' }) + @ApiResponse({ + status: 200, + description: 'La catégorie a été supprimée avec succès.', + }) @ApiResponse({ status: 404, description: 'Catégorie non trouvée' }) remove(@Param('uuid') uuid: string) { return this.categoriesService.remove(uuid); diff --git a/src/categories/categories.module.ts b/src/categories/categories.module.ts index bd73adf5..2967d9f0 100644 --- a/src/categories/categories.module.ts +++ b/src/categories/categories.module.ts @@ -4,10 +4,17 @@ import { CategoriesController } from './categories.controller'; import { Category } from './entities/category.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ICategoriesServiceToken } from './interfaces/category.interface'; @Module({ imports: [TypeOrmModule.forFeature([Category])], controllers: [CategoriesController], - providers: [CategoriesService], + providers: [ + { + provide: ICategoriesServiceToken, + useClass: CategoriesService, + }, + ], + exports: [ICategoriesServiceToken], }) export class CategoriesModule {} diff --git a/src/categories/categories.service.ts b/src/categories/categories.service.ts index e9d8ed6f..b798d5af 100644 --- a/src/categories/categories.service.ts +++ b/src/categories/categories.service.ts @@ -7,10 +7,10 @@ import { CreateCategoryDto } from './dto/create-category.dto'; import { UpdateCategoryDto } from './dto/update-category.dto'; import { Category } from './entities/category.entity'; +import { ICategoriesService } from './interfaces/category.interface'; @Injectable() -export class CategoriesService { - +export class CategoriesService implements ICategoriesService { constructor( @InjectRepository(Category) private categoryRepository: Repository, @@ -28,8 +28,8 @@ export class CategoriesService { channels: true, course: true, promotion: true, - guildTemplate: true - } + guildTemplate: true, + }, }); } @@ -41,8 +41,8 @@ export class CategoriesService { channels: true, course: true, promotion: true, - guildTemplate: true - } + guildTemplate: true, + }, }); } @@ -51,13 +51,14 @@ export class CategoriesService { if (!category) { return null; } - + // Mise à jour des champs autorisés uniquement const { name, position, uuidGuildTemplate } = updateCategoryDto; if (name !== undefined) category.name = name; if (position !== undefined) category.position = position; - if (uuidGuildTemplate !== undefined) category.uuidGuildTemplate = uuidGuildTemplate; - + if (uuidGuildTemplate !== undefined) + category.uuidGuildTemplate = uuidGuildTemplate; + category.updatedAt = new Date(); return this.categoryRepository.save(category); } diff --git a/src/categories/interfaces/category.interface.ts b/src/categories/interfaces/category.interface.ts new file mode 100644 index 00000000..0e6317e4 --- /dev/null +++ b/src/categories/interfaces/category.interface.ts @@ -0,0 +1,12 @@ +import { CreateCategoryDto } from '../dto/create-category.dto'; +import { UpdateCategoryDto } from '../dto/update-category.dto'; + +export interface ICategoriesService { + create(createCategoryDto: CreateCategoryDto); + findAll(); + findOne(uuid: string); + update(uuid: string, updateCategoryDto: UpdateCategoryDto); + remove(uuid: string); +} + +export const ICategoriesServiceToken = Symbol('ICategoriesService'); From b2fb06f89312acb8b7e5eeb02ccca78899db0988 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:45:06 +0200 Subject: [PATCH 05/20] refactor(86c9kt9cf): implement interface for channels module --- src/channels/channels.controller.ts | 61 ++++++++++++++++---- src/channels/channels.module.ts | 12 +++- src/channels/channels.service.ts | 16 ++--- src/channels/interfaces/channel.interface.ts | 12 ++++ 4 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 src/channels/interfaces/channel.interface.ts diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index ae683a06..2b641a7a 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -1,19 +1,45 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { ChannelsService } from './channels.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateChannelDto } from './dto/create-channel.dto'; import { UpdateChannelDto } from './dto/update-channel.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiParam, + ApiBearerAuth, +} from '@nestjs/swagger'; import { Channel } from './entities/channel.entity'; +import { + IChannelsServiceToken, + IChannelsService, +} from './interfaces/channel.interface'; @ApiTags('channels') @ApiBearerAuth('JWT-auth') @Controller('channels') export class ChannelsController { - constructor(private readonly channelService: ChannelsService) {} + constructor( + @Inject(IChannelsServiceToken) + private readonly channelService: IChannelsService, + ) {} @Post() @ApiOperation({ summary: 'Créer un nouveau channel' }) - @ApiResponse({ status: 201, description: 'Le channel a été créé avec succès.', type: Channel }) + @ApiResponse({ + status: 201, + description: 'Le channel a été créé avec succès.', + type: Channel, + }) @ApiResponse({ status: 400, description: 'Requête invalide' }) create(@Body() createChannelDto: CreateChannelDto) { return this.channelService.create(createChannelDto); @@ -21,7 +47,11 @@ export class ChannelsController { @Get() @ApiOperation({ summary: 'Récupérer tous les channels' }) - @ApiResponse({ status: 200, description: 'Liste des channels récupérée avec succès.', type: [Channel] }) + @ApiResponse({ + status: 200, + description: 'Liste des channels récupérée avec succès.', + type: [Channel], + }) findAll() { return this.channelService.findAll(); } @@ -29,7 +59,11 @@ export class ChannelsController { @Get(':uuid') @ApiOperation({ summary: 'Récupérer un channel par son UUID' }) @ApiParam({ name: 'uuid', description: 'UUID du channel' }) - @ApiResponse({ status: 200, description: 'Le channel a été trouvé.', type: Channel }) + @ApiResponse({ + status: 200, + description: 'Le channel a été trouvé.', + type: Channel, + }) @ApiResponse({ status: 404, description: 'Channel non trouvé' }) findOne(@Param('uuid') uuid: string) { return this.channelService.findOne(uuid); @@ -38,10 +72,17 @@ export class ChannelsController { @Put(':uuid') @ApiOperation({ summary: 'Mettre à jour un channel' }) @ApiParam({ name: 'uuid', description: 'UUID du channel' }) - @ApiResponse({ status: 200, description: 'Le channel a été mis à jour.', type: Channel }) + @ApiResponse({ + status: 200, + description: 'Le channel a été mis à jour.', + type: Channel, + }) @ApiResponse({ status: 404, description: 'Channel non trouvé' }) @ApiResponse({ status: 400, description: 'Requête invalide' }) - async update(@Param('uuid') uuid: string, @Body() updateChannelDto: UpdateChannelDto) { + async update( + @Param('uuid') uuid: string, + @Body() updateChannelDto: UpdateChannelDto, + ) { const channel = await this.channelService.update(uuid, updateChannelDto); if (!channel) { throw new NotFoundException(`Channel with UUID "${uuid}" not found`); @@ -57,4 +98,4 @@ export class ChannelsController { remove(@Param('uuid') uuid: string) { return this.channelService.remove(uuid); } -} \ No newline at end of file +} diff --git a/src/channels/channels.module.ts b/src/channels/channels.module.ts index 20133780..01230ce5 100644 --- a/src/channels/channels.module.ts +++ b/src/channels/channels.module.ts @@ -3,11 +3,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ChannelsService } from './channels.service'; import { ChannelsController } from './channels.controller'; import { Channel } from './entities/channel.entity'; +import { IChannelsServiceToken } from './interfaces/channel.interface'; @Module({ imports: [TypeOrmModule.forFeature([Channel])], controllers: [ChannelsController], - providers: [ChannelsService], - exports: [ChannelsService] + providers: [ + { + provide: IChannelsServiceToken, + useClass: ChannelsService, + }, + ], + exports: [IChannelsServiceToken], }) -export class ChannelsModule {} \ No newline at end of file +export class ChannelsModule {} diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index 30ad9263..96991237 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -4,9 +4,10 @@ import { Repository } from 'typeorm'; import { CreateChannelDto } from './dto/create-channel.dto'; import { UpdateChannelDto } from './dto/update-channel.dto'; import { Channel } from './entities/channel.entity'; +import { IChannelsService } from './interfaces/channel.interface'; @Injectable() -export class ChannelsService { +export class ChannelsService implements IChannelsService { constructor( @InjectRepository(Channel) private channelRepository: Repository, @@ -19,14 +20,14 @@ export class ChannelsService { findAll() { return this.channelRepository.find({ - relations: ['guild', 'category'] + relations: ['guild', 'category'], }); } findOne(uuid: string) { return this.channelRepository.findOne({ where: { uuid }, - relations: ['guild', 'category'] + relations: ['guild', 'category'], }); } @@ -35,14 +36,15 @@ export class ChannelsService { if (!channel) { return null; } - + // Mise à jour des champs autorisés uniquement const { name, type, channelPosition, uuidCategory } = updateChannelDto; if (name !== undefined) channel.name = name; if (type !== undefined) channel.type = type; - if (channelPosition !== undefined) channel.channelPosition = channelPosition; + if (channelPosition !== undefined) + channel.channelPosition = channelPosition; if (uuidCategory !== undefined) channel.uuidCategory = uuidCategory; - + channel.updatedAt = new Date(); return this.channelRepository.save(channel); } @@ -50,4 +52,4 @@ export class ChannelsService { remove(uuid: string) { return this.channelRepository.delete({ uuid }); } -} \ No newline at end of file +} diff --git a/src/channels/interfaces/channel.interface.ts b/src/channels/interfaces/channel.interface.ts new file mode 100644 index 00000000..4ec836a0 --- /dev/null +++ b/src/channels/interfaces/channel.interface.ts @@ -0,0 +1,12 @@ +import { CreateChannelDto } from '../dto/create-channel.dto'; +import { UpdateChannelDto } from '../dto/update-channel.dto'; + +export interface IChannelsService { + create(createChannelDto: CreateChannelDto); + findAll(); + findOne(uuid: string); + update(uuid: string, updateChannelDto: UpdateChannelDto); + remove(uuid: string); +} + +export const IChannelsServiceToken = Symbol('IChannelsService'); From f02878ce3daeb7d033d3b8cbc2b85072d70b6e3a Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:46:57 +0200 Subject: [PATCH 06/20] refactor(86c9kt15m): implement interface for courses module --- src/courses/courses.controller.ts | 30 ++- src/courses/courses.module.ts | 35 +-- src/courses/courses.service.ts | 236 +++++++++++---------- src/courses/interfaces/course.interface.ts | 13 ++ 4 files changed, 177 insertions(+), 137 deletions(-) create mode 100644 src/courses/interfaces/course.interface.ts diff --git a/src/courses/courses.controller.ts b/src/courses/courses.controller.ts index 6eea05e6..b624a7d9 100644 --- a/src/courses/courses.controller.ts +++ b/src/courses/courses.controller.ts @@ -7,8 +7,12 @@ import { Param, Body, HttpStatus, + Inject, } from '@nestjs/common'; -import { CoursesService } from './courses.service'; +import { + ICoursesServiceToken, + ICoursesService, +} from './interfaces/course.interface'; import { CreateCourseDto } from './dto/create-course.dto'; import { UpdateCourseDto } from './dto/update-course.dto'; import { Course } from './entities/course.entity'; @@ -18,14 +22,17 @@ import { ApiResponse, ApiTags, ApiBody, - ApiBearerAuth + ApiBearerAuth, } from '@nestjs/swagger'; @ApiTags('courses') @ApiBearerAuth('JWT-auth') @Controller('courses') export class CoursesController { - constructor(private readonly coursesService: CoursesService) {} + constructor( + @Inject(ICoursesServiceToken) + private readonly coursesService: ICoursesService, + ) {} @Post() @ApiOperation({ @@ -49,20 +56,21 @@ export class CoursesController { @Get() @ApiOperation({ - summary: 'Récupérer toutes les formations', - description: 'Retourne la liste complète des formations avec leurs relations' + summary: 'Récupérer toutes les formations', + description: + 'Retourne la liste complète des formations avec leurs relations', }) @ApiResponse({ - status: HttpStatus.OK, - description: 'Liste des formations récupérée avec succès', - type: [Course] + status: HttpStatus.OK, + description: 'Liste des formations récupérée avec succès', + type: [Course], }) @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: 'Erreur lors de la récupération des formations' + status: HttpStatus.BAD_REQUEST, + description: 'Erreur lors de la récupération des formations', }) async findAll(): Promise { - return this.coursesService.findAll(); + return this.coursesService.findAll(); } @Get(':uuid_course') diff --git a/src/courses/courses.module.ts b/src/courses/courses.module.ts index fa5fff38..04635522 100644 --- a/src/courses/courses.module.ts +++ b/src/courses/courses.module.ts @@ -1,19 +1,22 @@ -import { Module } from "@nestjs/common"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { CoursesController } from "./courses.controller"; -import { CoursesService } from "./courses.service"; -import { Course } from "./entities/course.entity"; -import { Category } from "../categories/entities/category.entity"; -import { Guild } from "../guilds/entities/guild.entity"; -import { RolesModule } from "../roles/roles.module"; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CoursesController } from './courses.controller'; +import { CoursesService } from './courses.service'; +import { Course } from './entities/course.entity'; +import { Category } from '../categories/entities/category.entity'; +import { Guild } from '../guilds/entities/guild.entity'; +import { RolesModule } from '../roles/roles.module'; +import { ICoursesServiceToken } from './interfaces/course.interface'; @Module({ - imports: [ - TypeOrmModule.forFeature([Course, Category, Guild]), - RolesModule - ], - controllers: [CoursesController], - providers: [CoursesService], - exports: [CoursesService], + imports: [TypeOrmModule.forFeature([Course, Category, Guild]), RolesModule], + controllers: [CoursesController], + providers: [ + { + provide: ICoursesServiceToken, + useClass: CoursesService, + }, + ], + exports: [ICoursesServiceToken], }) -export class CoursesModule {} \ No newline at end of file +export class CoursesModule {} diff --git a/src/courses/courses.service.ts b/src/courses/courses.service.ts index 881e760e..201cd791 100644 --- a/src/courses/courses.service.ts +++ b/src/courses/courses.service.ts @@ -1,129 +1,145 @@ -import { ConflictException, Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; +import { + ConflictException, + Injectable, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Course } from './entities/course.entity'; import { Role } from 'src/roles/entities/role.entity'; import { CreateCourseDto } from './dto/create-course.dto'; import { UpdateCourseDto } from './dto/update-course.dto'; +import { ICoursesService } from './interfaces/course.interface'; @Injectable() -export class CoursesService { - constructor( - @InjectRepository(Course) - private readonly courseRepository: Repository, - - @InjectRepository(Role) - private readonly roleRepository: Repository, - - ) {} - - async create(createCourseDto: CreateCourseDto): Promise { - try { - const existingCourse = await this.courseRepository.findOne({ - where: { name: createCourseDto.name }, - }); - - if (existingCourse) { - throw new ConflictException(`Course with name ${createCourseDto.name} already exists`); - } - - const courseData = { - name: createCourseDto.name, - isCertified: createCourseDto.isCertified, - uuidGuild: createCourseDto.uuidGuild, - uuidCategory: createCourseDto.uuidCategory, - }; - - const newCourse = this.courseRepository.create(courseData); - const savedCourse = await this.courseRepository.save(newCourse); - - if (createCourseDto.uuidRole) { - const role = await this.roleRepository.findOne({ - where: { uuidRole: createCourseDto.uuidRole } - }); - - if (role) { - await this.courseRepository - .createQueryBuilder() - .relation(Course, "roles") - .of(savedCourse) - .add(role.uuidRole); - } - } - - const courseWithRelations = await this.courseRepository.findOne({ - where: { uuid: savedCourse.uuid }, - relations: { - category: true, - guild: true, - roles: true, - promotions: true, - channels: true - } - }); - - if (!courseWithRelations) { - throw new NotFoundException(`Course with UUID ${savedCourse.uuid} not found after creation`); - } - - return courseWithRelations; - } catch (error) { - throw new BadRequestException('Erreur lors de la création du cours: ' + error.message); - } - } - - async findAll(): Promise { - return await this.courseRepository.find({ - relations: ['category', 'guild', 'roles', 'promotions', 'channels'], - }); - } - - async getByUUID(uuid: string): Promise { - const course = await this.courseRepository.findOne({ - where: { uuid }, - relations: ['category', 'guild', 'roles', 'promotions', 'channels'], +export class CoursesService implements ICoursesService { + constructor( + @InjectRepository(Course) + private readonly courseRepository: Repository, + + @InjectRepository(Role) + private readonly roleRepository: Repository, + ) {} + + async create(createCourseDto: CreateCourseDto): Promise { + try { + const existingCourse = await this.courseRepository.findOne({ + where: { name: createCourseDto.name }, + }); + + if (existingCourse) { + throw new ConflictException( + `Course with name ${createCourseDto.name} already exists`, + ); + } + + const courseData = { + name: createCourseDto.name, + isCertified: createCourseDto.isCertified, + uuidGuild: createCourseDto.uuidGuild, + uuidCategory: createCourseDto.uuidCategory, + }; + + const newCourse = this.courseRepository.create(courseData); + const savedCourse = await this.courseRepository.save(newCourse); + + if (createCourseDto.uuidRole) { + const role = await this.roleRepository.findOne({ + where: { uuidRole: createCourseDto.uuidRole }, }); - if (!course) { - throw new NotFoundException(`Course with UUID ${uuid} not found`); + if (role) { + await this.courseRepository + .createQueryBuilder() + .relation(Course, 'roles') + .of(savedCourse) + .add(role.uuidRole); } - return course; + } + + const courseWithRelations = await this.courseRepository.findOne({ + where: { uuid: savedCourse.uuid }, + relations: { + category: true, + guild: true, + roles: true, + promotions: true, + channels: true, + }, + }); + + if (!courseWithRelations) { + throw new NotFoundException( + `Course with UUID ${savedCourse.uuid} not found after creation`, + ); + } + + return courseWithRelations; + } catch (error) { + throw new BadRequestException( + 'Erreur lors de la création du cours: ' + error.message, + ); + } + } + + async findAll(): Promise { + return await this.courseRepository.find({ + relations: ['category', 'guild', 'roles', 'promotions', 'channels'], + }); + } + + async getByUUID(uuid: string): Promise { + const course = await this.courseRepository.findOne({ + where: { uuid }, + relations: ['category', 'guild', 'roles', 'promotions', 'channels'], + }); + + if (!course) { + throw new NotFoundException(`Course with UUID ${uuid} not found`); + } + return course; + } + + async updateByUUID( + uuid: string, + updateCourseDto: UpdateCourseDto, + ): Promise { + const course = await this.courseRepository.findOne({ + where: { uuid }, + relations: ['category', 'guild', 'roles', 'promotions', 'channels'], + }); + + if (!course) { + throw new NotFoundException(`Course with UUID ${uuid} not found`); } - async updateByUUID(uuid: string, updateCourseDto: UpdateCourseDto): Promise { - const course = await this.courseRepository.findOne({ - where: { uuid }, - relations: ['category', 'guild', 'roles', 'promotions', 'channels'] - }); + if (updateCourseDto.name) { + const existingCourse = await this.courseRepository.findOne({ + where: { name: updateCourseDto.name }, + }); + if (existingCourse && existingCourse.uuid !== uuid) { + throw new ConflictException( + `Course with name ${updateCourseDto.name} already exists`, + ); + } + } - if (!course) { - throw new NotFoundException(`Course with UUID ${uuid} not found`); - } + Object.assign(course, updateCourseDto); + return await this.courseRepository.save(course); + } - if (updateCourseDto.name) { - const existingCourse = await this.courseRepository.findOne({ - where: { name: updateCourseDto.name }, - }); - if (existingCourse && existingCourse.uuid !== uuid) { - throw new ConflictException(`Course with name ${updateCourseDto.name} already exists`); - } - } + async deleteByUUID(uuid: string): Promise { + const course = await this.courseRepository.findOne({ + where: { uuid }, + }); - Object.assign(course, updateCourseDto); - return await this.courseRepository.save(course); + if (!course) { + throw new NotFoundException(`Course with UUID ${uuid} not found`); } - - async deleteByUUID(uuid: string): Promise { - const course = await this.courseRepository.findOne({ - where: { uuid } - }); - - if (!course) { - throw new NotFoundException(`Course with UUID ${uuid} not found`); - } - const result = await this.courseRepository.delete({ uuid }); - if (result.affected === 0) { - throw new BadRequestException('Failed to delete course'); - } + const result = await this.courseRepository.delete({ uuid }); + if (result.affected === 0) { + throw new BadRequestException('Failed to delete course'); } + } } diff --git a/src/courses/interfaces/course.interface.ts b/src/courses/interfaces/course.interface.ts new file mode 100644 index 00000000..88dee8e6 --- /dev/null +++ b/src/courses/interfaces/course.interface.ts @@ -0,0 +1,13 @@ +import { CreateCourseDto } from '../dto/create-course.dto'; +import { UpdateCourseDto } from '../dto/update-course.dto'; +import { Course } from '../entities/course.entity'; + +export interface ICoursesService { + create(createCourseDto: CreateCourseDto): Promise; + findAll(): Promise; + getByUUID(uuid: string): Promise; + updateByUUID(uuid: string, updateCourseDto: UpdateCourseDto): Promise; + deleteByUUID(uuid: string): Promise; +} + +export const ICoursesServiceToken = Symbol('ICoursesService'); From 36758cf0939b9aa799f53335aa5907a21baf0079 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:49:03 +0200 Subject: [PATCH 07/20] refactor(86c9kt102): implement interface for discord-users module --- src/discord-users/discord-users.controller.ts | 74 +++++++++++++++---- src/discord-users/discord-users.module.ts | 12 ++- src/discord-users/discord-users.service.ts | 21 ++++-- .../interfaces/discord-user.interface.ts | 12 +++ 4 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 src/discord-users/interfaces/discord-user.interface.ts diff --git a/src/discord-users/discord-users.controller.ts b/src/discord-users/discord-users.controller.ts index 55098199..f2589aa6 100644 --- a/src/discord-users/discord-users.controller.ts +++ b/src/discord-users/discord-users.controller.ts @@ -1,19 +1,44 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { DiscordUsersService } from './discord-users.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateDiscordUserDto } from './dto/create-discord-user.dto'; import { UpdateDiscordUserDto } from './dto/update-discord-user.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; import { DiscordUser } from './entities/discord-user.entity'; +import { + IDiscordUsersServiceToken, + IDiscordUsersService, +} from './interfaces/discord-user.interface'; @ApiTags('discord-users') @ApiBearerAuth('JWT-auth') @Controller('discord-users') export class DiscordUsersController { - constructor(private readonly discordUsersService: DiscordUsersService) {} + constructor( + @Inject(IDiscordUsersServiceToken) + private readonly discordUsersService: IDiscordUsersService, + ) {} @Post() @ApiOperation({ summary: 'Créer un nouvel utilisateur Discord' }) - @ApiResponse({ status: 201, description: 'L\'utilisateur a été créé avec succès.', type: DiscordUser }) + @ApiResponse({ + status: 201, + description: "L'utilisateur a été créé avec succès.", + type: DiscordUser, + }) @ApiResponse({ status: 400, description: 'Requête invalide' }) create(@Body() createDiscordUserDto: CreateDiscordUserDto) { return this.discordUsersService.create(createDiscordUserDto); @@ -21,14 +46,22 @@ export class DiscordUsersController { @Get() @ApiOperation({ summary: 'Récupérer tous les utilisateurs Discord' }) - @ApiResponse({ status: 200, description: 'Liste des utilisateurs récupérée avec succès.', type: [DiscordUser] }) + @ApiResponse({ + status: 200, + description: 'Liste des utilisateurs récupérée avec succès.', + type: [DiscordUser], + }) findAll() { return this.discordUsersService.findAll(); } @Get(':uuidDiscord') @ApiOperation({ summary: 'Récupérer un utilisateur Discord par son UUID' }) - @ApiResponse({ status: 200, description: 'L\'utilisateur a été trouvé.', type: DiscordUser }) + @ApiResponse({ + status: 200, + description: "L'utilisateur a été trouvé.", + type: DiscordUser, + }) @ApiResponse({ status: 404, description: 'Utilisateur non trouvé' }) findOne(@Param('uuidDiscord') uuidDiscord: string) { return this.discordUsersService.findOne(uuidDiscord); @@ -36,21 +69,36 @@ export class DiscordUsersController { @Put(':uuidDiscord') @ApiOperation({ summary: 'Mettre à jour un utilisateur Discord' }) - @ApiResponse({ status: 200, description: 'L\'utilisateur a été mis à jour avec succès.', type: DiscordUser }) + @ApiResponse({ + status: 200, + description: "L'utilisateur a été mis à jour avec succès.", + type: DiscordUser, + }) @ApiResponse({ status: 404, description: 'Utilisateur non trouvé' }) - async update(@Param('uuidDiscord') uuidDiscord: string, @Body() updateDiscordUserDto: UpdateDiscordUserDto) { - const discordUser = await this.discordUsersService.update(uuidDiscord, updateDiscordUserDto); + async update( + @Param('uuidDiscord') uuidDiscord: string, + @Body() updateDiscordUserDto: UpdateDiscordUserDto, + ) { + const discordUser = await this.discordUsersService.update( + uuidDiscord, + updateDiscordUserDto, + ); if (!discordUser) { - throw new NotFoundException(`Discord user with UUID "${uuidDiscord}" not found`); + throw new NotFoundException( + `Discord user with UUID "${uuidDiscord}" not found`, + ); } return discordUser; } @Delete(':uuidDiscord') @ApiOperation({ summary: 'Supprimer un utilisateur Discord' }) - @ApiResponse({ status: 200, description: 'L\'utilisateur a été supprimé avec succès.' }) + @ApiResponse({ + status: 200, + description: "L'utilisateur a été supprimé avec succès.", + }) @ApiResponse({ status: 404, description: 'Utilisateur non trouvé' }) remove(@Param('uuidDiscord') uuidDiscord: string) { return this.discordUsersService.remove(uuidDiscord); } -} \ No newline at end of file +} diff --git a/src/discord-users/discord-users.module.ts b/src/discord-users/discord-users.module.ts index ec74ecdc..f5105167 100644 --- a/src/discord-users/discord-users.module.ts +++ b/src/discord-users/discord-users.module.ts @@ -3,11 +3,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { DiscordUsersService } from './discord-users.service'; import { DiscordUsersController } from './discord-users.controller'; import { DiscordUser } from './entities/discord-user.entity'; +import { IDiscordUsersServiceToken } from './interfaces/discord-user.interface'; @Module({ imports: [TypeOrmModule.forFeature([DiscordUser])], controllers: [DiscordUsersController], - providers: [DiscordUsersService], - exports: [DiscordUsersService] + providers: [ + { + provide: IDiscordUsersServiceToken, + useClass: DiscordUsersService, + }, + ], + exports: [IDiscordUsersServiceToken], }) -export class DiscordUsersModule {} \ No newline at end of file +export class DiscordUsersModule {} diff --git a/src/discord-users/discord-users.service.ts b/src/discord-users/discord-users.service.ts index 53a6597d..6d276d7a 100644 --- a/src/discord-users/discord-users.service.ts +++ b/src/discord-users/discord-users.service.ts @@ -4,9 +4,10 @@ import { Repository } from 'typeorm'; import { CreateDiscordUserDto } from './dto/create-discord-user.dto'; import { UpdateDiscordUserDto } from './dto/update-discord-user.dto'; import { DiscordUser } from './entities/discord-user.entity'; +import { IDiscordUsersService } from './interfaces/discord-user.interface'; @Injectable() -export class DiscordUsersService { +export class DiscordUsersService implements IDiscordUsersService { constructor( @InjectRepository(DiscordUser) private discordUserRepository: Repository, @@ -25,16 +26,22 @@ export class DiscordUsersService { return this.discordUserRepository.findOneBy({ uuidDiscord }); } - async update(uuidDiscord: string, updateDiscordUserDto: UpdateDiscordUserDto) { - const discordUser = await this.discordUserRepository.findOneBy({ uuidDiscord }); + async update( + uuidDiscord: string, + updateDiscordUserDto: UpdateDiscordUserDto, + ) { + const discordUser = await this.discordUserRepository.findOneBy({ + uuidDiscord, + }); if (!discordUser) { return null; } - + const { discordUsername, discriminator } = updateDiscordUserDto; - if (discordUsername !== undefined) discordUser.discordUsername = discordUsername; + if (discordUsername !== undefined) + discordUser.discordUsername = discordUsername; if (discriminator !== undefined) discordUser.discriminator = discriminator; - + discordUser.updatedAt = new Date(); return this.discordUserRepository.save(discordUser); } @@ -42,4 +49,4 @@ export class DiscordUsersService { remove(uuidDiscord: string) { return this.discordUserRepository.delete({ uuidDiscord }); } -} \ No newline at end of file +} diff --git a/src/discord-users/interfaces/discord-user.interface.ts b/src/discord-users/interfaces/discord-user.interface.ts new file mode 100644 index 00000000..0766f741 --- /dev/null +++ b/src/discord-users/interfaces/discord-user.interface.ts @@ -0,0 +1,12 @@ +import { UpdateDiscordUserDto } from '../dto/update-discord-user.dto'; +import { CreateDiscordUserDto } from '../dto/create-discord-user.dto'; + +export interface IDiscordUsersService { + create(createDiscordUserDto: CreateDiscordUserDto); + findAll(); + findOne(uuid: string); + update(uuid: string, updateDiscordUserDto: UpdateDiscordUserDto); + remove(uuid: string); +} + +export const IDiscordUsersServiceToken = Symbol('IDiscordUsersService'); From 77b8ecbf71b4279d9320b8105086a8407e7fbaba Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:51:18 +0200 Subject: [PATCH 08/20] refactor(86c9kt13e): implement interface for guilds module --- src/guilds/guilds.controller.ts | 62 ++++++++++++++++++++---- src/guilds/guilds.module.ts | 10 +++- src/guilds/guilds.service.ts | 31 +++++++++--- src/guilds/interfaces/guild.interface.ts | 12 +++++ 4 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 src/guilds/interfaces/guild.interface.ts diff --git a/src/guilds/guilds.controller.ts b/src/guilds/guilds.controller.ts index faf07938..9c1d7240 100644 --- a/src/guilds/guilds.controller.ts +++ b/src/guilds/guilds.controller.ts @@ -1,19 +1,43 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { GuildsService } from './guilds.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateGuildDto } from './dto/create-guild.dto'; import { UpdateGuildDto } from './dto/update-guild.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; import { Guild } from './entities/guild.entity'; +import { + IGuildsServiceToken, + IGuildsService, +} from './interfaces/guild.interface'; @ApiTags('guilds') @ApiBearerAuth('JWT-auth') @Controller('guilds') export class GuildsController { - constructor(private readonly guildService: GuildsService) {} + constructor( + @Inject(IGuildsServiceToken) private readonly guildService: IGuildsService, + ) {} @Post() @ApiOperation({ summary: 'Créer un nouveau serveur Discord' }) - @ApiResponse({ status: 201, description: 'Le serveur a été créé avec succès.', type: Guild }) + @ApiResponse({ + status: 201, + description: 'Le serveur a été créé avec succès.', + type: Guild, + }) @ApiResponse({ status: 400, description: 'Requête invalide' }) create(@Body() createGuildDto: CreateGuildDto) { return this.guildService.create(createGuildDto); @@ -21,14 +45,22 @@ export class GuildsController { @Get() @ApiOperation({ summary: 'Récupérer tous les serveurs Discord' }) - @ApiResponse({ status: 200, description: 'Liste des serveurs récupérée avec succès.', type: [Guild] }) + @ApiResponse({ + status: 200, + description: 'Liste des serveurs récupérée avec succès.', + type: [Guild], + }) findAll() { return this.guildService.findAll(); } @Get(':uuid') @ApiOperation({ summary: 'Récupérer un serveur Discord par son UUID' }) - @ApiResponse({ status: 200, description: 'Le serveur a été trouvé.', type: Guild }) + @ApiResponse({ + status: 200, + description: 'Le serveur a été trouvé.', + type: Guild, + }) @ApiResponse({ status: 404, description: 'Serveur non trouvé' }) findOne(@Param('uuid') uuid: string) { return this.guildService.findOne(uuid); @@ -36,9 +68,16 @@ export class GuildsController { @Put(':uuid') @ApiOperation({ summary: 'Mettre à jour un serveur Discord' }) - @ApiResponse({ status: 200, description: 'Le serveur a été mis à jour avec succès.', type: Guild }) + @ApiResponse({ + status: 200, + description: 'Le serveur a été mis à jour avec succès.', + type: Guild, + }) @ApiResponse({ status: 404, description: 'Serveur non trouvé' }) - async update(@Param('uuid') uuid: string, @Body() updateGuildDto: UpdateGuildDto) { + async update( + @Param('uuid') uuid: string, + @Body() updateGuildDto: UpdateGuildDto, + ) { const guild = await this.guildService.update(uuid, updateGuildDto); if (!guild) { throw new NotFoundException(`Guild with UUID "${uuid}" not found`); @@ -48,7 +87,10 @@ export class GuildsController { @Delete(':uuid') @ApiOperation({ summary: 'Supprimer un serveur Discord' }) - @ApiResponse({ status: 200, description: 'Le serveur a été supprimé avec succès.' }) + @ApiResponse({ + status: 200, + description: 'Le serveur a été supprimé avec succès.', + }) @ApiResponse({ status: 404, description: 'Serveur non trouvé' }) remove(@Param('uuid') uuid: string) { return this.guildService.remove(uuid); diff --git a/src/guilds/guilds.module.ts b/src/guilds/guilds.module.ts index ec113ad1..bbd8e5b9 100644 --- a/src/guilds/guilds.module.ts +++ b/src/guilds/guilds.module.ts @@ -3,11 +3,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { GuildsService } from './guilds.service'; import { GuildsController } from './guilds.controller'; import { Guild } from './entities/guild.entity'; +import { IGuildsServiceToken } from './interfaces/guild.interface'; @Module({ imports: [TypeOrmModule.forFeature([Guild])], controllers: [GuildsController], - providers: [GuildsService], - exports: [GuildsService] + providers: [ + { + provide: IGuildsServiceToken, + useClass: GuildsService, + }, + ], + exports: [IGuildsServiceToken], }) export class GuildsModule {} diff --git a/src/guilds/guilds.service.ts b/src/guilds/guilds.service.ts index b43e6997..0e6c2aee 100644 --- a/src/guilds/guilds.service.ts +++ b/src/guilds/guilds.service.ts @@ -4,9 +4,10 @@ import { Repository } from 'typeorm'; import { CreateGuildDto } from './dto/create-guild.dto'; import { UpdateGuildDto } from './dto/update-guild.dto'; import { Guild } from './entities/guild.entity'; +import { IGuildsService } from './interfaces/guild.interface'; @Injectable() -export class GuildsService { +export class GuildsService implements IGuildsService { constructor( @InjectRepository(Guild) private readonly guildRepository: Repository, @@ -21,7 +22,16 @@ export class GuildsService { // Récupérer toutes les guilds async findAll(): Promise { return await this.guildRepository.find({ - relations: ['courses', 'members', 'roles', 'channels', 'categories', 'campuses', 'promotions', 'template'] + relations: [ + 'courses', + 'members', + 'roles', + 'channels', + 'categories', + 'campuses', + 'promotions', + 'template', + ], }); } @@ -29,7 +39,16 @@ export class GuildsService { async findOne(uuid: string): Promise { const guild = await this.guildRepository.findOne({ where: { uuid }, - relations: ['courses', 'members', 'roles', 'channels', 'categories', 'campuses', 'promotions', 'template'] + relations: [ + 'courses', + 'members', + 'roles', + 'channels', + 'categories', + 'campuses', + 'promotions', + 'template', + ], }); if (!guild) { @@ -42,17 +61,17 @@ export class GuildsService { // Mettre à jour une guild async update(uuid: string, updateGuildDto: UpdateGuildDto): Promise { const guild = await this.findOne(uuid); - + // Mise à jour des propriétés simples Object.assign(guild, updateGuildDto); - + return await this.guildRepository.save(guild); } // Supprimer une guild async remove(uuid: string): Promise { const result = await this.guildRepository.delete({ uuid }); - + if (result.affected === 0) { throw new NotFoundException(`Guild with UUID "${uuid}" not found`); } diff --git a/src/guilds/interfaces/guild.interface.ts b/src/guilds/interfaces/guild.interface.ts new file mode 100644 index 00000000..cc9b2bc3 --- /dev/null +++ b/src/guilds/interfaces/guild.interface.ts @@ -0,0 +1,12 @@ +import { CreateGuildDto } from '../dto/create-guild.dto'; +import { UpdateGuildDto } from '../dto/update-guild.dto'; + +export interface IGuildsService { + create(createGuildDto: CreateGuildDto); + findAll(); + findOne(uuid: string); + update(uuid: string, updateGuildDto: UpdateGuildDto); + remove(uuid: string); +} + +export const IGuildsServiceToken = Symbol('IGuildsService'); From 1858b337a5bd646b5e56a30c3018f7d4ec82315b Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:53:24 +0200 Subject: [PATCH 09/20] refactor(86c9kt17t): implement interface for guilds-templates module --- .../guilds-templates.controller.ts | 72 +++++++++++++++---- .../guilds-templates.module.ts | 9 ++- .../guilds-templates.service.ts | 25 ++++--- .../interfaces/guild-template.interface.ts | 12 ++++ 4 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 src/guilds-templates/interfaces/guild-template.interface.ts diff --git a/src/guilds-templates/guilds-templates.controller.ts b/src/guilds-templates/guilds-templates.controller.ts index 8787cdf4..ab526b77 100644 --- a/src/guilds-templates/guilds-templates.controller.ts +++ b/src/guilds-templates/guilds-templates.controller.ts @@ -1,19 +1,44 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { GuildsTemplatesService } from './guilds-templates.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateGuildTemplateDto } from './dto/create-guild-template.dto'; import { UpdateGuildTemplateDto } from './dto/update-guild-template.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; import { GuildTemplate } from './entities/guild-template.entity'; +import { + IGuildsTemplatesService, + IGuildsTemplatesServiceToken, +} from './interfaces/guild-template.interface'; @ApiTags('guilds-templates') @ApiBearerAuth('JWT-auth') @Controller('guilds-templates') export class GuildsTemplatesController { - constructor(private readonly guildsTemplatesService: GuildsTemplatesService) {} + constructor( + @Inject(IGuildsTemplatesServiceToken) + private readonly guildsTemplatesService: IGuildsTemplatesService, + ) {} @Post() @ApiOperation({ summary: 'Créer un nouveau template de serveur' }) - @ApiResponse({ status: 201, description: 'Le template a été créé avec succès.', type: GuildTemplate }) + @ApiResponse({ + status: 201, + description: 'Le template a été créé avec succès.', + type: GuildTemplate, + }) @ApiResponse({ status: 400, description: 'Requête invalide' }) create(@Body() createGuildTemplateDto: CreateGuildTemplateDto) { return this.guildsTemplatesService.create(createGuildTemplateDto); @@ -21,14 +46,22 @@ export class GuildsTemplatesController { @Get() @ApiOperation({ summary: 'Récupérer tous les templates de serveur' }) - @ApiResponse({ status: 200, description: 'Liste des templates récupérée avec succès.', type: [GuildTemplate] }) + @ApiResponse({ + status: 200, + description: 'Liste des templates récupérée avec succès.', + type: [GuildTemplate], + }) findAll() { return this.guildsTemplatesService.findAll(); } @Get(':uuid') @ApiOperation({ summary: 'Récupérer un template de serveur par son UUID' }) - @ApiResponse({ status: 200, description: 'Le template a été trouvé.', type: GuildTemplate }) + @ApiResponse({ + status: 200, + description: 'Le template a été trouvé.', + type: GuildTemplate, + }) @ApiResponse({ status: 404, description: 'Template non trouvé' }) findOne(@Param('uuid') uuid: string) { return this.guildsTemplatesService.findOne(uuid); @@ -36,19 +69,34 @@ export class GuildsTemplatesController { @Put(':uuid') @ApiOperation({ summary: 'Mettre à jour un template de serveur' }) - @ApiResponse({ status: 200, description: 'Le template a été mis à jour avec succès.', type: GuildTemplate }) + @ApiResponse({ + status: 200, + description: 'Le template a été mis à jour avec succès.', + type: GuildTemplate, + }) @ApiResponse({ status: 404, description: 'Template non trouvé' }) - async update(@Param('uuid') uuid: string, @Body() updateGuildTemplateDto: UpdateGuildTemplateDto) { - const guildTemplate = await this.guildsTemplatesService.update(uuid, updateGuildTemplateDto); + async update( + @Param('uuid') uuid: string, + @Body() updateGuildTemplateDto: UpdateGuildTemplateDto, + ) { + const guildTemplate = await this.guildsTemplatesService.update( + uuid, + updateGuildTemplateDto, + ); if (!guildTemplate) { - throw new NotFoundException(`Guild template with UUID "${uuid}" not found`); + throw new NotFoundException( + `Guild template with UUID "${uuid}" not found`, + ); } return guildTemplate; } @Delete(':uuid') @ApiOperation({ summary: 'Supprimer un template de serveur' }) - @ApiResponse({ status: 200, description: 'Le template a été supprimé avec succès.' }) + @ApiResponse({ + status: 200, + description: 'Le template a été supprimé avec succès.', + }) @ApiResponse({ status: 404, description: 'Template non trouvé' }) remove(@Param('uuid') uuid: string) { return this.guildsTemplatesService.remove(uuid); diff --git a/src/guilds-templates/guilds-templates.module.ts b/src/guilds-templates/guilds-templates.module.ts index 699af61e..d56d1d12 100644 --- a/src/guilds-templates/guilds-templates.module.ts +++ b/src/guilds-templates/guilds-templates.module.ts @@ -3,10 +3,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { GuildsTemplatesService } from './guilds-templates.service'; import { GuildsTemplatesController } from './guilds-templates.controller'; import { GuildTemplate } from './entities/guild-template.entity'; +import { IGuildsTemplatesServiceToken } from './interfaces/guild-template.interface'; @Module({ imports: [TypeOrmModule.forFeature([GuildTemplate])], controllers: [GuildsTemplatesController], - providers: [GuildsTemplatesService], + providers: [ + { + provide: IGuildsTemplatesServiceToken, + useClass: GuildsTemplatesService, + }, + ], + exports: [IGuildsTemplatesServiceToken], }) export class GuildsTemplatesModule {} diff --git a/src/guilds-templates/guilds-templates.service.ts b/src/guilds-templates/guilds-templates.service.ts index efa5ba79..f2bb9b56 100644 --- a/src/guilds-templates/guilds-templates.service.ts +++ b/src/guilds-templates/guilds-templates.service.ts @@ -4,45 +4,52 @@ import { Repository } from 'typeorm'; import { CreateGuildTemplateDto } from './dto/create-guild-template.dto'; import { UpdateGuildTemplateDto } from './dto/update-guild-template.dto'; import { GuildTemplate } from './entities/guild-template.entity'; +import { IGuildsTemplatesService } from './interfaces/guild-template.interface'; @Injectable() -export class GuildsTemplatesService { +export class GuildsTemplatesService implements IGuildsTemplatesService { constructor( @InjectRepository(GuildTemplate) private guildTemplateRepository: Repository, ) {} create(createGuildTemplateDto: CreateGuildTemplateDto) { - const guildTemplate = this.guildTemplateRepository.create(createGuildTemplateDto); + const guildTemplate = this.guildTemplateRepository.create( + createGuildTemplateDto, + ); return this.guildTemplateRepository.save(guildTemplate); } findAll() { return this.guildTemplateRepository.find({ - relations: ['guild', 'category'] + relations: ['guild', 'category'], }); } findOne(uuid: string) { return this.guildTemplateRepository.findOne({ where: { uuid }, - relations: ['guild', 'category'] + relations: ['guild', 'category'], }); } async update(uuid: string, updateGuildTemplateDto: UpdateGuildTemplateDto) { - const guildTemplate = await this.guildTemplateRepository.findOneBy({ uuid }); + const guildTemplate = await this.guildTemplateRepository.findOneBy({ + uuid, + }); if (!guildTemplate) { return null; } - + // Mise à jour des champs autorisés uniquement - const { name, description, configuration, uuidCategory } = updateGuildTemplateDto; + const { name, description, configuration, uuidCategory } = + updateGuildTemplateDto; if (name !== undefined) guildTemplate.name = name; if (description !== undefined) guildTemplate.description = description; - if (configuration !== undefined) guildTemplate.configuration = configuration; + if (configuration !== undefined) + guildTemplate.configuration = configuration; if (uuidCategory !== undefined) guildTemplate.uuidCategory = uuidCategory; - + guildTemplate.updatedAt = new Date(); return this.guildTemplateRepository.save(guildTemplate); } diff --git a/src/guilds-templates/interfaces/guild-template.interface.ts b/src/guilds-templates/interfaces/guild-template.interface.ts new file mode 100644 index 00000000..84ebda38 --- /dev/null +++ b/src/guilds-templates/interfaces/guild-template.interface.ts @@ -0,0 +1,12 @@ +import { CreateGuildTemplateDto } from '../dto/create-guild-template.dto'; +import { UpdateGuildTemplateDto } from '../dto/update-guild-template.dto'; + +export interface IGuildsTemplatesService { + create(createGuildTemplateDto: CreateGuildTemplateDto); + findAll(); + findOne(uuid: string); + update(uuid: string, updateGuildTemplateDto: UpdateGuildTemplateDto); + remove(uuid: string); +} + +export const IGuildsTemplatesServiceToken = Symbol('IGuildsTemplatesService'); From 3fc308d36d4a4aad09896e34d5870abde4860921 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 20:55:25 +0200 Subject: [PATCH 10/20] refactor(86c9kt114): implement interface for identification-requests module --- .../identification-requests.controller.ts | 45 +++++++++++++++---- .../identification-requests.module.ts | 9 +++- .../identification-requests.service.ts | 30 ++++++++----- .../identification-request.interface.ts | 17 +++++++ 4 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 src/identification-requests/interfaces/identification-request.interface.ts diff --git a/src/identification-requests/identification-requests.controller.ts b/src/identification-requests/identification-requests.controller.ts index 3cdc81f8..3f84baa1 100644 --- a/src/identification-requests/identification-requests.controller.ts +++ b/src/identification-requests/identification-requests.controller.ts @@ -1,15 +1,35 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { IdentificationRequestsService } from './identification-requests.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateIdentificationRequestDto } from './dto/create-identification-request.dto'; import { UpdateIdentificationRequestDto } from './dto/update-identification-request.dto'; +import { + IIdentificationRequestsServiceToken, + IIdentificationRequestsService, +} from './interfaces/identification-request.interface'; @Controller('identification-requests') export class IdentificationRequestsController { - constructor(private readonly identificationRequestsService: IdentificationRequestsService) {} + constructor( + @Inject(IIdentificationRequestsServiceToken) + private readonly identificationRequestsService: IIdentificationRequestsService, + ) {} @Post() - create(@Body() createIdentificationRequestDto: CreateIdentificationRequestDto) { - return this.identificationRequestsService.create(createIdentificationRequestDto); + create( + @Body() createIdentificationRequestDto: CreateIdentificationRequestDto, + ) { + return this.identificationRequestsService.create( + createIdentificationRequestDto, + ); } @Get() @@ -23,10 +43,19 @@ export class IdentificationRequestsController { } @Put(':uuid') - async update(@Param('uuid') uuid: string, @Body() updateIdentificationRequestDto: UpdateIdentificationRequestDto) { - const identificationRequest = await this.identificationRequestsService.update(uuid, updateIdentificationRequestDto); + async update( + @Param('uuid') uuid: string, + @Body() updateIdentificationRequestDto: UpdateIdentificationRequestDto, + ) { + const identificationRequest = + await this.identificationRequestsService.update( + uuid, + updateIdentificationRequestDto, + ); if (!identificationRequest) { - throw new NotFoundException(`IdentificationRequest with UUID "${uuid}" not found`); + throw new NotFoundException( + `IdentificationRequest with UUID "${uuid}" not found`, + ); } return identificationRequest; } diff --git a/src/identification-requests/identification-requests.module.ts b/src/identification-requests/identification-requests.module.ts index fe88115f..37117369 100644 --- a/src/identification-requests/identification-requests.module.ts +++ b/src/identification-requests/identification-requests.module.ts @@ -4,10 +4,17 @@ import { IdentificationRequestsController } from './identification-requests.cont import { TypeOrmModule } from '@nestjs/typeorm'; import { IdentificationRequest } from './entities/identification-request.entity'; +import { IIdentificationRequestsServiceToken } from './interfaces/identification-request.interface'; @Module({ imports: [TypeOrmModule.forFeature([IdentificationRequest])], controllers: [IdentificationRequestsController], - providers: [IdentificationRequestsService], + providers: [ + { + provide: IIdentificationRequestsServiceToken, + useClass: IdentificationRequestsService, + }, + ], + exports: [IIdentificationRequestsServiceToken], }) export class IdentificationRequestsModule {} diff --git a/src/identification-requests/identification-requests.service.ts b/src/identification-requests/identification-requests.service.ts index 4146e14d..1faeb178 100644 --- a/src/identification-requests/identification-requests.service.ts +++ b/src/identification-requests/identification-requests.service.ts @@ -5,16 +5,20 @@ import { UpdateIdentificationRequestDto } from './dto/update-identification-requ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { IdentificationRequest } from './entities/identification-request.entity'; +import { IIdentificationRequestsService } from './interfaces/identification-request.interface'; @Injectable() -export class IdentificationRequestsService { - +export class IdentificationRequestsService + implements IIdentificationRequestsService +{ constructor( @InjectRepository(IdentificationRequest) private identificationRequestsRepository: Repository, ) {} create(createIdentificationRequestDto: CreateIdentificationRequestDto) { - const identificationRequest = this.identificationRequestsRepository.create(createIdentificationRequestDto); + const identificationRequest = this.identificationRequestsRepository.create( + createIdentificationRequestDto, + ); return this.identificationRequestsRepository.save(identificationRequest); } @@ -26,22 +30,26 @@ export class IdentificationRequestsService { return this.identificationRequestsRepository.findOneBy({ uuid }); } - async update(uuid: string, updateIdentificationRequestDto: UpdateIdentificationRequestDto) { - const identificationRequest = await this.identificationRequestsRepository.findOneBy({ uuid }); - + async update( + uuid: string, + updateIdentificationRequestDto: UpdateIdentificationRequestDto, + ) { + const identificationRequest = + await this.identificationRequestsRepository.findOneBy({ uuid }); + if (!identificationRequest) { return null; } - + Object.assign(identificationRequest, updateIdentificationRequestDto); - + if (updateIdentificationRequestDto.uuidMember !== undefined) { - identificationRequest.uuidMember = updateIdentificationRequestDto.uuidMember; + identificationRequest.uuidMember = + updateIdentificationRequestDto.uuidMember; } - + return this.identificationRequestsRepository.save(identificationRequest); } - remove(uuid: string) { return this.identificationRequestsRepository.delete(uuid); diff --git a/src/identification-requests/interfaces/identification-request.interface.ts b/src/identification-requests/interfaces/identification-request.interface.ts new file mode 100644 index 00000000..27144d61 --- /dev/null +++ b/src/identification-requests/interfaces/identification-request.interface.ts @@ -0,0 +1,17 @@ +import { CreateIdentificationRequestDto } from '../dto/create-identification-request.dto'; +import { UpdateIdentificationRequestDto } from '../dto/update-identification-request.dto'; + +export interface IIdentificationRequestsService { + create(createIdentificationRequestDto: CreateIdentificationRequestDto); + findAll(); + findOne(uuid: string); + update( + uuid: string, + updateIdentificationRequestDto: UpdateIdentificationRequestDto, + ); + remove(uuid: string); +} + +export const IIdentificationRequestsServiceToken = Symbol( + 'IIdentificationRequestsService', +); From 8cb5add38ab49f87e6aad203e2ada73f6311b0b4 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 21:47:46 +0200 Subject: [PATCH 11/20] refactor(86c9kt12c): implement interface for members module --- src/channels/channels.controller.spec.ts | 2 +- src/members/interfaces/member.interface.ts | 16 +++++ src/members/members.controller.ts | 19 ++--- src/members/members.module.ts | 10 ++- src/members/members.service.ts | 81 ++++++++++++++-------- 5 files changed, 88 insertions(+), 40 deletions(-) create mode 100644 src/members/interfaces/member.interface.ts diff --git a/src/channels/channels.controller.spec.ts b/src/channels/channels.controller.spec.ts index 632bf0a2..dfdfc45b 100644 --- a/src/channels/channels.controller.spec.ts +++ b/src/channels/channels.controller.spec.ts @@ -29,4 +29,4 @@ describe('ChannelsController', () => { it('should be defined', () => { expect(controller).toBeDefined(); }); -}); \ No newline at end of file +}); diff --git a/src/members/interfaces/member.interface.ts b/src/members/interfaces/member.interface.ts new file mode 100644 index 00000000..79f37ba1 --- /dev/null +++ b/src/members/interfaces/member.interface.ts @@ -0,0 +1,16 @@ +import { CreateMemberDto } from '../dto/create-member.dto'; +import { UpdateMemberDto } from '../dto/update-member.dto'; + +export interface IMembersService { + create(createMemberDto: CreateMemberDto); + findAll(); + findOne(uuidMember: string); + findMemberPromotions(uuidMember: string); + update(uuidMember: string, updateMemberDto: UpdateMemberDto); + remove(uuidMember: string); + getMemberRoles(uuidMember: string); + assignRoleToMember(uuidMember: string, uuidRole: string); + removeRoleFromMember(uuidMember: string, uuidRole: string); +} + +export const IMembersServiceToken = Symbol('IMembersService'); diff --git a/src/members/members.controller.ts b/src/members/members.controller.ts index 6f6f2e48..f95d839b 100644 --- a/src/members/members.controller.ts +++ b/src/members/members.controller.ts @@ -4,33 +4,36 @@ import { Post, Body, Put, - Patch, Param, Delete, HttpStatus, + Inject, } from '@nestjs/common'; -import { MembersService } from './members.service'; import { CreateMemberDto } from './dto/create-member.dto'; import { UpdateMemberDto } from './dto/update-member.dto'; -import { AssignRoleToMemberDto } from '../roles/dto/assign-role-to-member.dto'; -import { UpdateMemberRolesDto } from '../roles/dto/update-member-roles.dto'; import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody, - ApiBearerAuth + ApiBearerAuth, } from '@nestjs/swagger'; import { Member } from './entities/member.entity'; import { Role } from '../roles/entities/role.entity'; -import { Promotion } from '../promotions/entities/promotion.entity'; +import { + IMembersServiceToken, + IMembersService, +} from './interfaces/member.interface'; @ApiTags('members') @ApiBearerAuth('JWT-auth') @Controller('members') export class MembersController { - constructor(private readonly membersService: MembersService) {} + constructor( + @Inject(IMembersServiceToken) + private readonly membersService: IMembersService, + ) {} @Post() @ApiOperation({ @@ -239,7 +242,7 @@ export class MembersController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: "Erreur lors de la récupération des promotions du membre.", + description: 'Erreur lors de la récupération des promotions du membre.', }) findMemberPromotions(@Param('uuid') uuid: string) { return this.membersService.findMemberPromotions(uuid); diff --git a/src/members/members.module.ts b/src/members/members.module.ts index d64f673c..a86e7d9e 100644 --- a/src/members/members.module.ts +++ b/src/members/members.module.ts @@ -4,11 +4,17 @@ import { MembersController } from './members.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Member } from './entities/member.entity'; import { RolesModule } from 'src/roles/roles.module'; +import { IMembersServiceToken } from './interfaces/member.interface'; @Module({ imports: [TypeOrmModule.forFeature([Member]), RolesModule], controllers: [MembersController], - providers: [MembersService], - exports: [MembersService] + providers: [ + { + provide: IMembersServiceToken, + useClass: MembersService, + }, + ], + exports: [IMembersServiceToken], }) export class MembersModule {} diff --git a/src/members/members.service.ts b/src/members/members.service.ts index fbd7030d..4903b3a9 100644 --- a/src/members/members.service.ts +++ b/src/members/members.service.ts @@ -1,19 +1,24 @@ -import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; +import { + Injectable, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository } from 'typeorm'; import { CreateMemberDto } from './dto/create-member.dto'; import { UpdateMemberDto } from './dto/update-member.dto'; import { Member } from './entities/member.entity'; +import { IMembersService } from './interfaces/member.interface'; import { Role } from '../roles/entities/role.entity'; @Injectable() -export class MembersService { +export class MembersService implements IMembersService { constructor( @InjectRepository(Member) private membersRepository: Repository, @InjectRepository(Role) - private readonly rolesRepository: Repository + private readonly rolesRepository: Repository, ) {} // Créer un nouveau membre @@ -25,7 +30,7 @@ export class MembersService { // Récupérer tous les membres async findAll(): Promise { return await this.membersRepository.find({ - relations: ['resources'] + relations: ['resources'], }); } @@ -34,7 +39,7 @@ export class MembersService { try { const member = await this.membersRepository.findOne({ where: { uuidMember }, - relations: ['resources'] + relations: ['resources'], }); if (!member) { @@ -45,16 +50,20 @@ export class MembersService { if (error instanceof NotFoundException) { throw error; } - throw new BadRequestException(`Erreur lors de la récupération du membre: ${error.message}`); + throw new BadRequestException( + `Erreur lors de la récupération du membre: ${error.message}`, + ); } } // Récupérer les promotions suivies et gérées par un membre - async findMemberPromotions(uuidMember: string): Promise<{ followedPromotions: any[], managedPromotions: any[] }> { + async findMemberPromotions( + uuidMember: string, + ): Promise<{ followedPromotions: any[]; managedPromotions: any[] }> { try { // Vérifier d'abord si le membre existe const member = await this.membersRepository.findOne({ - where: { uuidMember } + where: { uuidMember }, }); if (!member) { @@ -63,18 +72,23 @@ export class MembersService { return { followedPromotions: member.followedPromotions || [], - managedPromotions: member.managedPromotions || [] + managedPromotions: member.managedPromotions || [], }; } catch (error) { if (error instanceof NotFoundException) { throw error; } - throw new BadRequestException(`Erreur lors de la récupération des promotions du membre: ${error.message}`); + throw new BadRequestException( + `Erreur lors de la récupération des promotions du membre: ${error.message}`, + ); } } // Mettre à jour un membre - async update(uuidMember: string, updateMemberDto: UpdateMemberDto): Promise { + async update( + uuidMember: string, + updateMemberDto: UpdateMemberDto, + ): Promise { const member = await this.findOne(uuidMember); Object.assign(member, updateMemberDto); return await this.membersRepository.save(member); @@ -91,35 +105,38 @@ export class MembersService { async getMemberRoles(uuidMember: string): Promise { const member = await this.membersRepository.findOne({ - where: { uuidMember }, - relations: ['roles'], + where: { uuidMember }, + relations: ['roles'], }); if (!member) { - throw new NotFoundException(`Member with UUID ${uuidMember} not found`); + throw new NotFoundException(`Member with UUID ${uuidMember} not found`); } return member.roles; } - async assignRoleToMember(uuidMember: string, uuidRole: string): Promise { + async assignRoleToMember( + uuidMember: string, + uuidRole: string, + ): Promise { const member = await this.membersRepository.findOne({ - where: { uuidMember }, - relations: ['roles'], + where: { uuidMember }, + relations: ['roles'], }); if (!member) { - throw new NotFoundException(`Member with UUID ${uuidMember} not found`); + throw new NotFoundException(`Member with UUID ${uuidMember} not found`); } const role = await this.rolesRepository.findOne({ where: { uuidRole } }); if (!role) { - throw new NotFoundException(`Role with UUID ${uuidRole} not found`); + throw new NotFoundException(`Role with UUID ${uuidRole} not found`); } // Vérifier si le membre possède déjà ce rôle - if (member.roles.some(r => r.uuidRole === uuidRole)) { - throw new BadRequestException(`Member already has the role ${uuidRole}`); + if (member.roles.some((r) => r.uuidRole === uuidRole)) { + throw new BadRequestException(`Member already has the role ${uuidRole}`); } // Ajouter le rôle au membre @@ -132,28 +149,34 @@ export class MembersService { return await this.membersRepository.save(member); } - async removeRoleFromMember(uuidMember: string, uuidRole: string): Promise { + async removeRoleFromMember( + uuidMember: string, + uuidRole: string, + ): Promise { const member = await this.membersRepository.findOne({ - where: { uuidMember }, - relations: ['roles'], + where: { uuidMember }, + relations: ['roles'], }); if (!member) { - throw new NotFoundException(`Member with UUID ${uuidMember} not found`); + throw new NotFoundException(`Member with UUID ${uuidMember} not found`); } const role = await this.rolesRepository.findOne({ where: { uuidRole } }); if (!role) { - throw new NotFoundException(`Role with UUID ${uuidRole} not found`); + throw new NotFoundException(`Role with UUID ${uuidRole} not found`); } // Supprimer le rôle du membre - member.roles = member.roles.filter(r => r.uuidRole !== uuidRole); + member.roles = member.roles.filter((r) => r.uuidRole !== uuidRole); // Mettre à jour `member_count` - role.memberCount = Math.max(0, parseInt(role.memberCount.toString(), 10) - 1); + role.memberCount = Math.max( + 0, + parseInt(role.memberCount.toString(), 10) - 1, + ); await this.rolesRepository.save(role); return await this.membersRepository.save(member); } -} \ No newline at end of file +} From 9f06bbf5cb253daa0f6742438cce959fb1b671a7 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 21:49:22 +0200 Subject: [PATCH 12/20] refactor(86c9kt11v): implement interface for members-informations module --- .../member-information.interface.ts | 15 ++++ .../members-informations.controller.ts | 80 +++++++++++++++---- .../members-informations.module.ts | 9 ++- .../members-informations.service.ts | 16 ++-- 4 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 src/members-informations/interfaces/member-information.interface.ts diff --git a/src/members-informations/interfaces/member-information.interface.ts b/src/members-informations/interfaces/member-information.interface.ts new file mode 100644 index 00000000..8684faa6 --- /dev/null +++ b/src/members-informations/interfaces/member-information.interface.ts @@ -0,0 +1,15 @@ +import { CreateMemberInformationsDto } from '../dto/create-member-informations.dto'; +import { UpdateMemberInformationsDto } from '../dto/update-member-informations.dto'; +import { MemberInformation } from '../entities/member-information.entity'; + +export interface IMembersInformationsService { + create(createMemberInformationDto: CreateMemberInformationsDto); + findAll(); + findOne(uuid: string); + update(uuid: string, updateMemberInformationDto: UpdateMemberInformationsDto); + remove(uuid: string); +} + +export const IMembersInformationsServiceToken = Symbol( + 'IMembersInformationsService', +); diff --git a/src/members-informations/members-informations.controller.ts b/src/members-informations/members-informations.controller.ts index ec47b118..b95f878c 100644 --- a/src/members-informations/members-informations.controller.ts +++ b/src/members-informations/members-informations.controller.ts @@ -1,19 +1,44 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common'; -import { MembersInformationsService } from './members-informations.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + Inject, +} from '@nestjs/common'; import { CreateMemberInformationsDto } from './dto/create-member-informations.dto'; import { UpdateMemberInformationsDto } from './dto/update-member-informations.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; import { MemberInformation } from './entities/member-information.entity'; +import { + IMembersInformationsServiceToken, + IMembersInformationsService, +} from './interfaces/member-information.interface'; @ApiTags('members-informations') @ApiBearerAuth('JWT-auth') @Controller('members-informations') export class MembersInformationsController { - constructor(private readonly membersInformationsService: MembersInformationsService) {} + constructor( + @Inject(IMembersInformationsServiceToken) + private readonly membersInformationsService: IMembersInformationsService, + ) {} @Post() @ApiOperation({ summary: 'Créer des informations pour un membre' }) - @ApiResponse({ status: 201, description: 'Les informations ont été créées avec succès.', type: MemberInformation }) + @ApiResponse({ + status: 201, + description: 'Les informations ont été créées avec succès.', + type: MemberInformation, + }) @ApiResponse({ status: 400, description: 'Requête invalide' }) create(@Body() createMemberInformationDto: CreateMemberInformationsDto) { return this.membersInformationsService.create(createMemberInformationDto); @@ -21,34 +46,59 @@ export class MembersInformationsController { @Get() @ApiOperation({ summary: 'Récupérer les informations de tous les membres' }) - @ApiResponse({ status: 200, description: 'Liste des informations récupérée avec succès.', type: [MemberInformation] }) + @ApiResponse({ + status: 200, + description: 'Liste des informations récupérée avec succès.', + type: [MemberInformation], + }) findAll() { return this.membersInformationsService.findAll(); } @Get(':uuid') - @ApiOperation({ summary: 'Récupérer les informations d\'un membre par son UUID' }) - @ApiResponse({ status: 200, description: 'Les informations ont été trouvées.', type: MemberInformation }) + @ApiOperation({ + summary: "Récupérer les informations d'un membre par son UUID", + }) + @ApiResponse({ + status: 200, + description: 'Les informations ont été trouvées.', + type: MemberInformation, + }) @ApiResponse({ status: 404, description: 'Informations non trouvées' }) findOne(@Param('uuid') uuid: string) { return this.membersInformationsService.findOne(uuid); } @Put(':uuid') - @ApiOperation({ summary: 'Mettre à jour les informations d\'un membre' }) - @ApiResponse({ status: 200, description: 'Les informations ont été mises à jour avec succès.', type: MemberInformation }) + @ApiOperation({ summary: "Mettre à jour les informations d'un membre" }) + @ApiResponse({ + status: 200, + description: 'Les informations ont été mises à jour avec succès.', + type: MemberInformation, + }) @ApiResponse({ status: 404, description: 'Informations non trouvées' }) - async update(@Param('uuid') uuid: string, @Body() updateMemberInformationsDto: UpdateMemberInformationsDto) { - const memberInformations = await this.membersInformationsService.update(uuid, updateMemberInformationsDto); + async update( + @Param('uuid') uuid: string, + @Body() updateMemberInformationsDto: UpdateMemberInformationsDto, + ) { + const memberInformations = await this.membersInformationsService.update( + uuid, + updateMemberInformationsDto, + ); if (!memberInformations) { - throw new NotFoundException(`MemberInformations with UUID "${uuid}" not found`); + throw new NotFoundException( + `MemberInformations with UUID "${uuid}" not found`, + ); } return memberInformations; } @Delete(':uuid') - @ApiOperation({ summary: 'Supprimer les informations d\'un membre' }) - @ApiResponse({ status: 200, description: 'Les informations ont été supprimées avec succès.' }) + @ApiOperation({ summary: "Supprimer les informations d'un membre" }) + @ApiResponse({ + status: 200, + description: 'Les informations ont été supprimées avec succès.', + }) @ApiResponse({ status: 404, description: 'Informations non trouvées' }) remove(@Param('uuid') uuid: string) { return this.membersInformationsService.remove(uuid); diff --git a/src/members-informations/members-informations.module.ts b/src/members-informations/members-informations.module.ts index 50ee96a9..0b792722 100644 --- a/src/members-informations/members-informations.module.ts +++ b/src/members-informations/members-informations.module.ts @@ -3,10 +3,17 @@ import { MemberInformation } from './entities/member-information.entity'; import { MembersInformationsService } from './members-informations.service'; import { MembersInformationsController } from './members-informations.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { IMembersInformationsServiceToken } from './interfaces/member-information.interface'; @Module({ imports: [TypeOrmModule.forFeature([MemberInformation])], controllers: [MembersInformationsController], - providers: [MembersInformationsService] + providers: [ + { + provide: IMembersInformationsServiceToken, + useClass: MembersInformationsService, + }, + ], + exports: [IMembersInformationsServiceToken], }) export class MembersInformationsModule {} diff --git a/src/members-informations/members-informations.service.ts b/src/members-informations/members-informations.service.ts index a67726c9..f84db11d 100644 --- a/src/members-informations/members-informations.service.ts +++ b/src/members-informations/members-informations.service.ts @@ -4,9 +4,10 @@ import { UpdateMemberInformationsDto } from './dto/update-member-informations.dt import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { MemberInformation } from './entities/member-information.entity'; +import { IMembersInformationsService } from './interfaces/member-information.interface'; @Injectable() -export class MembersInformationsService { +export class MembersInformationsService implements IMembersInformationsService { constructor( @InjectRepository(MemberInformation) private memberInformationsRepository: Repository, @@ -24,18 +25,23 @@ export class MembersInformationsService { return this.memberInformationsRepository.findOneBy({ uuid }); } - async update(uuid: string, updateMemberInformationDto: UpdateMemberInformationsDto) { - const memberInfo = await this.memberInformationsRepository.findOneBy({ uuid }); + async update( + uuid: string, + updateMemberInformationDto: UpdateMemberInformationsDto, + ) { + const memberInfo = await this.memberInformationsRepository.findOneBy({ + uuid, + }); if (!memberInfo) { return null; } - + // Mise à jour des champs autorisés uniquement const { firstName, lastName, email } = updateMemberInformationDto; if (firstName !== undefined) memberInfo.firstName = firstName; if (lastName !== undefined) memberInfo.lastName = lastName; if (email !== undefined) memberInfo.email = email; - + memberInfo.updatedAt = new Date(); return this.memberInformationsRepository.save(memberInfo); } From 55dff68a54d28bc961b88addcea9bdf5fcd75b49 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 21:51:01 +0200 Subject: [PATCH 13/20] refactor(86c9kt14n): implement interface for promotions module --- .../interfaces/promotion.interface.ts | 15 +++ src/promotions/promotions.controller.ts | 120 ++++++++++++++---- src/promotions/promotions.module.ts | 17 ++- src/promotions/promotions.service.ts | 88 ++++++++++--- 4 files changed, 184 insertions(+), 56 deletions(-) create mode 100644 src/promotions/interfaces/promotion.interface.ts diff --git a/src/promotions/interfaces/promotion.interface.ts b/src/promotions/interfaces/promotion.interface.ts new file mode 100644 index 00000000..4d622380 --- /dev/null +++ b/src/promotions/interfaces/promotion.interface.ts @@ -0,0 +1,15 @@ +import { CreatePromotionDto } from '../dto/create-promotion.dto'; +import { UpdatePromotionDto } from '../dto/update-promotion.dto'; +import { Promotion } from '../entities/promotion.entity'; + +export interface IPromotionsService { + create(createPromotionDto: CreatePromotionDto); + findAll(); + findOne(uuid: string); + update(uuid: string, updatePromotionDto: UpdatePromotionDto); + remove(uuid: string); + addFollower(uuidPromotion: string, uuidMember: string); + addManager(uuidPromotion: string, uuidMember: string); +} + +export const IPromotionsServiceToken = Symbol('IPromotionsService'); diff --git a/src/promotions/promotions.controller.ts b/src/promotions/promotions.controller.ts index e4b18a83..98fa414c 100644 --- a/src/promotions/promotions.controller.ts +++ b/src/promotions/promotions.controller.ts @@ -1,32 +1,55 @@ -import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException, HttpStatus } from '@nestjs/common'; -import { PromotionsService } from './promotions.service'; +import { + Controller, + Get, + Post, + Body, + Put, + Param, + Delete, + NotFoundException, + HttpStatus, + Inject, +} from '@nestjs/common'; import { CreatePromotionDto } from './dto/create-promotion.dto'; import { UpdatePromotionDto } from './dto/update-promotion.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiParam, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBody, + ApiParam, + ApiBearerAuth, +} from '@nestjs/swagger'; import { Promotion } from './entities/promotion.entity'; +import { + IPromotionsServiceToken, + IPromotionsService, +} from './interfaces/promotion.interface'; @ApiTags('promotions') @ApiBearerAuth('JWT-auth') @Controller('promotions') export class PromotionsController { constructor( - private readonly promotionsService: PromotionsService - ) {} + @Inject(IPromotionsServiceToken) + private readonly promotionsService: IPromotionsService, + ) {} @Post() - @ApiOperation({ + @ApiOperation({ summary: 'Créer une promotion', - description: 'Crée une promotion avec un rôle associé et l\'enregistre en base de données.' + description: + "Crée une promotion avec un rôle associé et l'enregistre en base de données.", }) @ApiBody({ type: CreatePromotionDto }) - @ApiResponse({ - status: HttpStatus.CREATED, + @ApiResponse({ + status: HttpStatus.CREATED, description: 'La promotion a été créée avec succès.', - type: Promotion + type: Promotion, }) - @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: 'Données invalides fournies dans la requête.' + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Données invalides fournies dans la requête.', }) create(@Body() createPromotionDto: CreatePromotionDto) { return this.promotionsService.create(createPromotionDto); @@ -34,14 +57,22 @@ export class PromotionsController { @Get() @ApiOperation({ summary: 'Récupérer toutes les promotions' }) - @ApiResponse({ status: 200, description: 'Liste des promotions récupérée avec succès.', type: [Promotion] }) + @ApiResponse({ + status: 200, + description: 'Liste des promotions récupérée avec succès.', + type: [Promotion], + }) findAll() { return this.promotionsService.findAll(); } @Get(':uuid') @ApiOperation({ summary: 'Récupérer une promotion par son UUID' }) - @ApiResponse({ status: 200, description: 'Promotion récupérée avec succès.', type: Promotion }) + @ApiResponse({ + status: 200, + description: 'Promotion récupérée avec succès.', + type: Promotion, + }) @ApiResponse({ status: 404, description: 'Promotion non trouvée' }) findOne(@Param('uuid') uuid: string) { return this.promotionsService.findOne(uuid); @@ -49,10 +80,20 @@ export class PromotionsController { @Put(':uuid') @ApiOperation({ summary: 'Mettre à jour une promotion' }) - @ApiResponse({ status: 200, description: 'La promotion a été mise à jour avec succès.', type: Promotion }) + @ApiResponse({ + status: 200, + description: 'La promotion a été mise à jour avec succès.', + type: Promotion, + }) @ApiResponse({ status: 404, description: 'Promotion non trouvée' }) - async update(@Param('uuid') uuid: string, @Body() updatePromotionDto: UpdatePromotionDto) { - const promotion = await this.promotionsService.update(uuid, updatePromotionDto); + async update( + @Param('uuid') uuid: string, + @Body() updatePromotionDto: UpdatePromotionDto, + ) { + const promotion = await this.promotionsService.update( + uuid, + updatePromotionDto, + ); if (!promotion) { throw new NotFoundException(`Promotion with UUID "${uuid}" not found`); } @@ -61,19 +102,32 @@ export class PromotionsController { @Delete(':uuid') @ApiOperation({ summary: 'Supprimer une promotion' }) - @ApiResponse({ status: 200, description: 'La promotion a été supprimée avec succès.' }) + @ApiResponse({ + status: 200, + description: 'La promotion a été supprimée avec succès.', + }) @ApiResponse({ status: 404, description: 'Promotion non trouvée' }) remove(@Param('uuid') uuid: string) { return this.promotionsService.remove(uuid); } @Post(':uuid_promotion/followers/:uuid_member') - @ApiOperation({ summary: 'Ajouter un membre comme follower d\'une promotion' }) + @ApiOperation({ summary: "Ajouter un membre comme follower d'une promotion" }) @ApiParam({ name: 'uuid_promotion', description: 'UUID de la promotion' }) - @ApiParam({ name: 'uuid_member', description: 'UUID du membre à ajouter comme follower' }) - @ApiResponse({ status: 200, description: 'Le membre a été ajouté comme follower avec succès.', type: Promotion }) + @ApiParam({ + name: 'uuid_member', + description: 'UUID du membre à ajouter comme follower', + }) + @ApiResponse({ + status: 200, + description: 'Le membre a été ajouté comme follower avec succès.', + type: Promotion, + }) @ApiResponse({ status: 404, description: 'Promotion ou membre non trouvé' }) - @ApiResponse({ status: 400, description: 'Le membre est déjà follower de cette promotion' }) + @ApiResponse({ + status: 400, + description: 'Le membre est déjà follower de cette promotion', + }) addFollower( @Param('uuid_promotion') uuidPromotion: string, @Param('uuid_member') uuidMember: string, @@ -82,16 +136,26 @@ export class PromotionsController { } @Post(':uuid_promotion/managers/:uuid_member') - @ApiOperation({ summary: 'Ajouter un membre comme manager d\'une promotion' }) + @ApiOperation({ summary: "Ajouter un membre comme manager d'une promotion" }) @ApiParam({ name: 'uuid_promotion', description: 'UUID de la promotion' }) - @ApiParam({ name: 'uuid_member', description: 'UUID du membre à ajouter comme manager' }) - @ApiResponse({ status: 200, description: 'Le membre a été ajouté comme manager avec succès.', type: Promotion }) + @ApiParam({ + name: 'uuid_member', + description: 'UUID du membre à ajouter comme manager', + }) + @ApiResponse({ + status: 200, + description: 'Le membre a été ajouté comme manager avec succès.', + type: Promotion, + }) @ApiResponse({ status: 404, description: 'Promotion ou membre non trouvé' }) - @ApiResponse({ status: 400, description: 'Le membre est déjà manager de cette promotion' }) + @ApiResponse({ + status: 400, + description: 'Le membre est déjà manager de cette promotion', + }) addManager( @Param('uuid_promotion') uuidPromotion: string, @Param('uuid_member') uuidMember: string, ) { return this.promotionsService.addManager(uuidPromotion, uuidMember); } -} \ No newline at end of file +} diff --git a/src/promotions/promotions.module.ts b/src/promotions/promotions.module.ts index faa4ecae..42eac12e 100644 --- a/src/promotions/promotions.module.ts +++ b/src/promotions/promotions.module.ts @@ -5,14 +5,17 @@ import { PromotionsController } from './promotions.controller'; import { Promotion } from './entities/promotion.entity'; import { RolesModule } from 'src/roles/roles.module'; import { Member } from 'src/members/entities/member.entity'; +import { IPromotionsServiceToken } from './interfaces/promotion.interface'; @Module({ - imports: [ - TypeOrmModule.forFeature([Promotion, Member]), - RolesModule - ], + imports: [TypeOrmModule.forFeature([Promotion, Member]), RolesModule], controllers: [PromotionsController], - providers: [PromotionsService], - exports: [PromotionsService] + providers: [ + { + provide: IPromotionsServiceToken, + useClass: PromotionsService, + }, + ], + exports: [IPromotionsServiceToken], }) -export class PromotionsModule {} \ No newline at end of file +export class PromotionsModule {} diff --git a/src/promotions/promotions.service.ts b/src/promotions/promotions.service.ts index 787376a1..5c4c51b1 100644 --- a/src/promotions/promotions.service.ts +++ b/src/promotions/promotions.service.ts @@ -1,14 +1,19 @@ -import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common'; +import { + Injectable, + BadRequestException, + NotFoundException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreatePromotionDto } from './dto/create-promotion.dto'; import { UpdatePromotionDto } from './dto/update-promotion.dto'; import { Promotion } from './entities/promotion.entity'; +import { IPromotionsService } from './interfaces/promotion.interface'; import { Role } from '../roles/entities/role.entity'; import { Member } from '../members/entities/member.entity'; @Injectable() -export class PromotionsService { +export class PromotionsService implements IPromotionsService { constructor( @InjectRepository(Promotion) private promotionRepository: Repository, @@ -30,7 +35,7 @@ export class PromotionsService { memberCount: 0, rolePosition: 0, hoist: false, - color: "#000000", + color: '#000000', }); // Sauvegarde du rôle @@ -44,29 +49,50 @@ export class PromotionsService { return await this.promotionRepository.save(newPromotion); } catch (error) { - throw new BadRequestException('Erreur lors de la création de la promotion: ' + error.message); + throw new BadRequestException( + 'Erreur lors de la création de la promotion: ' + error.message, + ); } } async findAll(): Promise { return await this.promotionRepository.find({ - relations: ['followers', 'managers', 'category', 'course', 'campus', 'role', 'guild'] + relations: [ + 'followers', + 'managers', + 'category', + 'course', + 'campus', + 'role', + 'guild', + ], }); } async findOne(uuid: string): Promise { const promotion = await this.promotionRepository.findOne({ where: { uuid }, - relations: ['followers', 'managers', 'category', 'course', 'campus', 'role', 'guild'] + relations: [ + 'followers', + 'managers', + 'category', + 'course', + 'campus', + 'role', + 'guild', + ], }); - + if (!promotion) { throw new NotFoundException(`Promotion avec UUID ${uuid} non trouvée`); } return promotion; } - async update(uuid: string, updatePromotionDto: UpdatePromotionDto): Promise { + async update( + uuid: string, + updatePromotionDto: UpdatePromotionDto, + ): Promise { const promotion = await this.findOne(uuid); // Mise à jour des champs autorisés @@ -74,7 +100,7 @@ export class PromotionsService { if (name !== undefined) promotion.name = name; if (startDate !== undefined) promotion.startDate = startDate; if (endDate !== undefined) promotion.endDate = endDate; - + promotion.updatedAt = new Date(); return await this.promotionRepository.save(promotion); } @@ -84,14 +110,19 @@ export class PromotionsService { return await this.promotionRepository.remove(promotion); } - async addFollower(uuidPromotion: string, uuidMember: string): Promise { + async addFollower( + uuidPromotion: string, + uuidMember: string, + ): Promise { const promotion = await this.promotionRepository.findOne({ where: { uuid: uuidPromotion }, - relations: ['followers'] + relations: ['followers'], }); if (!promotion) { - throw new NotFoundException(`Promotion avec UUID ${uuidPromotion} non trouvée`); + throw new NotFoundException( + `Promotion avec UUID ${uuidPromotion} non trouvée`, + ); } const member = await this.memberRepository.findOneBy({ uuidMember }); @@ -100,8 +131,13 @@ export class PromotionsService { } // Vérifier si le membre est déjà follower - if (promotion.followers && promotion.followers.some(follower => follower.uuidMember === uuidMember)) { - throw new BadRequestException(`Le membre est déjà follower de cette promotion`); + if ( + promotion.followers && + promotion.followers.some((follower) => follower.uuidMember === uuidMember) + ) { + throw new BadRequestException( + `Le membre est déjà follower de cette promotion`, + ); } // Initialiser le tableau des followers s'il n'existe pas @@ -111,19 +147,24 @@ export class PromotionsService { // Ajouter le membre aux followers promotion.followers.push(member); - + // Sauvegarder la promotion mise à jour return await this.promotionRepository.save(promotion); } - async addManager(uuidPromotion: string, uuidMember: string): Promise { + async addManager( + uuidPromotion: string, + uuidMember: string, + ): Promise { const promotion = await this.promotionRepository.findOne({ where: { uuid: uuidPromotion }, - relations: ['managers'] + relations: ['managers'], }); if (!promotion) { - throw new NotFoundException(`Promotion avec UUID ${uuidPromotion} non trouvée`); + throw new NotFoundException( + `Promotion avec UUID ${uuidPromotion} non trouvée`, + ); } const member = await this.memberRepository.findOneBy({ uuidMember }); @@ -132,8 +173,13 @@ export class PromotionsService { } // Vérifier si le membre est déjà manager - if (promotion.managers && promotion.managers.some(manager => manager.uuidMember === uuidMember)) { - throw new BadRequestException(`Le membre est déjà manager de cette promotion`); + if ( + promotion.managers && + promotion.managers.some((manager) => manager.uuidMember === uuidMember) + ) { + throw new BadRequestException( + `Le membre est déjà manager de cette promotion`, + ); } // Initialiser le tableau des managers s'il n'existe pas @@ -143,7 +189,7 @@ export class PromotionsService { // Ajouter le membre aux managers promotion.managers.push(member); - + // Sauvegarder la promotion mise à jour return await this.promotionRepository.save(promotion); } From 322f5a28219bcbc728c350a3ae3a82cc001b265f Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 21:54:20 +0200 Subject: [PATCH 14/20] refactor(86c9kt16n): implement interface for roles module --- src/roles/interfaces/role.interface.ts | 13 +++ src/roles/roles.controller.ts | 133 +++++++++++++++---------- src/roles/roles.module.ts | 10 +- src/roles/roles.service.ts | 36 +++++-- 4 files changed, 127 insertions(+), 65 deletions(-) create mode 100644 src/roles/interfaces/role.interface.ts diff --git a/src/roles/interfaces/role.interface.ts b/src/roles/interfaces/role.interface.ts new file mode 100644 index 00000000..b7a21d9a --- /dev/null +++ b/src/roles/interfaces/role.interface.ts @@ -0,0 +1,13 @@ +import { CreateRoleDto } from '../dto/create-role.dto'; +import { UpdateRoleDto } from '../dto/update-role.dto'; +import { Role } from '../entities/role.entity'; + +export interface IRolesService { + create(createRoleDto: CreateRoleDto); + findAll(); + findOne(uuidRole: string); + update(uuidRole: string, updateRoleDto: UpdateRoleDto); + remove(uuidRole: string); +} + +export const IRolesServicesToken = Symbol('IRolesServices'); diff --git a/src/roles/roles.controller.ts b/src/roles/roles.controller.ts index 37b4969c..fa5ea361 100644 --- a/src/roles/roles.controller.ts +++ b/src/roles/roles.controller.ts @@ -1,117 +1,144 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, HttpStatus } from '@nestjs/common'; -import { RolesService } from './roles.service'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + HttpStatus, + Inject, +} from '@nestjs/common'; import { CreateRoleDto } from './dto/create-role.dto'; import { UpdateRoleDto } from './dto/update-role.dto'; -import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody, ApiBearerAuth } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiParam, + ApiBody, + ApiBearerAuth, +} from '@nestjs/swagger'; import { Role } from './entities/role.entity'; +import { + IRolesServicesToken, + IRolesService, +} from './interfaces/role.interface'; @ApiTags('roles') @ApiBearerAuth('JWT-auth') @Controller('roles') export class RolesController { - constructor(private readonly rolesService: RolesService) {} + constructor( + @Inject(IRolesServicesToken) private readonly rolesService: IRolesService, + ) {} @Post() - @ApiOperation({ + @ApiOperation({ summary: 'Créer un nouveau rôle', - description: 'Crée un nouveau rôle dans la base de données avec les informations fournies.' + description: + 'Crée un nouveau rôle dans la base de données avec les informations fournies.', }) @ApiBody({ type: CreateRoleDto }) - @ApiResponse({ - status: HttpStatus.CREATED, + @ApiResponse({ + status: HttpStatus.CREATED, description: 'Le rôle a été créé avec succès.', - type: Role + type: Role, }) - @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: 'Données invalides fournies dans la requête.' + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Données invalides fournies dans la requête.', }) create(@Body() createRoleDto: CreateRoleDto) { return this.rolesService.create(createRoleDto); } @Get() - @ApiOperation({ + @ApiOperation({ summary: 'Récupérer tous les rôles', - description: 'Retourne la liste de tous les rôles enregistrés dans la base de données.' + description: + 'Retourne la liste de tous les rôles enregistrés dans la base de données.', }) - @ApiResponse({ - status: HttpStatus.OK, + @ApiResponse({ + status: HttpStatus.OK, description: 'Liste de tous les rôles récupérée avec succès.', - type: [Role] + type: [Role], }) findAll() { return this.rolesService.findAll(); } @Get(':uuid') - @ApiOperation({ + @ApiOperation({ summary: 'Récupérer un rôle par son UUID', - description: 'Recherche et retourne un rôle spécifique en utilisant son UUID.' + description: + 'Recherche et retourne un rôle spécifique en utilisant son UUID.', }) - @ApiParam({ - name: 'uuid', + @ApiParam({ + name: 'uuid', description: 'UUID unique du rôle à rechercher', - example: '123e4567-e89b-12d3-a456-426614174000' + example: '123e4567-e89b-12d3-a456-426614174000', }) - @ApiResponse({ - status: HttpStatus.OK, + @ApiResponse({ + status: HttpStatus.OK, description: 'Le rôle a été trouvé et récupéré avec succès.', - type: Role + type: Role, }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: 'Aucun rôle trouvé avec l\'UUID fourni.' + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: "Aucun rôle trouvé avec l'UUID fourni.", }) findOne(@Param('uuid') uuid: string) { return this.rolesService.findOne(uuid); } @Patch(':uuid') - @ApiOperation({ + @ApiOperation({ summary: 'Mettre à jour un rôle', - description: 'Met à jour les informations d\'un rôle existant en utilisant son UUID.' + description: + "Met à jour les informations d'un rôle existant en utilisant son UUID.", }) - @ApiParam({ - name: 'uuid', + @ApiParam({ + name: 'uuid', description: 'UUID unique du rôle à mettre à jour', - example: '123e4567-e89b-12d3-a456-426614174000' + example: '123e4567-e89b-12d3-a456-426614174000', }) @ApiBody({ type: UpdateRoleDto }) - @ApiResponse({ - status: HttpStatus.OK, + @ApiResponse({ + status: HttpStatus.OK, description: 'Le rôle a été mis à jour avec succès.', - type: Role + type: Role, }) - @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: 'Données invalides fournies dans la requête.' + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Données invalides fournies dans la requête.', }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: 'Aucun rôle trouvé avec l\'UUID fourni.' + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: "Aucun rôle trouvé avec l'UUID fourni.", }) update(@Param('uuid') uuid: string, @Body() updateRoleDto: UpdateRoleDto) { return this.rolesService.update(uuid, updateRoleDto); } @Delete(':uuid') - @ApiOperation({ + @ApiOperation({ summary: 'Supprimer un rôle', - description: 'Supprime un rôle existant de la base de données en utilisant son UUID.' + description: + 'Supprime un rôle existant de la base de données en utilisant son UUID.', }) - @ApiParam({ - name: 'uuid', + @ApiParam({ + name: 'uuid', description: 'UUID unique du rôle à supprimer', - example: '123e4567-e89b-12d3-a456-426614174000' + example: '123e4567-e89b-12d3-a456-426614174000', }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Le rôle a été supprimé avec succès.' + @ApiResponse({ + status: HttpStatus.OK, + description: 'Le rôle a été supprimé avec succès.', }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: 'Aucun rôle trouvé avec l\'UUID fourni.' + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: "Aucun rôle trouvé avec l'UUID fourni.", }) remove(@Param('uuid') uuid: string) { return this.rolesService.remove(uuid); diff --git a/src/roles/roles.module.ts b/src/roles/roles.module.ts index aff99f66..10b391cd 100644 --- a/src/roles/roles.module.ts +++ b/src/roles/roles.module.ts @@ -3,11 +3,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { RolesService } from './roles.service'; import { RolesController } from './roles.controller'; import { Role } from './entities/role.entity'; +import { IRolesServicesToken } from './interfaces/role.interface'; @Module({ imports: [TypeOrmModule.forFeature([Role])], controllers: [RolesController], - providers: [RolesService], - exports: [TypeOrmModule, RolesService], + providers: [ + { + provide: IRolesServicesToken, + useClass: RolesService, + }, + ], + exports: [TypeOrmModule, IRolesServicesToken], }) export class RolesModule {} diff --git a/src/roles/roles.service.ts b/src/roles/roles.service.ts index e79de36c..21f0d730 100644 --- a/src/roles/roles.service.ts +++ b/src/roles/roles.service.ts @@ -1,12 +1,17 @@ -import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; +import { + Injectable, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateRoleDto } from './dto/create-role.dto'; import { UpdateRoleDto } from './dto/update-role.dto'; import { Role } from './entities/role.entity'; +import { IRolesService } from './interfaces/role.interface'; @Injectable() -export class RolesService { +export class RolesService implements IRolesService { constructor( @InjectRepository(Role) private roleRepository: Repository, @@ -19,17 +24,20 @@ export class RolesService { const roleData = { ...createRoleDto, memberCount: parseInt(createRoleDto.memberCount, 10), - rolePosition: parseInt(createRoleDto.rolePosition, 10) + rolePosition: parseInt(createRoleDto.rolePosition, 10), }; const role = this.roleRepository.create(roleData); const savedRole = await this.roleRepository.save(role); return savedRole; } catch (error) { - if (error.code === '23505') { // Code PostgreSQL pour violation de contrainte unique + if (error.code === '23505') { + // Code PostgreSQL pour violation de contrainte unique throw new BadRequestException('Un rôle avec cet UUID existe déjà'); } - throw new BadRequestException('Erreur lors de la création du rôle: ' + error.message); + throw new BadRequestException( + 'Erreur lors de la création du rôle: ' + error.message, + ); } } @@ -38,7 +46,9 @@ export class RolesService { try { return await this.roleRepository.find(); } catch (error) { - throw new BadRequestException('Erreur lors de la récupération des rôles: ' + error.message); + throw new BadRequestException( + 'Erreur lors de la récupération des rôles: ' + error.message, + ); } } @@ -54,7 +64,9 @@ export class RolesService { if (error instanceof NotFoundException) { throw error; } - throw new BadRequestException('Erreur lors de la récupération du rôle: ' + error.message); + throw new BadRequestException( + 'Erreur lors de la récupération du rôle: ' + error.message, + ); } } @@ -68,7 +80,9 @@ export class RolesService { if (error instanceof NotFoundException) { throw error; } - throw new BadRequestException('Erreur lors de la mise à jour du rôle: ' + error.message); + throw new BadRequestException( + 'Erreur lors de la mise à jour du rôle: ' + error.message, + ); } } @@ -83,7 +97,9 @@ export class RolesService { if (error instanceof NotFoundException) { throw error; } - throw new BadRequestException('Erreur lors de la suppression du rôle: ' + error.message); + throw new BadRequestException( + 'Erreur lors de la suppression du rôle: ' + error.message, + ); } } -} \ No newline at end of file +} From ebe89fa43efbd5a19250a9f72e8a8d5eb38821a8 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 21:56:51 +0200 Subject: [PATCH 15/20] refactor(86c6w80gq): update signature dependencies after implement interfaces --- src/signature/signature.service.ts | 40 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/signature/signature.service.ts b/src/signature/signature.service.ts index f2fdce80..ae28ba36 100644 --- a/src/signature/signature.service.ts +++ b/src/signature/signature.service.ts @@ -1,10 +1,12 @@ -import { Injectable, NotFoundException, Logger } from '@nestjs/common'; -import { PromotionsService } from '../promotions/promotions.service'; -import { MembersService } from '../members/members.service'; -import { RolesService } from '../roles/roles.service'; -import { GuildsService } from '../guilds/guilds.service'; -import { ChannelsService } from '../channels/channels.service'; -import { DiscordUsersService } from '../discord-users/discord-users.service'; +import { Injectable, NotFoundException, Logger, Inject } from '@nestjs/common'; +import { IPromotionsServiceToken, IPromotionsService } from 'src/promotions/interfaces/promotion.interface'; +import { IMembersServiceToken, IMembersService } from 'src/members/interfaces/member.interface'; +import { IRolesServicesToken, IRolesService } from 'src/roles/interfaces/role.interface'; +import { IGuildsServiceToken, IGuildsService } from 'src/guilds/interfaces/guild.interface'; +import { IChannelsServiceToken, IChannelsService } from 'src/channels/interfaces/channel.interface'; +import { IDiscordUsersServiceToken, IDiscordUsersService } from 'src/discord-users/interfaces/discord-user.interface'; + + import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Promotion } from '../promotions/entities/promotion.entity'; @@ -22,12 +24,24 @@ export class SignatureService { private readonly logger = new Logger(SignatureService.name); constructor( - private readonly promotionsService: PromotionsService, - private readonly membersService: MembersService, - private readonly rolesService: RolesService, - private readonly guildsService: GuildsService, - private readonly channelsService: ChannelsService, - private readonly discordUsersService: DiscordUsersService, + @Inject(IPromotionsServiceToken) + private readonly promotionsService: IPromotionsService, + + @Inject(IMembersServiceToken) + private readonly membersService: IMembersService, + + @Inject(IRolesServicesToken) + private readonly rolesService: IRolesService, + + @Inject(IGuildsServiceToken) + private readonly guildsService: IGuildsService, + + @Inject(IChannelsServiceToken) + private readonly channelsService: IChannelsService, + + @Inject(IDiscordUsersServiceToken) + private readonly discordUsersService: IDiscordUsersService, + @InjectRepository(Promotion) private promotionRepository: Repository, @InjectRepository(Member) From e1feb5a223445b36fc9869bd255f168a791d0752 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Fri, 1 May 2026 21:58:51 +0200 Subject: [PATCH 16/20] chore:update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 325a4ce7..bbb05b69 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ lerna-debug.log* *.sublime-workspace # IDE - VSCode +.vscode/ .vscode/* !.vscode/settings.json !.vscode/tasks.json From 31a18992e615c61ee1990c2a4d9aa80565f732f8 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Tue, 17 Jun 2025 22:35:31 +0200 Subject: [PATCH 17/20] feat(86c4246cr): implement helmet, csp and adjust dependency necessity to have fastify and platform fastify at the same version --- package-lock.json | 71 +++++++++++++++++++++++------------------------ package.json | 3 +- src/main.ts | 31 ++++++++++++++++++++- 3 files changed, 66 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b9d5644..85318a69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { + "@fastify/helmet": "^13.0.1", "@fastify/static": "^8.1.1", "@nestjs/axios": "^4.0.0", "@nestjs/common": "^11.0.1", @@ -26,7 +27,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.7", - "fastify": "^5.2.1", + "fastify": "^5.3.3", "jsonwebtoken": "^9.0.2", "nestjs-pino": "^4.3.0", "passport": "^0.7.0", @@ -1589,6 +1590,26 @@ "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==", "license": "MIT" }, + "node_modules/@fastify/helmet": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@fastify/helmet/-/helmet-13.0.1.tgz", + "integrity": "sha512-i+ifqazG3d0HwHL3zuZdg6B/WPc9Ee6kVfGpwGho4nxm0UaK1htss0zq+1rVhOoAorZlCgTZ3/i4S58hUGkkoA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "helmet": "^8.0.0" + } + }, "node_modules/@fastify/merge-json-schemas": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", @@ -3626,39 +3647,6 @@ } } }, - "node_modules/@nestjs/platform-fastify/node_modules/fastify": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.3.3.tgz", - "integrity": "sha512-nCBiBCw9q6jPx+JJNVgO8JVnTXeUyrGcyTKPQikRkA/PanrFcOIo4R+ZnLeOLPZPGgzjomqfVarzE0kYx7qWiQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/ajv-compiler": "^4.0.0", - "@fastify/error": "^4.0.0", - "@fastify/fast-json-stringify-compiler": "^5.0.0", - "@fastify/proxy-addr": "^5.0.0", - "abstract-logging": "^2.0.1", - "avvio": "^9.0.0", - "fast-json-stringify": "^6.0.0", - "find-my-way": "^9.0.0", - "light-my-request": "^6.0.0", - "pino": "^9.0.0", - "process-warning": "^5.0.0", - "rfdc": "^1.3.1", - "secure-json-parse": "^4.0.0", - "semver": "^7.6.0", - "toad-cache": "^3.7.0" - } - }, "node_modules/@nestjs/schematics": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.5.tgz", @@ -8885,9 +8873,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastify": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.4.0.tgz", - "integrity": "sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.3.3.tgz", + "integrity": "sha512-nCBiBCw9q6jPx+JJNVgO8JVnTXeUyrGcyTKPQikRkA/PanrFcOIo4R+ZnLeOLPZPGgzjomqfVarzE0kYx7qWiQ==", "funding": [ { "type": "github", @@ -9619,6 +9607,15 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/help-me": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", diff --git a/package.json b/package.json index 03b3b963..a8b33b98 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:e2e": "playwright test" }, "dependencies": { + "@fastify/helmet": "^13.0.1", "@fastify/static": "^8.1.1", "@nestjs/axios": "^4.0.0", "@nestjs/common": "^11.0.1", @@ -37,7 +38,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.7", - "fastify": "^5.2.1", + "fastify": "^5.3.3", "jsonwebtoken": "^9.0.2", "nestjs-pino": "^4.3.0", "passport": "^0.7.0", diff --git a/src/main.ts b/src/main.ts index 23c63c35..3d93aaa7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,8 @@ import { EmptyResponseInterceptor } from './common/interceptors/empty-response.i import { HttpExceptionFilter } from './common/filters/http-exception.filter'; import { ValidationPipe } from '@nestjs/common'; import { join } from 'path'; +import helmet from '@fastify/helmet'; +import { createHash } from 'crypto'; dotenv.config(); async function bootstrap() { @@ -14,6 +16,10 @@ async function bootstrap() { AppModule, new FastifyAdapter(), ); + + const nonce = createHash('sha256') + .update(Date.now().toString()) + .digest('base64'); app.useGlobalInterceptors(new EmptyResponseInterceptor()); app.useGlobalFilters(new HttpExceptionFilter()); @@ -56,11 +62,34 @@ async function bootstrap() { }); } - SwaggerModule.setup('api', app, document); + SwaggerModule.setup('api', app, document, { + customJs: [ + `'nonce-${nonce}'` + ] + }); // Nous ne définissons plus de préfixe global pour l'API // app.setGlobalPrefix('api'); + //Attention, lorsque fastify et nestjs/platform-fastify n'ont pas la même version, + //cela provoque des erreurs sur helmet + await app.register(helmet, { + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", `'nonce-${nonce}'`], + styleSrc: ["'self'", `'nonce-${nonce}'`], + imgSrc: ["'self'", "data:", "validator.swagger.io"], + connectSrc: ["'self'", "https://discord.com/api","http://localhost:3000", "http://127.0.0.1:3000" ], + fontSrc: ["'self'"], + objectSrc: ["'none'"], + mediaSrc: ["'none'"], + frameSrc: ["'none'"], + formAction: ["'self'"], + } + }, + }); + // Configuration de la version de l'API await app.listen(3000, '0.0.0.0'); } From 3659541257d8cf7608a0c61de78df544c6e25470 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Tue, 17 Jun 2025 22:51:50 +0200 Subject: [PATCH 18/20] feat(86c4246hm): implement cors --- src/main.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main.ts b/src/main.ts index 3d93aaa7..3acb3d00 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,6 +16,26 @@ async function bootstrap() { AppModule, new FastifyAdapter(), ); + + // Configuration CORS + app.enableCors({ + origin: [ + 'http://localhost:3000', + 'https://discord.com', + 'https://discordapp.com', + 'https://cdn.discordapp.com', + 'https://discord.gg', + 'wss://gateway.discord.gg' + ], + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], + allowedHeaders: [ + 'Content-Type', + 'Authorization', + 'X-Super-Properties', + 'X-Discord-Locale' + ], + credentials: false, + }); const nonce = createHash('sha256') .update(Date.now().toString()) From 9e90c970e6befb52f617ea89930af383867502fa Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Tue, 17 Jun 2025 23:00:08 +0200 Subject: [PATCH 19/20] feat(86c4246ke): add rate limit --- package-lock.json | 12 ++++++++++++ package.json | 1 + src/app.module.ts | 8 +++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 85318a69..dd29f418 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@nestjs/platform-express": "^11.0.1", "@nestjs/platform-fastify": "^11.0.9", "@nestjs/swagger": "^11.0.5", + "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "@types/uuid": "^10.0.0", "axios": "^1.7.9", @@ -3806,6 +3807,17 @@ } } }, + "node_modules/@nestjs/throttler": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.4.0.tgz", + "integrity": "sha512-osL67i0PUuwU5nqSuJjtUJZMkxAnYB4VldgYUMGzvYRJDCqGRFMWbsbzm/CkUtPLRL30I8T74Xgt/OQxnYokiA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0" + } + }, "node_modules/@nestjs/typeorm": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", diff --git a/package.json b/package.json index a8b33b98..42ce214b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@nestjs/platform-express": "^11.0.1", "@nestjs/platform-fastify": "^11.0.9", "@nestjs/swagger": "^11.0.5", + "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "@types/uuid": "^10.0.0", "axios": "^1.7.9", diff --git a/src/app.module.ts b/src/app.module.ts index f233a74a..bbe76494 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -37,6 +37,8 @@ import { AnswerTemplatesModule } from './answer-templates/answer-templates.modul // IMPORTS POUR LA SÉCURITÉ GLOBALE import { APP_GUARD } from '@nestjs/core'; import { GlobalAuthGuard } from './auth/guards/global-auth.guard'; +import { ThrottlerModule } from '@nestjs/throttler'; + /** * Module principal de l'application @@ -84,7 +86,11 @@ import { GlobalAuthGuard } from './auth/guards/global-auth.guard'; SignatureModule, PollTemplatesModule, QuestionTemplatesModule, - AnswerTemplatesModule + AnswerTemplatesModule, + ThrottlerModule.forRoot([{ + ttl: 1, // 1 seconde + limit: 50, // 50 requêtes par seconde + }]), ], controllers: [AppController], // CONFIGURATION DU GUARD GLOBAL From 22d137396174723fb45bda4fba670666d3602c23 Mon Sep 17 00:00:00 2001 From: Aurore Reynier Date: Sun, 29 Jun 2025 00:21:12 +0200 Subject: [PATCH 20/20] feat(86c4246cr): change csp wether it's dev or prod environment --- src/main.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main.ts b/src/main.ts index 3acb3d00..82b17eb4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,7 +8,6 @@ import { HttpExceptionFilter } from './common/filters/http-exception.filter'; import { ValidationPipe } from '@nestjs/common'; import { join } from 'path'; import helmet from '@fastify/helmet'; -import { createHash } from 'crypto'; dotenv.config(); async function bootstrap() { @@ -21,7 +20,7 @@ async function bootstrap() { app.enableCors({ origin: [ 'http://localhost:3000', - 'https://discord.com', + 'https://discord.com', 'https://discordapp.com', 'https://cdn.discordapp.com', 'https://discord.gg', @@ -36,10 +35,6 @@ async function bootstrap() { ], credentials: false, }); - - const nonce = createHash('sha256') - .update(Date.now().toString()) - .digest('base64'); app.useGlobalInterceptors(new EmptyResponseInterceptor()); app.useGlobalFilters(new HttpExceptionFilter()); @@ -83,24 +78,21 @@ async function bootstrap() { } SwaggerModule.setup('api', app, document, { - customJs: [ - `'nonce-${nonce}'` - ] }); - // Nous ne définissons plus de préfixe global pour l'API - // app.setGlobalPrefix('api'); + + const isDevelopment = process.env.NODE_ENV === 'development'; //Attention, lorsque fastify et nestjs/platform-fastify n'ont pas la même version, //cela provoque des erreurs sur helmet await app.register(helmet, { - contentSecurityPolicy: { + contentSecurityPolicy: isDevelopment ? false : { directives: { defaultSrc: ["'self'"], - scriptSrc: ["'self'", `'nonce-${nonce}'`], - styleSrc: ["'self'", `'nonce-${nonce}'`], + scriptSrc: ["'self'"], + styleSrc: ["'self'"], imgSrc: ["'self'", "data:", "validator.swagger.io"], - connectSrc: ["'self'", "https://discord.com/api","http://localhost:3000", "http://127.0.0.1:3000" ], + connectSrc: ["'self'", "https://discord.com", "https://discord.com/api", "http://localhost:3000", "http://127.0.0.1:3000" ], fontSrc: ["'self'"], objectSrc: ["'none'"], mediaSrc: ["'none'"],