Compare commits

...

4 Commits

17 changed files with 360 additions and 146 deletions

View File

@ -3,6 +3,14 @@
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src", "sourceRoot": "src",
"compilerOptions": { "compilerOptions": {
"deleteOutDir": true "deleteOutDir": true,
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"introspectComments": true
}
}
]
} }
} }

View File

@ -1,6 +1,6 @@
import { Body, Controller, Post } from '@nestjs/common' import { Body, Controller, Post } from '@nestjs/common'
import { AuthService } from './auth.service' import { AuthService } from './auth.service'
import { ApiOkResponse, ApiTags } from '@nestjs/swagger' import { ApiTags } from '@nestjs/swagger'
import { AuthEntity } from './entity/auth.entity' import { AuthEntity } from './entity/auth.entity'
import { LoginDto } from './dto/login.dto' import { LoginDto } from './dto/login.dto'
@ -9,9 +9,13 @@ import { LoginDto } from './dto/login.dto'
export class AuthController { export class AuthController {
constructor(private readonly authService: AuthService) {} 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') @Post('login')
@ApiOkResponse({ type: AuthEntity }) async login(@Body() { name, password }: LoginDto): Promise<AuthEntity> {
login(@Body() { name, password }: LoginDto) { return await this.authService.login(name, password)
return this.authService.login(name, password)
} }
} }

View File

@ -1,14 +1,9 @@
import { ApiProperty } from '@nestjs/swagger' import { IsNotEmpty } from 'class-validator'
import { IsNotEmpty, IsString } from 'class-validator'
export class LoginDto { export class LoginDto {
@IsString()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty()
name: string name: string
@IsString()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty()
password: string password: string
} }

View File

@ -1,6 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'
export class AuthEntity { export class AuthEntity {
@ApiProperty() /**
* Jeton d'accès à l'API, valable 12h.
*/
accessToken: string accessToken: string
} }

View File

