From 77b33144f6d86c3d34ecae1b96f2b86eb50810d0 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Sun, 8 Dec 2024 18:18:11 +0100 Subject: [PATCH] Utilisation du plugin swagger pour de la meilleure documentation, et meilleure prise en charge d'erreurs --- server/nest-cli.json | 10 +++- server/src/auth/auth.controller.ts | 12 ++-- server/src/auth/dto/login.dto.ts | 7 +-- server/src/auth/entity/auth.entity.ts | 6 +- .../challenge-actions.controller.ts | 59 +++++++++++++------ .../challenge-actions.service.ts | 6 +- .../src/challenges/challenges.controller.ts | 58 ++++++++++++------ server/src/challenges/challenges.service.ts | 4 ++ .../geolocations/geolocations.controller.ts | 46 ++++++++++----- .../src/geolocations/geolocations.service.ts | 4 +- .../money-updates/money-updates.controller.ts | 45 ++++++++++---- .../money-updates/money-updates.service.ts | 6 +- server/src/players/players.controller.ts | 31 ++++++---- server/src/trains/trains.controller.ts | 56 +++++++++++++----- server/src/trains/trains.service.ts | 6 +- 15 files changed, 249 insertions(+), 107 deletions(-) diff --git a/server/nest-cli.json b/server/nest-cli.json index f9aa683..b246267 100644 --- a/server/nest-cli.json +++ b/server/nest-cli.json @@ -3,6 +3,14 @@ "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { - "deleteOutDir": true + "deleteOutDir": true, + "plugins": [ + { + "name": "@nestjs/swagger", + "options": { + "introspectComments": true + } + } + ] } } diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index 11f1273..e6b6372 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Post } from '@nestjs/common' import { AuthService } from './auth.service' -import { ApiOkResponse, ApiTags } from '@nestjs/swagger' +import { ApiTags } from '@nestjs/swagger' import { AuthEntity } from './entity/auth.entity' import { LoginDto } from './dto/login.dto' @@ -9,9 +9,13 @@ import { LoginDto } from './dto/login.dto' export class AuthController { constructor(private readonly authService: AuthService) {} + /** + * Se connecter par nom et mot de passe pour récupérer un jeton de connexion. + * + * @throws {401} Mot de passe incorrect. + */ @Post('login') - @ApiOkResponse({ type: AuthEntity }) - login(@Body() { name, password }: LoginDto) { - return this.authService.login(name, password) + async login(@Body() { name, password }: LoginDto): Promise { + return await this.authService.login(name, password) } } diff --git a/server/src/auth/dto/login.dto.ts b/server/src/auth/dto/login.dto.ts index eb6dde5..5641b78 100644 --- a/server/src/auth/dto/login.dto.ts +++ b/server/src/auth/dto/login.dto.ts @@ -1,14 +1,9 @@ -import { ApiProperty } from '@nestjs/swagger' -import { IsNotEmpty, IsString } from 'class-validator' +import { IsNotEmpty } from 'class-validator' export class LoginDto { - @IsString() @IsNotEmpty() - @ApiProperty() name: string - @IsString() @IsNotEmpty() - @ApiProperty() password: string } \ No newline at end of file diff --git a/server/src/auth/entity/auth.entity.ts b/server/src/auth/entity/auth.entity.ts index e331a97..5ba0f3f 100644 --- a/server/src/auth/entity/auth.entity.ts +++ b/server/src/auth/entity/auth.entity.ts @@ -1,6 +1,6 @@ -import { ApiProperty } from '@nestjs/swagger' - export class AuthEntity { - @ApiProperty() + /** + * Jeton d'accès à l'API, valable 12h. + */ accessToken: string } diff --git a/server/src/challenge-actions/challenge-actions.controller.ts b/server/src/challenge-actions/challenge-actions.controller.ts index 56157ba..8bf95e4 100644 --- a/server/src/challenge-actions/challenge-actions.controller.ts +++ b/server/src/challenge-actions/challenge-actions.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe, HttpCode, UseGuards, Req, Query, NotFoundException } from '@nestjs/common' import { ChallengeActionsService } from './challenge-actions.service' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' -import { ApiBearerAuth, ApiConflictResponse, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' +import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger' import { ChallengeActionEntity } from './entities/challenge-action.entity' import { CreateChallengeActionDto } from './dto/create-challenge-action.dto' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' @@ -15,33 +15,44 @@ import { EndChallengeActionDto } from './dto/end-challenge-action.dto' export class ChallengeActionsController { constructor(private readonly challengeActionsService: ChallengeActionsService) {} + /** + * Création d'une action de défi + * + * @throws {400} Erreurs dans le formulaire de création + * @throws {401} Non authentifié⋅e + */ @Post() @HttpCode(201) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiCreatedResponse({ type: ChallengeActionEntity, description: "Objet créé avec succès" }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async create(@Req() request: AuthenticatedRequest, @Body() createChallengeActionDto: CreateChallengeActionDto): Promise { const challenge = await this.challengeActionsService.create(request.user, createChallengeActionDto) return new ChallengeActionEntity(challenge) } + /** + * Recherche d'actions de défi + * + * @throws {401} Non authentifié⋅e + */ @Get() @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOkResponsePaginated(ChallengeActionEntity) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async findAll(@Query() queryPagination: QueryPaginationDto, @Query() filterChallengeActions: FilterChallengeActionsDto): Promise> { const [challengeActions, total] = await this.challengeActionsService.findAll(queryPagination, filterChallengeActions) return paginateOutput(challengeActions.map(challengeAction => new ChallengeActionEntity(challengeAction)), total, queryPagination) } + /** + * Recherche d'une action de défi par identifiant + * + * @throws {401} Non authentifié⋅e + * @throws {404} Action de défi non trouvée + */ @Get(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeActionEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async findOne(@Param('id', ParseIntPipe) id: number): Promise { const challenge = await this.challengeActionsService.findOne(id) if (!challenge) @@ -49,34 +60,44 @@ export class ChallengeActionsController { return new ChallengeActionEntity(challenge) } + /** + * Modification d'une action de défi par identifiant + * + * @throws {400} Erreurs dans le formulaire de modification + * @throws {401} Non authentifié⋅e + * @throws {404} Action de défi non trouvée + */ @Patch(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeActionEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) - async update(@Param('id', ParseIntPipe) id: number, @Body() updateChallengeActionDto: UpdateChallengeActionDto) { + async update(@Param('id', ParseIntPipe) id: number, @Body() updateChallengeActionDto: UpdateChallengeActionDto): Promise { return await this.challengeActionsService.update(id, updateChallengeActionDto) } + /** + * Suppression d'une action de défi par identifiant + * + * @throws {401} Non authentifié⋅e + * @throws {404} Action de défi non trouvée + */ @Delete(':id') @HttpCode(204) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeActionEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) - async remove(@Param('id', ParseIntPipe) id: number) { + @ApiNoContentResponse({ description: "Action de défi supprimée avec succès" }) + async remove(@Param('id', ParseIntPipe) id: number): Promise { await this.challengeActionsService.remove(id) } + /** + * Terminer l'action de défi en cours + * + * @throws {401} Non authentifié⋅e + * @throws {409} Aucun défi à terminer n'est en cours + */ @Post('/end-current') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeActionEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) - @ApiConflictResponse({ description: "Aucun défi à terminer n'est en cours" }) async endCurrent(@Req() request: AuthenticatedRequest, @Body() { success }: EndChallengeActionDto): Promise { const challengeAction = await this.challengeActionsService.endCurrentChallenge(request.user, success) return new ChallengeActionEntity(challengeAction) diff --git a/server/src/challenge-actions/challenge-actions.service.ts b/server/src/challenge-actions/challenge-actions.service.ts index 2ef16c9..fecceb7 100644 --- a/server/src/challenge-actions/challenge-actions.service.ts +++ b/server/src/challenge-actions/challenge-actions.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Injectable, UnprocessableEntityException } from '@nestjs/common' +import { BadRequestException, Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common' import { CreateChallengeActionDto } from './dto/create-challenge-action.dto' import { UpdateChallengeActionDto } from './dto/update-challenge-action.dto' import { ChallengeAction, Player } from '@prisma/client' @@ -35,6 +35,8 @@ export class ChallengeActionsService { } async update(id: number, updateChallengeActionDto: UpdateChallengeActionDto): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Aucune action de défi trouvée avec l'identifiant ${id}`) return await this.prisma.challengeAction.update({ where: { id }, data: updateChallengeActionDto, @@ -42,6 +44,8 @@ export class ChallengeActionsService { } async remove(id: number): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Aucune action de défi trouvée avec l'identifiant ${id}`) return await this.prisma.challengeAction.delete({ where: { id }, }) diff --git a/server/src/challenges/challenges.controller.ts b/server/src/challenges/challenges.controller.ts index 3548bf3..644a5bd 100644 --- a/server/src/challenges/challenges.controller.ts +++ b/server/src/challenges/challenges.controller.ts @@ -3,7 +3,7 @@ import { ChallengesService } from './challenges.service' import { CreateChallengeDto } from './dto/create-challenge.dto' import { UpdateChallengeDto } from './dto/update-challenge.dto' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' -import { ApiBearerAuth, ApiConflictResponse, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' +import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger' import { ChallengeEntity } from './entities/challenge.entity' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' @@ -13,33 +13,44 @@ import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto' export class ChallengesController { constructor(private readonly challengesService: ChallengesService) {} + /** + * Création d'un défi + * + * @throws {400} Erreurs dans le formulaire de création + * @throws {401} Non authentifié⋅e + */ @Post() @HttpCode(201) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiCreatedResponse({ type: ChallengeEntity, description: "Objet créé avec succès" }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async create(@Body() createChallengeDto: CreateChallengeDto): Promise { const challenge = await this.challengesService.create(createChallengeDto) return new ChallengeEntity(challenge) } + /** + * Recherche de défis + * + * @throws {401} Non authentifié⋅e + */ @Get() @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOkResponsePaginated(ChallengeEntity) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async findAll(@Query() queryPagination?: QueryPaginationDto): Promise> { const [challenges, total] = await this.challengesService.findAll(queryPagination) return paginateOutput(challenges.map(challenge => new ChallengeEntity(challenge)), total, queryPagination) } + /** + * Recherche d'un défi par identifiant + * + * @throws {401} Non authentifié⋅e + * @throws {404} Défi non trouvé + */ @Get(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async findOne(@Param('id', ParseIntPipe) id: number): Promise { const challenge = await this.challengesService.findOne(id) if (!challenge) @@ -47,34 +58,47 @@ export class ChallengesController { return new ChallengeEntity(challenge) } + /** + * Modification d'un défi par identifiant + * + * @throws {400} Erreurs dans le formulaire de modification + * @throws {401} Non authentifié⋅e + * @throws {404} Défi non trouvé + */ @Patch(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async update(@Param('id', ParseIntPipe) id: number, @Body() updateChallengeDto: UpdateChallengeDto) { return await this.challengesService.update(id, updateChallengeDto) } + /** + * Suppression d'un défi par identifiant + * + * @throws {401} Non authentifié⋅e + * @throws {404} Défi non trouvé + */ @Delete(':id') @HttpCode(204) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) + @ApiNoContentResponse({ description: "Le défi a bien été supprimé" }) async remove(@Param('id', ParseIntPipe) id: number) { await this.challengesService.remove(id) } + /** + * Tirage d'un nouveau défi aléatoire + * + * @remarks Aucun défi ne doit être en cours. + * + * @throws {401} Non authentifié⋅e + * @throws {404} Plus aucun défi n'est disponible + * @throws {409} Un défi est déjà en cours d'accomplissement + */ @Post('/draw-random') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: ChallengeEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) - @ApiConflictResponse({ description: "Un défi est déjà en cours d'accomplissement" }) async drawRandom(@Req() request: AuthenticatedRequest): Promise { const challenge = await this.challengesService.drawRandom(request.user) return new ChallengeEntity(challenge) diff --git a/server/src/challenges/challenges.service.ts b/server/src/challenges/challenges.service.ts index 67db055..39b1ea6 100644 --- a/server/src/challenges/challenges.service.ts +++ b/server/src/challenges/challenges.service.ts @@ -38,6 +38,8 @@ export class ChallengesService { } async update(id: number, updateChallengeDto: UpdateChallengeDto): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Aucun défi n'existe avec l'identifiant ${id}`) return await this.prisma.challenge.update({ where: { id }, data: updateChallengeDto, @@ -48,6 +50,8 @@ export class ChallengesService { } async remove(id: number): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Aucun défi n'existe avec l'identifiant ${id}`) return await this.prisma.challenge.delete({ where: { id }, include: { diff --git a/server/src/geolocations/geolocations.controller.ts b/server/src/geolocations/geolocations.controller.ts index 0393526..381675e 100644 --- a/server/src/geolocations/geolocations.controller.ts +++ b/server/src/geolocations/geolocations.controller.ts @@ -1,8 +1,8 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe, UseGuards, HttpCode, Req, NotFoundException, Query } from '@nestjs/common' +import { Controller, Get, Post, Body, Param, Delete, ParseIntPipe, UseGuards, HttpCode, Req, NotFoundException, Query } from '@nestjs/common' import { GeolocationsService } from './geolocations.service' import { CreateGeolocationDto } from './dto/create-geolocation.dto' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' -import { ApiBearerAuth, ApiCreatedResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' +import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger' import { GeolocationEntity } from './entities/geolocation.entity' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' @@ -13,33 +13,44 @@ import { PlayerFilterDto } from 'src/common/dto/player_filter.dto' export class GeolocationsController { constructor(private readonly geolocationsService: GeolocationsService) {} + /** + * Ajout d'une géolocalisation pour læ joueur⋅se connecté⋅e + * + * @throws {400} Erreurs dans le formulaire de création + * @throws {401} Non authentifié⋅e + */ @Post() @HttpCode(201) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiCreatedResponse({ type: GeolocationEntity, description: "Objet créé avec succès" }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async create(@Req() request: AuthenticatedRequest, @Body() createGeolocationDto: CreateGeolocationDto): Promise { const geolocation = await this.geolocationsService.create(request.user, createGeolocationDto) return new GeolocationEntity(geolocation) } + /** + * Recherche de géolocalisations + * + * @throws {401} Non authentifié⋅e + */ @Get() @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOkResponsePaginated(GeolocationEntity) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async findAll(@Query() queryPagination?: QueryPaginationDto, @Query() playerFilter?: PlayerFilterDto): Promise> { const [geolocations, total] = await this.geolocationsService.findAll(queryPagination, playerFilter) return paginateOutput(geolocations.map(geolocation => new GeolocationEntity(geolocation)), total, queryPagination) } + /** + * Recherche d'une géolocalisation par identifiant + * + * @throws {401} Non authentifié⋅e + * @throws {404} Géolocalisation non trouvée + */ @Get(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: GeolocationEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async findOne(@Param('id', ParseIntPipe) id: number): Promise { const geolocation = await this.geolocationsService.findOne(id) if (!geolocation) @@ -47,12 +58,15 @@ export class GeolocationsController { return new GeolocationEntity(geolocation) } + /** + * Récupération de la dernière posititon + * + * @throws {401} Non authentifié⋅e + * @throws {404} Aucune localisation envoyée + */ @Get('/last-location/:playerId') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: GeolocationEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Aucune localisation trouvée" }) async findLastLocation(@Param('playerId', ParseIntPipe) playerId: number): Promise { const geolocation = await this.geolocationsService.findLastLocation(playerId) if (!geolocation) @@ -60,13 +74,17 @@ export class GeolocationsController { return new GeolocationEntity(geolocation) } + /** + * Suppression d'une localisation + * + * @throws {401} Non authentifié⋅e + * @throws {404} Géolocalisation non trouvée + */ @Delete(':id') @HttpCode(204) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiNoContentResponse({ description: "Objet supprimé avec succès" }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) + @ApiNoContentResponse({ description: "La géolocalisation a bien été supprimée" }) async remove(@Param('id', ParseIntPipe) id: number): Promise { await this.geolocationsService.remove(+id) } diff --git a/server/src/geolocations/geolocations.service.ts b/server/src/geolocations/geolocations.service.ts index fe4e661..3cde2d4 100644 --- a/server/src/geolocations/geolocations.service.ts +++ b/server/src/geolocations/geolocations.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common' +import { Injectable, NotFoundException } from '@nestjs/common' import { CreateGeolocationDto } from './dto/create-geolocation.dto' import { PrismaService } from 'src/prisma/prisma.service' import { Geolocation, Player, Prisma } from '@prisma/client' @@ -43,6 +43,8 @@ export class GeolocationsService { async remove(id: number): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Aucune géolocalisation n'existe avec l'identifiant ${id}`) return await this.prisma.geolocation.delete({ where: { id } }) } } diff --git a/server/src/money-updates/money-updates.controller.ts b/server/src/money-updates/money-updates.controller.ts index 79dcca8..3f911a7 100644 --- a/server/src/money-updates/money-updates.controller.ts +++ b/server/src/money-updates/money-updates.controller.ts @@ -3,7 +3,7 @@ import { MoneyUpdatesService } from './money-updates.service' import { CreateMoneyUpdateDto } from './dto/create-money-update.dto' import { UpdateMoneyUpdateDto } from './dto/update-money-update.dto' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' -import { ApiBearerAuth, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' +import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger' import { MoneyUpdateEntity } from './entities/money-update.entity' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' @@ -14,33 +14,44 @@ import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto' export class MoneyUpdatesController { constructor(private readonly moneyUpdatesService: MoneyUpdatesService) {} + /** + * Création d'une modification de solde + * + * @throws {400} Erreurs dans le formulaire de création + * @throws {401} Non authentifié⋅e + */ @Post() @HttpCode(201) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiCreatedResponse({ type: MoneyUpdateEntity, description: "Objet créé avec succès" }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async create(@Req() request: AuthenticatedRequest, @Body() createMoneyUpdateDto: CreateMoneyUpdateDto): Promise { const moneyUpdate = await this.moneyUpdatesService.create(request.user, createMoneyUpdateDto) return new MoneyUpdateEntity(moneyUpdate) } + /** + * Recherche de modifications de solde + * + * @throws {401} Non authentifié⋅e + */ @Get() @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOkResponsePaginated(MoneyUpdateEntity) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise> { const [trains, total] = await this.moneyUpdatesService.findAll(queryPagination, playerFilter) return paginateOutput(trains.map(train => new MoneyUpdateEntity(train)), total, queryPagination) } + /** + * Recherche d'une modification de solde + * + * @throws {401} Non authentifié⋅e + * @throws {404} Modification de solde non trouvée + */ @Get(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: MoneyUpdateEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async findOne(@Param('id', ParseIntPipe) id: number): Promise { const train = await this.moneyUpdatesService.findOne(id) if (!train) @@ -48,23 +59,31 @@ export class MoneyUpdatesController { return new MoneyUpdateEntity(train) } + /** + * Modification d'une modification de solde + * + * @throws {400} Erreurs dans le formulaire de modification + * @throws {401} Non authentifié⋅e + * @throws {404} Modification de solde non trouvée + */ @Patch(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: MoneyUpdateEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async update(@Param('id', ParseIntPipe) id: number, @Body() updateMoneyUpdateDto: UpdateMoneyUpdateDto) { return await this.moneyUpdatesService.update(id, updateMoneyUpdateDto) } + /** + * Suppression d'une modification de solde + * + * @throws {401} Non authentifié⋅e + * @throws {404} Modification de solde non trouvée + */ @Delete(':id') @HttpCode(204) @UseGuards(JwtAuthGuard) + @ApiNoContentResponse({ description: "La modification de solde a bien été supprimée" }) @ApiBearerAuth() - @ApiOkResponse({ type: MoneyUpdateEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async remove(@Param('id', ParseIntPipe) id: number) { await this.moneyUpdatesService.remove(id) } diff --git a/server/src/money-updates/money-updates.service.ts b/server/src/money-updates/money-updates.service.ts index d98a3f3..27c6e75 100644 --- a/server/src/money-updates/money-updates.service.ts +++ b/server/src/money-updates/money-updates.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common' +import { Injectable, NotFoundException } from '@nestjs/common' import { CreateMoneyUpdateDto } from './dto/create-money-update.dto' import { UpdateMoneyUpdateDto } from './dto/update-money-update.dto' import { PrismaService } from 'src/prisma/prisma.service' @@ -39,6 +39,8 @@ export class MoneyUpdatesService { } async update(id: number, updateMoneyUpdateDto: UpdateMoneyUpdateDto): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Aucune modification de solde n'existe avec l'identifiant ${id}`) return await this.prisma.moneyUpdate.update({ where: { id }, data: updateMoneyUpdateDto, @@ -46,6 +48,8 @@ export class MoneyUpdatesService { } async remove(id: number): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Aucune modification de solde n'existe avec l'identifiant ${id}`) return await this.prisma.moneyUpdate.delete({ where: { id }, }) diff --git a/server/src/players/players.controller.ts b/server/src/players/players.controller.ts index 70b8393..35f373b 100644 --- a/server/src/players/players.controller.ts +++ b/server/src/players/players.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Get, HttpCode, NotFoundException, Param, ParseIntPipe, Patch, Query, Req, UseGuards } from '@nestjs/common' import { PlayersService } from './players.service' -import { ApiBadRequestResponse, ApiBearerAuth, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' +import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger' import { PlayerEntity } from './entities/player.entity' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { UpdatePasswordDto } from './dto/player_password.dto' @@ -12,37 +12,48 @@ import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto' export class PlayersController { constructor(private readonly playersService: PlayersService) {} + /** + * Récupération de toustes les joueur⋅ses + * + * @throws {401} Non authentifié⋅e + */ @Get() @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOkResponsePaginated(PlayerEntity) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async findAll(@Query() queryPagination?: QueryPaginationDto): Promise> { const [players, total] = await this.playersService.findAll(queryPagination) return paginateOutput(players.map(player => new PlayerEntity(player)), total, queryPagination) } + /** + * Récupération d'un⋅e joueur⋅se par son identifiant + * + * @throws {401} Non authentifié⋅e + * @throws {404} Joueur⋅se non trouvé⋅e + */ @Get(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: PlayerEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Joueur⋅se non trouvé⋅e" }) - async findOne(@Param('id', ParseIntPipe) id: number) { + async findOne(@Param('id', ParseIntPipe) id: number): Promise { const player = await this.playersService.findOne(id) if (!player) throw new NotFoundException(`Læ joueur⋅se avec l'identifiant ${id} n'existe pas`) return new PlayerEntity(player) } + /** + * Modification du mot de passe + * + * @throws {400} Mot de passe invalide + * @throws {401} Non authentifié⋅e + */ @Patch('/update-password') @HttpCode(204) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiNoContentResponse({description: "Le mot de passe a bien été modifié."}) - @ApiBadRequestResponse({description: "Erreur dans la saisie du nouveau mot de passe."}) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - async updatePassword(@Req() request: AuthenticatedRequest, @Body() body: UpdatePasswordDto) { + @ApiNoContentResponse({ description: "Le mot de passe a bien été modifié." }) + async updatePassword(@Req() request: AuthenticatedRequest, @Body() body: UpdatePasswordDto): Promise { await this.playersService.updatePassword(request.user, body) } } diff --git a/server/src/trains/trains.controller.ts b/server/src/trains/trains.controller.ts index c06fd4f..28ad57a 100644 --- a/server/src/trains/trains.controller.ts +++ b/server/src/trains/trains.controller.ts @@ -1,10 +1,10 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, HttpCode, UseGuards, Query, ParseIntPipe, NotFoundException, Req } from '@nestjs/common' +import { Controller, Get, Post, Body, Patch, Param, Delete, HttpCode, UseGuards, Query, NotFoundException, Req } from '@nestjs/common' import { TrainsService } from './trains.service' import { CreateTrainDto } from './dto/create-train.dto' import { UpdateTrainDto } from './dto/update-train.dto' import { TrainEntity } from './entities/train.entity' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' -import { ApiBearerAuth, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse, ApiUnprocessableEntityResponse } from '@nestjs/swagger' +import { ApiBearerAuth, ApiCreatedResponse, ApiNoContentResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto' @@ -15,33 +15,46 @@ import { PlayerFilterDto } from 'src/common/dto/player_filter.dto' export class TrainsController { constructor(private readonly trainsService: TrainsService) {} + /** + * Création d'un trajet en train manuellement + * + * @throws {400} Erreurs dans le formulaire de création + * @throws {401} Non authentifié⋅e + */ @Post() @HttpCode(201) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiCreatedResponse({ type: TrainEntity, description: "Objet créé avec succès" }) + @ApiCreatedResponse({ type: TrainEntity, description: "Trajet en train créé avec succès" }) @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async create(@Body() createTrainDto: CreateTrainDto): Promise { const train = await this.trainsService.create(createTrainDto) return new TrainEntity(train) } + /** + * Recherche de trajets en train + * + * @throws {401} Non authentifié⋅e + */ @Get() @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOkResponsePaginated(TrainEntity) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise> { const [trains, total] = await this.trainsService.findAll(queryPagination, playerFilter) return paginateOutput(trains.map(train => new TrainEntity(train)), total, queryPagination) } + /** + * Recherche d'un trajet en train + * + * @throws {401} Non authentifié⋅e + * @throws {404} Trajet en train non trouvé + */ @Get(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: TrainEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async findOne(@Param('id') id: string): Promise { const train = await this.trainsService.findOne(id) if (!train) @@ -49,34 +62,45 @@ export class TrainsController { return new TrainEntity(train) } + /** + * Modification d'un trajet en train manuellement + * + * @throws {400} Erreurs dans le formulaire de création + * @throws {401} Non authentifié⋅e + * @throws {404} Trajet en train non trouvé + */ @Patch(':id') @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: TrainEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) async update(@Param('id') id: string, @Body() updateTrainDto: UpdateTrainDto) { return await this.trainsService.update(id, updateTrainDto) } + /** + * Suppression d'un trajet en train + * + * @throws {401} Non authentifié⋅e + * @throws {404} Trajet en train non trouvé + */ @Delete(':id') @HttpCode(204) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({ type: TrainEntity }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiNotFoundResponse({ description: "Objet non trouvé" }) + @ApiNoContentResponse({ description: "Le trajet en train a bien été supprimé" }) async remove(@Param('id') id: string) { await this.trainsService.remove(id) } + /** + * Importation d'un trajet en train à partir de Rail Planner + * + * @throws {401} Non authentifié⋅e + * @throws {422} Le voyage Interrail à importer contient plusieurs trajets ou plusieurs trains + */ @Post("/import") @HttpCode(201) @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiCreatedResponse({ type: TrainEntity, description: "Train importé avec succès" }) - @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) - @ApiUnprocessableEntityResponse({ description: "Le voyage Interrail à importer contient plusieurs trajets ou plusieurs trains" }) async import(@Req() request: AuthenticatedRequest, @Body() importTrainDto: ImportTrainDto): Promise { const train = await this.trainsService.import(request.user, importTrainDto) return new TrainEntity(train) diff --git a/server/src/trains/trains.service.ts b/server/src/trains/trains.service.ts index eafce5a..d7a0ef3 100644 --- a/server/src/trains/trains.service.ts +++ b/server/src/trains/trains.service.ts @@ -1,4 +1,4 @@ -import { Injectable, UnprocessableEntityException } from '@nestjs/common' +import { Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common' import { CreateTrainDto } from './dto/create-train.dto' import { UpdateTrainDto } from './dto/update-train.dto' import { PrismaService } from 'src/prisma/prisma.service' @@ -37,6 +37,8 @@ export class TrainsService { } async update(id: string, updateTrainDto: UpdateTrainDto): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Le train à modifier n'existe pas avec l'identifiant ${id}`) return await this.prisma.trainTrip.update({ where: { id }, data: updateTrainDto, @@ -44,6 +46,8 @@ export class TrainsService { } async remove(id: string): Promise { + if (!this.findOne(id)) + throw new NotFoundException(`Le train à supprimer n'existe pas avec l'identifiant ${id}`) return await this.prisma.trainTrip.delete({ where: { id }, })