diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 204bf33..03e82cb 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -6,10 +6,11 @@ import { AuthModule } from './auth/auth.module' import { GeolocationsModule } from './geolocations/geolocations.module' import { ChallengesModule } from './challenges/challenges.module' import { ChallengeActionsModule } from './challenge-actions/challenge-actions.module' -import { TrainsModule } from './trains/trains.module'; +import { TrainsModule } from './trains/trains.module' +import { MoneyUpdatesModule } from './money-updates/money-updates.module' @Module({ - imports: [PrismaModule, UsersModule, AuthModule, GeolocationsModule, ChallengesModule, ChallengeActionsModule, TrainsModule], + imports: [PrismaModule, UsersModule, AuthModule, GeolocationsModule, ChallengesModule, ChallengeActionsModule, TrainsModule, MoneyUpdatesModule], providers: [PrismaService], }) export class AppModule {} diff --git a/server/src/money-updates/dto/create-money-update.dto.ts b/server/src/money-updates/dto/create-money-update.dto.ts new file mode 100644 index 0000000..32aeb79 --- /dev/null +++ b/server/src/money-updates/dto/create-money-update.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from "@nestjs/swagger" +import { MoneyUpdateType } from "@prisma/client" +import { Type } from "class-transformer" +import { IsEnum, IsInt, IsOptional, IsUUID } from "class-validator" + +export class CreateMoneyUpdateDto { + @IsInt() + @Type(() => Number) + @ApiProperty({ description: "Solde avant modification" }) + before: number + + @IsInt() + @Type(() => Number) + @ApiProperty({ description: "Solde après modification" }) + after: number + + @IsEnum(MoneyUpdateType) + @ApiProperty({ description: "Type de modification de solde" }) + reason: MoneyUpdateType + + @IsOptional() + @IsInt() + @Type(() => Number) + @ApiProperty({ description: "Identifiant de la réalisation de défi, si la mise à jour est liée à un défi", nullable: true }) + actionId?: number + + @IsOptional() + @IsUUID() + @ApiProperty({ description: "Identifiant du trajet acheté, si la mise à jour est liée à la réservation d'un train", nullable: true }) + tripId?: string +} diff --git a/server/src/money-updates/dto/update-money-update.dto.ts b/server/src/money-updates/dto/update-money-update.dto.ts new file mode 100644 index 0000000..33a43fe --- /dev/null +++ b/server/src/money-updates/dto/update-money-update.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger' +import { CreateMoneyUpdateDto } from './create-money-update.dto' + +export class UpdateMoneyUpdateDto extends PartialType(CreateMoneyUpdateDto) {} diff --git a/server/src/money-updates/entities/money-update.entity.ts b/server/src/money-updates/entities/money-update.entity.ts new file mode 100644 index 0000000..7fb734d --- /dev/null +++ b/server/src/money-updates/entities/money-update.entity.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from "@nestjs/swagger" +import { MoneyUpdate, MoneyUpdateType } from "@prisma/client" +import { IsOptional } from "class-validator" + +export class MoneyUpdateEntity implements MoneyUpdate { + constructor (partial: Partial) { + Object.assign(this, partial) + } + + @ApiProperty({ description: "Identifiant unique de la mise à jour de solde" }) + id: number + + @ApiProperty({ description: "Utilisateur⋅rice concerné⋅e par la mise à jour de solde" }) + userId: number + + @ApiProperty({ description: "Solde avant modification" }) + before: number + + @ApiProperty({ description: "Solde après modification" }) + after: number + + @ApiProperty({ description: "Type de modification de solde" }) + reason: MoneyUpdateType + + @ApiProperty({ description: "Identifiant de la réalisation de défi, si la mise à jour est liée à un défi", nullable: true }) + actionId: number + + @ApiProperty({ description: "Identifiant du trajet acheté, si la mise à jour est liée à la réservation d'un train", nullable: true }) + tripId: string +} diff --git a/server/src/money-updates/money-updates.controller.spec.ts b/server/src/money-updates/money-updates.controller.spec.ts new file mode 100644 index 0000000..214b0f6 --- /dev/null +++ b/server/src/money-updates/money-updates.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { MoneyUpdatesController } from './money-updates.controller' +import { MoneyUpdatesService } from './money-updates.service' + +describe('MoneyUpdatesController', () => { + let controller: MoneyUpdatesController + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [MoneyUpdatesController], + providers: [MoneyUpdatesService], + }).compile() + + controller = module.get(MoneyUpdatesController) + }) + + it('should be defined', () => { + expect(controller).toBeDefined() + }) +}) diff --git a/server/src/money-updates/money-updates.controller.ts b/server/src/money-updates/money-updates.controller.ts new file mode 100644 index 0000000..d39cca0 --- /dev/null +++ b/server/src/money-updates/money-updates.controller.ts @@ -0,0 +1,76 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, HttpCode, UseGuards, Query, NotFoundException, ParseIntPipe, Req } from '@nestjs/common' +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, ApiForbiddenResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } 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' +import { UserFilterDto } from 'src/common/dto/user_filter.dto' +import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto' + +@Controller('money-updates') +export class MoneyUpdatesController { + constructor(private readonly moneyUpdatesService: MoneyUpdatesService) {} + + @Post() + @HttpCode(201) + @UseGuards(JwtAuthGuard) + @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 { + const moneyUpdate = await this.moneyUpdatesService.create(request.user, createMoneyUpdateDto) + return new MoneyUpdateEntity(moneyUpdate) + } + + @Get() + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOkResponsePaginated(MoneyUpdateEntity) + @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) + @ApiForbiddenResponse({ description: "Permission refusée" }) + async findAll(@Query() queryPagination: QueryPaginationDto, @Query() userFilter: UserFilterDto): Promise> { + const [trains, total] = await this.moneyUpdatesService.findAll(queryPagination, userFilter) + return paginateOutput(trains.map(train => new MoneyUpdateEntity(train)), total, queryPagination) + } + + @Get(':id') + @UseGuards(JwtAuthGuard) + @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 { + const train = await this.moneyUpdatesService.findOne(id) + if (!train) + throw new NotFoundException(`Trajet en train inexistant avec l'identifiant ${id}`) + return new MoneyUpdateEntity(train) + } + + @Patch(':id') + @UseGuards(JwtAuthGuard) + @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) { + return await this.moneyUpdatesService.update(id, updateMoneyUpdateDto) + } + + @Delete(':id') + @HttpCode(204) + @UseGuards(JwtAuthGuard) + @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) { + await this.moneyUpdatesService.remove(id) + } +} diff --git a/server/src/money-updates/money-updates.module.ts b/server/src/money-updates/money-updates.module.ts new file mode 100644 index 0000000..e100817 --- /dev/null +++ b/server/src/money-updates/money-updates.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common' +import { MoneyUpdatesService } from './money-updates.service' +import { MoneyUpdatesController } from './money-updates.controller' +import { PrismaModule } from 'src/prisma/prisma.module' + +@Module({ + controllers: [MoneyUpdatesController], + providers: [MoneyUpdatesService], + imports: [PrismaModule], +}) +export class MoneyUpdatesModule {} diff --git a/server/src/money-updates/money-updates.service.spec.ts b/server/src/money-updates/money-updates.service.spec.ts new file mode 100644 index 0000000..d6b8bf2 --- /dev/null +++ b/server/src/money-updates/money-updates.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { MoneyUpdatesService } from './money-updates.service' + +describe('MoneyUpdatesService', () => { + let service: MoneyUpdatesService + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [MoneyUpdatesService], + }).compile() + + service = module.get(MoneyUpdatesService) + }) + + it('should be defined', () => { + expect(service).toBeDefined() + }) +}) diff --git a/server/src/money-updates/money-updates.service.ts b/server/src/money-updates/money-updates.service.ts new file mode 100644 index 0000000..2070d40 --- /dev/null +++ b/server/src/money-updates/money-updates.service.ts @@ -0,0 +1,53 @@ +import { Injectable } 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' +import { MoneyUpdate, User } from '@prisma/client' +import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto' +import { UserFilterDto } from 'src/common/dto/user_filter.dto' +import { paginate } from 'src/common/utils/pagination.utils' + +@Injectable() +export class MoneyUpdatesService { + constructor(private prisma: PrismaService) { } + + async create(user: User, createMoneyUpdateDto: CreateMoneyUpdateDto): Promise { + return await this.prisma.moneyUpdate.create({ + data: { + ...createMoneyUpdateDto, + userId: user.id, + } + }) + } + + async findAll(queryPagination: QueryPaginationDto, userFilter: UserFilterDto): Promise<[MoneyUpdate[], number]> { + return [ + await this.prisma.moneyUpdate.findMany({ + where: userFilter, + ...paginate(queryPagination), + }), + await this.prisma.moneyUpdate.count({ + where: userFilter, + }), + ] + } + + async findOne(id: number): Promise { + return await this.prisma.moneyUpdate.findUnique({ + where: { id }, + }) + } + + async update(id: number, updateMoneyUpdateDto: UpdateMoneyUpdateDto): Promise { + return await this.prisma.moneyUpdate.update({ + where: { id }, + data: updateMoneyUpdateDto, + }) + } + + async remove(id: number): Promise { + return await this.prisma.moneyUpdate.delete({ + where: { id }, + }) + } +} diff --git a/server/src/trains/dto/update-train.dto.ts b/server/src/trains/dto/update-train.dto.ts index 0626856..7395b48 100644 --- a/server/src/trains/dto/update-train.dto.ts +++ b/server/src/trains/dto/update-train.dto.ts @@ -1,4 +1,4 @@ -import { PartialType } from '@nestjs/swagger'; -import { CreateTrainDto } from './create-train.dto'; +import { PartialType } from '@nestjs/swagger' +import { CreateTrainDto } from './create-train.dto' export class UpdateTrainDto extends PartialType(CreateTrainDto) {} diff --git a/server/src/trains/trains.controller.ts b/server/src/trains/trains.controller.ts index 5988e0f..33d733e 100644 --- a/server/src/trains/trains.controller.ts +++ b/server/src/trains/trains.controller.ts @@ -59,8 +59,8 @@ export class TrainsController { @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) @ApiForbiddenResponse({ description: "Permission refusée" }) @ApiNotFoundResponse({ description: "Objet non trouvé" }) - async update(@Param('id') id: string, @Body() updateChallengeDto: UpdateTrainDto) { - return await this.trainsService.update(id, updateChallengeDto) + async update(@Param('id') id: string, @Body() updateTrainDto: UpdateTrainDto) { + return await this.trainsService.update(id, updateTrainDto) } @Delete(':id')