@ -1,7 +1,7 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe, HttpCode, UseGuards, Req, Query, NotFoundException } from '@nestjs/common' import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe, HttpCode, UseGuards, Req, Query, NotFoundException } from '@nestjs/common'
import { ChallengeActionsService } from './challenge-actions.service' import { ChallengeActionsService } from './challenge-actions.service'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger'
import { ChallengeActionEntity } from './entities/challenge-action.entity' import { ChallengeActionEntity } from './entities/challenge-action.entity'
import { CreateChallengeActionDto } from './dto/create-challenge-action.dto' import { CreateChallengeActionDto } from './dto/create-challenge-action.dto'
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
@ -15,36 +15,44 @@ import { EndChallengeActionDto } from './dto/end-challenge-action.dto'
export class ChallengeActionsController { export class ChallengeActionsController {
constructor(private readonly challengeActionsService: ChallengeActionsService) {} 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() @Post()
@HttpCode(201) @HttpCode(201)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiCreatedResponse({ type: ChallengeActionEntity, description: "Objet créé avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async create(@Req() request: AuthenticatedRequest, @Body() createChallengeActionDto: CreateChallengeActionDto): Promise<ChallengeActionEntity> { async create(@Req() request: AuthenticatedRequest, @Body() createChallengeActionDto: CreateChallengeActionDto): Promise<ChallengeActionEntity> {
const challenge = await this.challengeActionsService.create(request.user, createChallengeActionDto) const challenge = await this.challengeActionsService.create(request.user, createChallengeActionDto)
return new ChallengeActionEntity(challenge) return new ChallengeActionEntity(challenge)
} }
/**
* Recherche d'actions de défi
*
* @throws {401} Non authentifiée
*/
@Get() @Get()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponsePaginated(ChallengeActionEntity) @ApiOkResponsePaginated(ChallengeActionEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination: QueryPaginationDto, @Query() filterChallengeActions: FilterChallengeActionsDto): Promise<PaginateOutputDto<ChallengeActionEntity>> { async findAll(@Query() queryPagination: QueryPaginationDto, @Query() filterChallengeActions: FilterChallengeActionsDto): Promise<PaginateOutputDto<ChallengeActionEntity>> {
const [challengeActions, total] = await this.challengeActionsService.findAll(queryPagination, filterChallengeActions) const [challengeActions, total] = await this.challengeActionsService.findAll(queryPagination, filterChallengeActions)
return paginateOutput<ChallengeActionEntity>(challengeActions.map(challengeAction => new ChallengeActionEntity(challengeAction)), total, queryPagination) return paginateOutput<ChallengeActionEntity>(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') @Get(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeActionEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async findOne(@Param('id', ParseIntPipe) id: number): Promise<ChallengeActionEntity> { async findOne(@Param('id', ParseIntPipe) id: number): Promise<ChallengeActionEntity> {
const challenge = await this.challengeActionsService.findOne(id) const challenge = await this.challengeActionsService.findOne(id)
if (!challenge) if (!challenge)
@ -52,36 +60,44 @@ export class ChallengeActionsController {
return new ChallengeActionEntity(challenge) 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') @Patch(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeActionEntity }) async update(@Param('id', ParseIntPipe) id: number, @Body() updateChallengeActionDto: UpdateChallengeActionDto): Promise<ChallengeActionEntity> {
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async update(@Param('id', ParseIntPipe) id: number, @Body() updateChallengeActionDto: UpdateChallengeActionDto) {
return await this.challengeActionsService.update(id, updateChallengeActionDto) 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') @Delete(':id')
@HttpCode(204) @HttpCode(204)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeActionEntity }) @ApiNoContentResponse({ description: "Action de défi supprimée avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) async remove(@Param('id', ParseIntPipe) id: number): Promise<void> {
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async remove(@Param('id', ParseIntPipe) id: number) {
await this.challengeActionsService.remove(id) 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') @Post('/end-current')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeActionEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async endCurrent(@Req() request: AuthenticatedRequest, @Body() { success }: EndChallengeActionDto): Promise<ChallengeActionEntity> { async endCurrent(@Req() request: AuthenticatedRequest, @Body() { success }: EndChallengeActionDto): Promise<ChallengeActionEntity> {
const challengeAction = await this.challengeActionsService.endCurrentChallenge(request.user, success) const challengeAction = await this.challengeActionsService.endCurrentChallenge(request.user, success)
return new ChallengeActionEntity(challengeAction) return new ChallengeActionEntity(challengeAction)

View File

@ -1,4 +1,4 @@
import { Injectable, NotAcceptableException } from '@nestjs/common' import { BadRequestException, Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common'
import { CreateChallengeActionDto } from './dto/create-challenge-action.dto' import { CreateChallengeActionDto } from './dto/create-challenge-action.dto'
import { UpdateChallengeActionDto } from './dto/update-challenge-action.dto' import { UpdateChallengeActionDto } from './dto/update-challenge-action.dto'
import { ChallengeAction, Player } from '@prisma/client' import { ChallengeAction, Player } from '@prisma/client'
@ -35,6 +35,8 @@ export class ChallengeActionsService {
} }
async update(id: number, updateChallengeActionDto: UpdateChallengeActionDto): Promise<ChallengeAction> { async update(id: number, updateChallengeActionDto: UpdateChallengeActionDto): Promise<ChallengeAction> {
if (!this.findOne(id))
throw new NotFoundException(`Aucune action de défi trouvée avec l'identifiant ${id}`)
return await this.prisma.challengeAction.update({ return await this.prisma.challengeAction.update({
where: { id }, where: { id },
data: updateChallengeActionDto, data: updateChallengeActionDto,
@ -42,6 +44,8 @@ export class ChallengeActionsService {
} }
async remove(id: number): Promise<ChallengeAction> { async remove(id: number): Promise<ChallengeAction> {
if (!this.findOne(id))
throw new NotFoundException(`Aucune action de défi trouvée avec l'identifiant ${id}`)
return await this.prisma.challengeAction.delete({ return await this.prisma.challengeAction.delete({
where: { id }, where: { id },
}) })
@ -55,7 +59,7 @@ export class ChallengeActionsService {
} }
}) })
if (!challengeAction) if (!challengeAction)
throw new NotAcceptableException("Aucun défi n'est en cours") throw new BadRequestException("Aucun défi n'est en cours")
let data let data
const now = new Date() const now = new Date()
if (success) { if (success) {

View File

@ -3,7 +3,7 @@ import { ChallengesService } from './challenges.service'
import { CreateChallengeDto } from './dto/create-challenge.dto' import { CreateChallengeDto } from './dto/create-challenge.dto'
import { UpdateChallengeDto } from './dto/update-challenge.dto' import { UpdateChallengeDto } from './dto/update-challenge.dto'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger'
import { ChallengeEntity } from './entities/challenge.entity' import { ChallengeEntity } from './entities/challenge.entity'
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
@ -13,36 +13,44 @@ import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
export class ChallengesController { export class ChallengesController {
constructor(private readonly challengesService: ChallengesService) {} 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() @Post()
@HttpCode(201) @HttpCode(201)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiCreatedResponse({ type: ChallengeEntity, description: "Objet créé avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async create(@Body() createChallengeDto: CreateChallengeDto): Promise<ChallengeEntity> { async create(@Body() createChallengeDto: CreateChallengeDto): Promise<ChallengeEntity> {
const challenge = await this.challengesService.create(createChallengeDto) const challenge = await this.challengesService.create(createChallengeDto)
return new ChallengeEntity(challenge) return new ChallengeEntity(challenge)
} }
/**
* Recherche de défis
*
* @throws {401} Non authentifiée
*/
@Get() @Get()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponsePaginated(ChallengeEntity) @ApiOkResponsePaginated(ChallengeEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination?: QueryPaginationDto): Promise<PaginateOutputDto<ChallengeEntity>> { async findAll(@Query() queryPagination?: QueryPaginationDto): Promise<PaginateOutputDto<ChallengeEntity>> {
const [challenges, total] = await this.challengesService.findAll(queryPagination) const [challenges, total] = await this.challengesService.findAll(queryPagination)
return paginateOutput<ChallengeEntity>(challenges.map(challenge => new ChallengeEntity(challenge)), total, queryPagination) return paginateOutput<ChallengeEntity>(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') @Get(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async findOne(@Param('id', ParseIntPipe) id: number): Promise<ChallengeEntity> { async findOne(@Param('id', ParseIntPipe) id: number): Promise<ChallengeEntity> {
const challenge = await this.challengesService.findOne(id) const challenge = await this.challengesService.findOne(id)
if (!challenge) if (!challenge)
@ -50,36 +58,47 @@ export class ChallengesController {
return new ChallengeEntity(challenge) 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') @Patch(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async update(@Param('id', ParseIntPipe) id: number, @Body() updateChallengeDto: UpdateChallengeDto) { async update(@Param('id', ParseIntPipe) id: number, @Body() updateChallengeDto: UpdateChallengeDto) {
return await this.challengesService.update(id, 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') @Delete(':id')
@HttpCode(204) @HttpCode(204)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeEntity }) @ApiNoContentResponse({ description: "Le défi a bien été supprimé" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async remove(@Param('id', ParseIntPipe) id: number) { async remove(@Param('id', ParseIntPipe) id: number) {
await this.challengesService.remove(id) 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') @Post('/draw-random')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: ChallengeEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async drawRandom(@Req() request: AuthenticatedRequest): Promise<ChallengeEntity> { async drawRandom(@Req() request: AuthenticatedRequest): Promise<ChallengeEntity> {
const challenge = await this.challengesService.drawRandom(request.user) const challenge = await this.challengesService.drawRandom(request.user)
return new ChallengeEntity(challenge) return new ChallengeEntity(challenge)

View File

@ -1,4 +1,4 @@
import { Injectable, NotAcceptableException, NotFoundException } from '@nestjs/common' import { ConflictException, Injectable, NotFoundException } from '@nestjs/common'
import { CreateChallengeDto } from './dto/create-challenge.dto' import { CreateChallengeDto } from './dto/create-challenge.dto'
import { UpdateChallengeDto } from './dto/update-challenge.dto' import { UpdateChallengeDto } from './dto/update-challenge.dto'
import { Challenge, Player } from '@prisma/client' import { Challenge, Player } from '@prisma/client'
@ -38,6 +38,8 @@ export class ChallengesService {
} }
async update(id: number, updateChallengeDto: UpdateChallengeDto): Promise<Challenge> { async update(id: number, updateChallengeDto: UpdateChallengeDto): Promise<Challenge> {
if (!this.findOne(id))
throw new NotFoundException(`Aucun défi n'existe avec l'identifiant ${id}`)
return await this.prisma.challenge.update({ return await this.prisma.challenge.update({
where: { id }, where: { id },
data: updateChallengeDto, data: updateChallengeDto,
@ -48,6 +50,8 @@ export class ChallengesService {
} }
async remove(id: number): Promise<Challenge> { async remove(id: number): Promise<Challenge> {
if (!this.findOne(id))
throw new NotFoundException(`Aucun défi n'existe avec l'identifiant ${id}`)
return await this.prisma.challenge.delete({ return await this.prisma.challenge.delete({
where: { id }, where: { id },
include: { include: {
@ -64,7 +68,7 @@ export class ChallengesService {
} }
}) })
if (currentChallengeAction) if (currentChallengeAction)
throw new NotAcceptableException("Un défi est déjà en cours d'accomplissement") throw new ConflictException("Un défi est déjà en cours d'accomplissement")
const remaningChallenges = await this.prisma.challenge.count({ const remaningChallenges = await this.prisma.challenge.count({
where: { where: {
action: null, action: null,

View File

@ -1,19 +1,63 @@
import { Controller, Get, UseGuards } from '@nestjs/common' import { Controller, Delete, Get, HttpCode, Post, UseGuards } from '@nestjs/common'
import { GameService } from './game.service' import { GameService } from './game.service'
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ApiBearerAuth, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBearerAuth, ApiNoContentResponse, ApiOkResponse, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger'
import { GameEntity } from './entities/game.entity' import { GameEntity } from './entities/game.entity'
@Controller('game') @Controller('game')
export class GameController { export class GameController {
constructor(private readonly gameService: GameService) {} constructor(private readonly gameService: GameService) {}
/**
* Récupérer l'objet du jeu
*
* @throws {401} Non authentifiée
*/
@Get() @Get()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: GameEntity }) async find(): Promise<GameEntity> {
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
async find() {
return new GameEntity(await this.gameService.find()) return new GameEntity(await this.gameService.find())
} }
/**
* Démarrer le jeu
* @remarks Lance le jeu, tire au sort joueurse de départ et donne un solde initial pour acheter des trains. Le jeu ne doit pas être déjà démarré.
*
* @throws {401} Non authentifiée
* @throws {409} La partie est déjà démarrée
*/
@Post('/start')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
async start(): Promise<GameEntity> {
return new GameEntity(await this.gameService.start())
}
/**
* Arrêter le jeu
* @remarks Arrête le jeu (si déjà lancé), à n'utiliser qu'en fin de partie.
*
* @throws {401} Non authentifiée
* @throws {409} La partie n'est pas démarrée
*/
@Post('/stop')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
async stop(): Promise<GameEntity> {
return new GameEntity(await this.gameService.stop())
}
/**
* Réinitialiser le jeu
* @remarks Réinitialise toutes les données de jeu, supprimant les trains parcourus et les défis accomplis. À utiliser avec précaution.
*
* @throws {401} Non authentifiée
*/
@Delete('/reset')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
async reset(): Promise<GameEntity> {
return new GameEntity(await this.gameService.reset())
}
} }

View File

@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common' import { ConflictException, Injectable } from '@nestjs/common'
import { MoneyUpdateType } from '@prisma/client'
import { PrismaService } from 'src/prisma/prisma.service' import { PrismaService } from 'src/prisma/prisma.service'
@Injectable() @Injectable()
@ -8,4 +9,59 @@ export class GameService {
async find() { async find() {
return await this.prisma.game.findUnique({ where: { id: 1 } }) return await this.prisma.game.findUnique({ where: { id: 1 } })
} }
async start() {
const game = await this.find()
if (game.started)
throw new ConflictException("La partie a déjà démarré.")
const players = await this.prisma.player.findMany()
const alreadyStarted = game.currentRunnerId !== null
if (!alreadyStarted) {
for (const player of players) {
await this.prisma.moneyUpdate.create({
data: {
playerId: player.id,
amount: 2000,
reason: MoneyUpdateType.START,
}
})
}
}
const runnerId = alreadyStarted ? game.currentRunnerId : players[Math.trunc(players.length * Math.random())].id
return await this.prisma.game.update({
where: { id: 1 },
data: {
started: true,
currentRunnerId: runnerId,
},
})
}
async stop() {
const game = await this.find()
if (!game.started)
throw new ConflictException("La partie n'a pas encore démarré.")
return await this.prisma.game.update({
where: { id: 1 },
data: {
started: false,
},
})
}
async reset() {
await this.prisma.moneyUpdate.deleteMany()
await this.prisma.challengeAction.deleteMany()
await this.prisma.trainTrip.deleteMany()
await this.prisma.geolocation.deleteMany()
await this.prisma.player.updateMany({ data: { money: 0 } })
await this.prisma.game.update({
where: { id: 1 },
data: {
started: false,
currentRunnerId: null,
},
})
return await this.find()
}
} }

View File

@ -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 { GeolocationsService } from './geolocations.service'
import { CreateGeolocationDto } from './dto/create-geolocation.dto' import { CreateGeolocationDto } from './dto/create-geolocation.dto'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger'
import { GeolocationEntity } from './entities/geolocation.entity' import { GeolocationEntity } from './entities/geolocation.entity'
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
@ -13,36 +13,44 @@ import { PlayerFilterDto } from 'src/common/dto/player_filter.dto'
export class GeolocationsController { export class GeolocationsController {
constructor(private readonly geolocationsService: GeolocationsService) {} constructor(private readonly geolocationsService: GeolocationsService) {}
/**
* Ajout d'une géolocalisation pour joueurse connectée
*
* @throws {400} Erreurs dans le formulaire de création
* @throws {401} Non authentifiée
*/
@Post() @Post()
@HttpCode(201) @HttpCode(201)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiCreatedResponse({ type: GeolocationEntity, description: "Objet créé avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async create(@Req() request: AuthenticatedRequest, @Body() createGeolocationDto: CreateGeolocationDto): Promise<GeolocationEntity> { async create(@Req() request: AuthenticatedRequest, @Body() createGeolocationDto: CreateGeolocationDto): Promise<GeolocationEntity> {
const geolocation = await this.geolocationsService.create(request.user, createGeolocationDto) const geolocation = await this.geolocationsService.create(request.user, createGeolocationDto)
return new GeolocationEntity(geolocation) return new GeolocationEntity(geolocation)
} }
/**
* Recherche de géolocalisations
*
* @throws {401} Non authentifiée
*/
@Get() @Get()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponsePaginated(GeolocationEntity) @ApiOkResponsePaginated(GeolocationEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination?: QueryPaginationDto, @Query() playerFilter?: PlayerFilterDto): Promise<PaginateOutputDto<GeolocationEntity>> { async findAll(@Query() queryPagination?: QueryPaginationDto, @Query() playerFilter?: PlayerFilterDto): Promise<PaginateOutputDto<GeolocationEntity>> {
const [geolocations, total] = await this.geolocationsService.findAll(queryPagination, playerFilter) const [geolocations, total] = await this.geolocationsService.findAll(queryPagination, playerFilter)
return paginateOutput<GeolocationEntity>(geolocations.map(geolocation => new GeolocationEntity(geolocation)), total, queryPagination) return paginateOutput<GeolocationEntity>(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') @Get(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: GeolocationEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async findOne(@Param('id', ParseIntPipe) id: number): Promise<GeolocationEntity> { async findOne(@Param('id', ParseIntPipe) id: number): Promise<GeolocationEntity> {
const geolocation = await this.geolocationsService.findOne(id) const geolocation = await this.geolocationsService.findOne(id)
if (!geolocation) if (!geolocation)
@ -50,13 +58,15 @@ export class GeolocationsController {
return new GeolocationEntity(geolocation) 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') @Get('/last-location/:playerId')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: GeolocationEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Aucune localisation trouvée" })
async findLastLocation(@Param('playerId', ParseIntPipe) playerId: number): Promise<GeolocationEntity> { async findLastLocation(@Param('playerId', ParseIntPipe) playerId: number): Promise<GeolocationEntity> {
const geolocation = await this.geolocationsService.findLastLocation(playerId) const geolocation = await this.geolocationsService.findLastLocation(playerId)
if (!geolocation) if (!geolocation)
@ -64,14 +74,17 @@ export class GeolocationsController {
return new GeolocationEntity(geolocation) return new GeolocationEntity(geolocation)
} }
/**
* Suppression d'une localisation
*
* @throws {401} Non authentifiée
* @throws {404} Géolocalisation non trouvée
*/
@Delete(':id') @Delete(':id')
@HttpCode(204) @HttpCode(204)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiNoContentResponse({ description: "Objet supprimé avec succès" }) @ApiNoContentResponse({ description: "La géolocalisation a bien été supprimée" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async remove(@Param('id', ParseIntPipe) id: number): Promise<void> { async remove(@Param('id', ParseIntPipe) id: number): Promise<void> {
await this.geolocationsService.remove(+id) await this.geolocationsService.remove(+id)
} }

View File

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common' import { Injectable, NotFoundException } from '@nestjs/common'
import { CreateGeolocationDto } from './dto/create-geolocation.dto' import { CreateGeolocationDto } from './dto/create-geolocation.dto'
import { PrismaService } from 'src/prisma/prisma.service' import { PrismaService } from 'src/prisma/prisma.service'
import { Geolocation, Player, Prisma } from '@prisma/client' import { Geolocation, Player, Prisma } from '@prisma/client'
@ -43,6 +43,8 @@ export class GeolocationsService {
async remove(id: number): Promise<Geolocation> { async remove(id: number): Promise<Geolocation> {
if (!this.findOne(id))
throw new NotFoundException(`Aucune géolocalisation n'existe avec l'identifiant ${id}`)
return await this.prisma.geolocation.delete({ where: { id } }) return await this.prisma.geolocation.delete({ where: { id } })
} }
} }

View File

@ -3,7 +3,7 @@ import { MoneyUpdatesService } from './money-updates.service'
import { CreateMoneyUpdateDto } from './dto/create-money-update.dto' import { CreateMoneyUpdateDto } from './dto/create-money-update.dto'
import { UpdateMoneyUpdateDto } from './dto/update-money-update.dto' import { UpdateMoneyUpdateDto } from './dto/update-money-update.dto'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger'
import { MoneyUpdateEntity } from './entities/money-update.entity' import { MoneyUpdateEntity } from './entities/money-update.entity'
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
@ -14,36 +14,44 @@ import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
export class MoneyUpdatesController { export class MoneyUpdatesController {
constructor(private readonly moneyUpdatesService: MoneyUpdatesService) {} 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() @Post()
@HttpCode(201) @HttpCode(201)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiCreatedResponse({ type: MoneyUpdateEntity, description: "Objet créé avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async create(@Req() request: AuthenticatedRequest, @Body() createMoneyUpdateDto: CreateMoneyUpdateDto): Promise<MoneyUpdateEntity> { async create(@Req() request: AuthenticatedRequest, @Body() createMoneyUpdateDto: CreateMoneyUpdateDto): Promise<MoneyUpdateEntity> {
const moneyUpdate = await this.moneyUpdatesService.create(request.user, createMoneyUpdateDto) const moneyUpdate = await this.moneyUpdatesService.create(request.user, createMoneyUpdateDto)
return new MoneyUpdateEntity(moneyUpdate) return new MoneyUpdateEntity(moneyUpdate)
} }
/**
* Recherche de modifications de solde
*
* @throws {401} Non authentifiée
*/
@Get() @Get()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponsePaginated(MoneyUpdateEntity) @ApiOkResponsePaginated(MoneyUpdateEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise<PaginateOutputDto<MoneyUpdateEntity>> { async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise<PaginateOutputDto<MoneyUpdateEntity>> {
const [trains, total] = await this.moneyUpdatesService.findAll(queryPagination, playerFilter) const [trains, total] = await this.moneyUpdatesService.findAll(queryPagination, playerFilter)
return paginateOutput<MoneyUpdateEntity>(trains.map(train => new MoneyUpdateEntity(train)), total, queryPagination) return paginateOutput<MoneyUpdateEntity>(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') @Get(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: MoneyUpdateEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async findOne(@Param('id', ParseIntPipe) id: number): Promise<MoneyUpdateEntity> { async findOne(@Param('id', ParseIntPipe) id: number): Promise<MoneyUpdateEntity> {
const train = await this.moneyUpdatesService.findOne(id) const train = await this.moneyUpdatesService.findOne(id)
if (!train) if (!train)
@ -51,25 +59,31 @@ export class MoneyUpdatesController {
return new MoneyUpdateEntity(train) 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') @Patch(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: MoneyUpdateEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async update(@Param('id', ParseIntPipe) id: number, @Body() updateMoneyUpdateDto: UpdateMoneyUpdateDto) { async update(@Param('id', ParseIntPipe) id: number, @Body() updateMoneyUpdateDto: UpdateMoneyUpdateDto) {
return await this.moneyUpdatesService.update(id, 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') @Delete(':id')
@HttpCode(204) @HttpCode(204)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiNoContentResponse({ description: "La modification de solde a bien été supprimée" })
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: MoneyUpdateEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async remove(@Param('id', ParseIntPipe) id: number) { async remove(@Param('id', ParseIntPipe) id: number) {
await this.moneyUpdatesService.remove(id) await this.moneyUpdatesService.remove(id)
} }

View File

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common' import { Injectable, NotFoundException } from '@nestjs/common'
import { CreateMoneyUpdateDto } from './dto/create-money-update.dto' import { CreateMoneyUpdateDto } from './dto/create-money-update.dto'
import { UpdateMoneyUpdateDto } from './dto/update-money-update.dto' import { UpdateMoneyUpdateDto } from './dto/update-money-update.dto'
import { PrismaService } from 'src/prisma/prisma.service' import { PrismaService } from 'src/prisma/prisma.service'
@ -39,6 +39,8 @@ export class MoneyUpdatesService {
} }
async update(id: number, updateMoneyUpdateDto: UpdateMoneyUpdateDto): Promise<MoneyUpdate> { async update(id: number, updateMoneyUpdateDto: UpdateMoneyUpdateDto): Promise<MoneyUpdate> {
if (!this.findOne(id))
throw new NotFoundException(`Aucune modification de solde n'existe avec l'identifiant ${id}`)
return await this.prisma.moneyUpdate.update({ return await this.prisma.moneyUpdate.update({
where: { id }, where: { id },
data: updateMoneyUpdateDto, data: updateMoneyUpdateDto,
@ -46,6 +48,8 @@ export class MoneyUpdatesService {
} }
async remove(id: number): Promise<MoneyUpdate> { async remove(id: number): Promise<MoneyUpdate> {
if (!this.findOne(id))
throw new NotFoundException(`Aucune modification de solde n'existe avec l'identifiant ${id}`)
return await this.prisma.moneyUpdate.delete({ return await this.prisma.moneyUpdate.delete({
where: { id }, where: { id },
}) })

View File

@ -1,6 +1,6 @@
import { Body, Controller, Get, HttpCode, NotFoundException, Param, ParseIntPipe, Patch, Query, Req, UseGuards } from '@nestjs/common' import { Body, Controller, Get, HttpCode, NotFoundException, Param, ParseIntPipe, Patch, Query, Req, UseGuards } from '@nestjs/common'
import { PlayersService } from './players.service' import { PlayersService } from './players.service'
import { ApiBadRequestResponse, ApiBearerAuth, ApiForbiddenResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBearerAuth, ApiNoContentResponse } from '@nestjs/swagger'
import { PlayerEntity } from './entities/player.entity' import { PlayerEntity } from './entities/player.entity'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { UpdatePasswordDto } from './dto/player_password.dto' import { UpdatePasswordDto } from './dto/player_password.dto'
@ -12,40 +12,48 @@ import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
export class PlayersController { export class PlayersController {
constructor(private readonly playersService: PlayersService) {} constructor(private readonly playersService: PlayersService) {}
/**
* Récupération de toustes les joueurses
*
* @throws {401} Non authentifiée
*/
@Get() @Get()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponsePaginated(PlayerEntity) @ApiOkResponsePaginated(PlayerEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination?: QueryPaginationDto): Promise<PaginateOutputDto<PlayerEntity>> { async findAll(@Query() queryPagination?: QueryPaginationDto): Promise<PaginateOutputDto<PlayerEntity>> {
const [players, total] = await this.playersService.findAll(queryPagination) const [players, total] = await this.playersService.findAll(queryPagination)
return paginateOutput<PlayerEntity>(players.map(player => new PlayerEntity(player)), total, queryPagination) return paginateOutput<PlayerEntity>(players.map(player => new PlayerEntity(player)), total, queryPagination)
} }
/**
* Récupération d'une joueurse par son identifiant
*
* @throws {401} Non authentifiée
* @throws {404} Joueurse non trouvée
*/
@Get(':id') @Get(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: PlayerEntity }) async findOne(@Param('id', ParseIntPipe) id: number): Promise<PlayerEntity> {
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Joueur⋅se non trouvé⋅e" })
async findOne(@Param('id', ParseIntPipe) id: number) {
const player = await this.playersService.findOne(id) const player = await this.playersService.findOne(id)
if (!player) if (!player)
throw new NotFoundException(`Læ joueur⋅se avec l'identifiant ${id} n'existe pas`) throw new NotFoundException(`Læ joueur⋅se avec l'identifiant ${id} n'existe pas`)
return new PlayerEntity(player) return new PlayerEntity(player)
} }
/**
* Modification du mot de passe
*
* @throws {400} Mot de passe invalide
* @throws {401} Non authentifiée
*/
@Patch('/update-password') @Patch('/update-password')
@HttpCode(204) @HttpCode(204)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiNoContentResponse({description: "Le mot de passe a bien été modifié."}) @ApiNoContentResponse({ description: "Le mot de passe a bien été modifié." })
@ApiBadRequestResponse({description: "Erreur dans la saisie du nouveau mot de passe."}) async updatePassword(@Req() request: AuthenticatedRequest, @Body() body: UpdatePasswordDto): Promise<void> {
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async updatePassword(@Req() request: AuthenticatedRequest, @Body() body: UpdatePasswordDto) {
await this.playersService.updatePassword(request.user, body) await this.playersService.updatePassword(request.user, body)
} }
} }

View File

@ -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 { TrainsService } from './trains.service'
import { CreateTrainDto } from './dto/create-train.dto' import { CreateTrainDto } from './dto/create-train.dto'
import { UpdateTrainDto } from './dto/update-train.dto' import { UpdateTrainDto } from './dto/update-train.dto'
import { TrainEntity } from './entities/train.entity' import { TrainEntity } from './entities/train.entity'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBearerAuth, ApiCreatedResponse, ApiNoContentResponse, ApiUnauthorizedResponse } from '@nestjs/swagger'
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils' import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto' import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
@ -15,36 +15,46 @@ import { PlayerFilterDto } from 'src/common/dto/player_filter.dto'
export class TrainsController { export class TrainsController {
constructor(private readonly trainsService: TrainsService) {} 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() @Post()
@HttpCode(201) @HttpCode(201)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @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" }) @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async create(@Body() createTrainDto: CreateTrainDto): Promise<TrainEntity> { async create(@Body() createTrainDto: CreateTrainDto): Promise<TrainEntity> {
const train = await this.trainsService.create(createTrainDto) const train = await this.trainsService.create(createTrainDto)
return new TrainEntity(train) return new TrainEntity(train)
} }
/**
* Recherche de trajets en train
*
* @throws {401} Non authentifiée
*/
@Get() @Get()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponsePaginated(TrainEntity) @ApiOkResponsePaginated(TrainEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise<PaginateOutputDto<TrainEntity>> { async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise<PaginateOutputDto<TrainEntity>> {
const [trains, total] = await this.trainsService.findAll(queryPagination, playerFilter) const [trains, total] = await this.trainsService.findAll(queryPagination, playerFilter)
return paginateOutput<TrainEntity>(trains.map(train => new TrainEntity(train)), total, queryPagination) return paginateOutput<TrainEntity>(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') @Get(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: TrainEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async findOne(@Param('id') id: string): Promise<TrainEntity> { async findOne(@Param('id') id: string): Promise<TrainEntity> {
const train = await this.trainsService.findOne(id) const train = await this.trainsService.findOne(id)
if (!train) if (!train)
@ -52,36 +62,45 @@ export class TrainsController {
return new TrainEntity(train) 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') @Patch(':id')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: TrainEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async update(@Param('id') id: string, @Body() updateTrainDto: UpdateTrainDto) { async update(@Param('id') id: string, @Body() updateTrainDto: UpdateTrainDto) {
return await this.trainsService.update(id, 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') @Delete(':id')
@HttpCode(204) @HttpCode(204)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOkResponse({ type: TrainEntity }) @ApiNoContentResponse({ description: "Le trajet en train a bien été supprimé" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async remove(@Param('id') id: string) { async remove(@Param('id') id: string) {
await this.trainsService.remove(id) 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") @Post("/import")
@HttpCode(201) @HttpCode(201)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiCreatedResponse({ type: TrainEntity, description: "Train importé avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async import(@Req() request: AuthenticatedRequest, @Body() importTrainDto: ImportTrainDto): Promise<TrainEntity> { async import(@Req() request: AuthenticatedRequest, @Body() importTrainDto: ImportTrainDto): Promise<TrainEntity> {
const train = await this.trainsService.import(request.user, importTrainDto) const train = await this.trainsService.import(request.user, importTrainDto)
return new TrainEntity(train) return new TrainEntity(train)

View File

@ -1,4 +1,4 @@
import { Injectable, NotAcceptableException } from '@nestjs/common' import { Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common'
import { CreateTrainDto } from './dto/create-train.dto' import { CreateTrainDto } from './dto/create-train.dto'
import { UpdateTrainDto } from './dto/update-train.dto' import { UpdateTrainDto } from './dto/update-train.dto'
import { PrismaService } from 'src/prisma/prisma.service' import { PrismaService } from 'src/prisma/prisma.service'
@ -37,6 +37,8 @@ export class TrainsService {
} }
async update(id: string, updateTrainDto: UpdateTrainDto): Promise<TrainTrip> { async update(id: string, updateTrainDto: UpdateTrainDto): Promise<TrainTrip> {
if (!this.findOne(id))
throw new NotFoundException(`Le train à modifier n'existe pas avec l'identifiant ${id}`)
return await this.prisma.trainTrip.update({ return await this.prisma.trainTrip.update({
where: { id }, where: { id },
data: updateTrainDto, data: updateTrainDto,
@ -44,6 +46,8 @@ export class TrainsService {
} }
async remove(id: string): Promise<TrainTrip> { async remove(id: string): Promise<TrainTrip> {
if (!this.findOne(id))
throw new NotFoundException(`Le train à supprimer n'existe pas avec l'identifiant ${id}`)
return await this.prisma.trainTrip.delete({ return await this.prisma.trainTrip.delete({
where: { id }, where: { id },
}) })
@ -53,10 +57,10 @@ export class TrainsService {
const interrailResult: InterrailJourney = await fetch(`https://3uiwjsimnh.execute-api.eu-central-1.amazonaws.com/Prod/journey-import?id=${trainId}`) const interrailResult: InterrailJourney = await fetch(`https://3uiwjsimnh.execute-api.eu-central-1.amazonaws.com/Prod/journey-import?id=${trainId}`)
.then(data => data.json()) .then(data => data.json())
if (interrailResult.data.travels.length !== 1) if (interrailResult.data.travels.length !== 1)
throw new NotAcceptableException(`Ce voyage contient ${interrailResult.data.travels.length} trajets. Merci d'ajouter les trajets un à un.`) throw new UnprocessableEntityException(`Ce voyage contient ${interrailResult.data.travels.length} trajets. Merci d'ajouter les trajets un à un.`)
const travel = interrailResult.data.travels[0] const travel = interrailResult.data.travels[0]
if (travel.legs.length !== 1) if (travel.legs.length !== 1)
throw new NotAcceptableException(`Ce trajet contient ${travel.legs.length} trains. Merci d'ajouter les trajets un à un.`) throw new UnprocessableEntityException(`Ce trajet contient ${travel.legs.length} trains. Merci d'ajouter les trajets un à un.`)
const leg = travel.legs[0] const leg = travel.legs[0]
const travelInfoJson: InterrailTravelInfo = JSON.parse(travel.infoJson) const travelInfoJson: InterrailTravelInfo = JSON.parse(travel.infoJson)