User => Player

This commit is contained in:
Emmy D'Anello 2024-12-08 13:41:37 +01:00
parent c6da328023
commit 65576fc5b1
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
39 changed files with 193 additions and 240 deletions

View File

@ -1,14 +0,0 @@
/*
Warnings:
- Added the required column `accuracy` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
- Added the required column `altitude` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
- Added the required column `altitudeAccuracy` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
- Added the required column `speed` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Geolocation" ADD COLUMN "accuracy" DOUBLE PRECISION NOT NULL,
ADD COLUMN "altitude" DOUBLE PRECISION NOT NULL,
ADD COLUMN "altitudeAccuracy" DOUBLE PRECISION NOT NULL,
ADD COLUMN "speed" DOUBLE PRECISION NOT NULL;

View File

@ -1,5 +0,0 @@
-- AlterTable
ALTER TABLE "ChallengeAction" ADD COLUMN "end" TIMESTAMP(3),
ADD COLUMN "penaltyEnd" TIMESTAMP(3),
ADD COLUMN "penaltyStart" TIMESTAMP(3),
ADD COLUMN "start" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;

View File

@ -1,12 +0,0 @@
-- AlterTable
ALTER TABLE "ChallengeAction" ALTER COLUMN "end" SET DATA TYPE TIMESTAMPTZ(3),
ALTER COLUMN "penaltyEnd" SET DATA TYPE TIMESTAMPTZ(3),
ALTER COLUMN "penaltyStart" SET DATA TYPE TIMESTAMPTZ(3),
ALTER COLUMN "start" SET DATA TYPE TIMESTAMPTZ(3);
-- AlterTable
ALTER TABLE "Geolocation" ALTER COLUMN "timestamp" SET DATA TYPE TIMESTAMPTZ(3);
-- AlterTable
ALTER TABLE "TrainTrip" ALTER COLUMN "departureTime" SET DATA TYPE TIMESTAMPTZ(3),
ALTER COLUMN "arrivalTime" SET DATA TYPE TIMESTAMPTZ(3);

View File

@ -1,8 +0,0 @@
/*
Warnings:
- You are about to drop the column `geometry` on the `TrainTrip` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "TrainTrip" DROP COLUMN "geometry";

View File

@ -1,13 +0,0 @@
/*
Warnings:
- You are about to drop the column `after` on the `MoneyUpdate` table. All the data in the column will be lost.
- You are about to drop the column `before` on the `MoneyUpdate` table. All the data in the column will be lost.
- Added the required column `amount` to the `MoneyUpdate` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "MoneyUpdate" DROP COLUMN "after",
DROP COLUMN "before",
ADD COLUMN "amount" INTEGER NOT NULL,
ADD COLUMN "timestamp" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;

View File

@ -2,23 +2,27 @@
CREATE TYPE "MoneyUpdateType" AS ENUM ('START', 'WIN_CHALLENGE', 'BUY_TRAIN');
-- CreateTable
CREATE TABLE "User" (
CREATE TABLE "Player" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"password" TEXT NOT NULL,
"money" INTEGER NOT NULL DEFAULT 0,
"currentRunner" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
CONSTRAINT "Player_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Geolocation" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"playerId" INTEGER NOT NULL,
"longitude" DOUBLE PRECISION NOT NULL,
"latitude" DOUBLE PRECISION NOT NULL,
"timestamp" TIMESTAMP(3) NOT NULL,
"speed" DOUBLE PRECISION NOT NULL,
"accuracy" DOUBLE PRECISION NOT NULL,
"altitude" DOUBLE PRECISION NOT NULL,
"altitudeAccuracy" DOUBLE PRECISION NOT NULL,
"timestamp" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "Geolocation_pkey" PRIMARY KEY ("id")
);
@ -36,10 +40,14 @@ CREATE TABLE "Challenge" (
-- CreateTable
CREATE TABLE "ChallengeAction" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"playerId" INTEGER NOT NULL,
"challengeId" INTEGER NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT false,
"success" BOOLEAN NOT NULL DEFAULT false,
"start" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"end" TIMESTAMPTZ(3),
"penaltyStart" TIMESTAMPTZ(3),
"penaltyEnd" TIMESTAMPTZ(3),
CONSTRAINT "ChallengeAction_pkey" PRIMARY KEY ("id")
);
@ -47,14 +55,13 @@ CREATE TABLE "ChallengeAction" (
-- CreateTable
CREATE TABLE "TrainTrip" (
"id" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
"playerId" INTEGER NOT NULL,
"distance" DOUBLE PRECISION NOT NULL,
"from" TEXT NOT NULL,
"to" TEXT NOT NULL,
"departureTime" TIMESTAMP(3) NOT NULL,
"arrivalTime" TIMESTAMP(3) NOT NULL,
"departureTime" TIMESTAMPTZ(3) NOT NULL,
"arrivalTime" TIMESTAMPTZ(3) NOT NULL,
"infoJson" JSONB NOT NULL,
"geometry" TEXT NOT NULL,
CONSTRAINT "TrainTrip_pkey" PRIMARY KEY ("id")
);
@ -62,18 +69,18 @@ CREATE TABLE "TrainTrip" (
-- CreateTable
CREATE TABLE "MoneyUpdate" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"before" INTEGER NOT NULL,
"after" INTEGER NOT NULL,
"playerId" INTEGER NOT NULL,
"amount" INTEGER NOT NULL,
"reason" "MoneyUpdateType" NOT NULL,
"actionId" INTEGER,
"tripId" TEXT,
"timestamp" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "MoneyUpdate_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_name_key" ON "User"("name");
CREATE UNIQUE INDEX "Player_name_key" ON "Player"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Challenge_title_key" ON "Challenge"("title");
@ -88,22 +95,22 @@ CREATE UNIQUE INDEX "MoneyUpdate_actionId_key" ON "MoneyUpdate"("actionId");
CREATE UNIQUE INDEX "MoneyUpdate_tripId_key" ON "MoneyUpdate"("tripId");
-- AddForeignKey
ALTER TABLE "Geolocation" ADD CONSTRAINT "Geolocation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "Geolocation" ADD CONSTRAINT "Geolocation_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ChallengeAction" ADD CONSTRAINT "ChallengeAction_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ChallengeAction" ADD CONSTRAINT "ChallengeAction_challengeId_fkey" FOREIGN KEY ("challengeId") REFERENCES "Challenge"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ChallengeAction" ADD CONSTRAINT "ChallengeAction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "TrainTrip" ADD CONSTRAINT "TrainTrip_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TrainTrip" ADD CONSTRAINT "TrainTrip_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "MoneyUpdate" ADD CONSTRAINT "MoneyUpdate_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MoneyUpdate" ADD CONSTRAINT "MoneyUpdate_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "ChallengeAction"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MoneyUpdate" ADD CONSTRAINT "MoneyUpdate_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "TrainTrip"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MoneyUpdate" ADD CONSTRAINT "MoneyUpdate_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -7,7 +7,7 @@ datasource db {
url = env("DATABASE_URL")
}
model User {
model Player {
id Int @id @default(autoincrement())
name String @unique
password String
@ -21,8 +21,8 @@ model User {
model Geolocation {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
player Player @relation(fields: [playerId], references: [id])
playerId Int
longitude Float
latitude Float
speed Float
@ -42,8 +42,8 @@ model Challenge {
model ChallengeAction {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
player Player @relation(fields: [playerId], references: [id])
playerId Int
challenge Challenge @relation(fields: [challengeId], references: [id])
challengeId Int @unique
active Boolean @default(false)
@ -57,8 +57,8 @@ model ChallengeAction {
model TrainTrip {
id String @id
user User @relation(fields: [userId], references: [id])
userId Int
player Player @relation(fields: [playerId], references: [id])
playerId Int
distance Float
from String
to String
@ -70,8 +70,8 @@ model TrainTrip {
model MoneyUpdate {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
player Player @relation(fields: [playerId], references: [id])
playerId Int
amount Int
reason MoneyUpdateType
action ChallengeAction? @relation(fields: [actionId], references: [id])

View File

@ -5,14 +5,14 @@ const prisma = new PrismaClient()
async function main() {
const emmyPassword = await bcrypt.hash("Emmy", 10)
const emmy = await prisma.user.upsert({
const emmy = await prisma.player.upsert({
where: { id: 1 },
update: { name: 'Emmy' },
create: { name: 'Emmy', password: emmyPassword },
})
const taminaPassword = await bcrypt.hash("Tamina", 10)
const tamina = await prisma.user.upsert({
const tamina = await prisma.player.upsert({
where: { id: 2 },
update: { name: 'Tamina' },
create: { name: 'Tamina', password: taminaPassword },

View File

@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'
import { PrismaService } from './prisma/prisma.service'
import { PrismaModule } from './prisma/prisma.module'
import { UsersModule } from './users/users.module'
import { PlayersModule } from './players/players.module'
import { AuthModule } from './auth/auth.module'
import { GeolocationsModule } from './geolocations/geolocations.module'
import { ChallengesModule } from './challenges/challenges.module'
@ -10,7 +10,7 @@ import { TrainsModule } from './trains/trains.module'
import { MoneyUpdatesModule } from './money-updates/money-updates.module'
@Module({
imports: [PrismaModule, UsersModule, AuthModule, GeolocationsModule, ChallengesModule, ChallengeActionsModule, TrainsModule, MoneyUpdatesModule],
imports: [PrismaModule, PlayersModule, AuthModule, GeolocationsModule, ChallengesModule, ChallengeActionsModule, TrainsModule, MoneyUpdatesModule],
providers: [PrismaService],
})
export class AppModule {}

View File

@ -5,7 +5,7 @@ import { PrismaModule } from 'src/prisma/prisma.module'
import { PassportModule } from '@nestjs/passport'
import { JwtModule } from '@nestjs/jwt'
import { env } from 'process'
import { UsersModule } from 'src/users/users.module'
import { PlayersModule } from 'src/players/players.module'
import { JwtStrategy } from './jwt.strategy'
export const JWT_SECRET = env.JWT_SECRET
@ -18,7 +18,7 @@ export const JWT_SECRET = env.JWT_SECRET
secret: JWT_SECRET,
signOptions: { expiresIn: '12h' },
}),
UsersModule,
PlayersModule,
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],

View File

@ -9,17 +9,17 @@ export class AuthService {
constructor(private prisma: PrismaService, private jwtService: JwtService) {}
async login(name: string, password: string): Promise<AuthEntity> {
const user = await this.prisma.user.findUnique({ where: { name: name } })
if (!user) {
throw new NotFoundException(`Aucun⋅e utilisateur⋅rice avec pour nom ${name}`)
const player = await this.prisma.player.findUnique({ where: { name: name } })
if (!player) {
throw new NotFoundException(`Aucun⋅e joueur⋅se avec pour nom ${name}`)
}
const isPasswordValid = await bcrypt.compare(password, user.password)
const isPasswordValid = await bcrypt.compare(password, player.password)
if (!isPasswordValid) {
throw new UnauthorizedException('Mot de passe incorrect')
}
return {
accessToken: this.jwtService.sign({ userId: user.id }),
accessToken: this.jwtService.sign({ playerId: player.id }),
}
}
}

View File

@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { User } from '@prisma/client'
import { Player } from '@prisma/client'
import { Request } from 'express'
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
export type AuthenticatedRequest = Request & { user: User }
export type AuthenticatedRequest = Request & { user: Player }

View File

@ -2,23 +2,24 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'
import { JWT_SECRET } from './auth.module'
import { UsersService } from 'src/users/users.service'
import { User } from '@prisma/client'
import { PlayersService } from 'src/players/players.service'
import { Player } from '@prisma/client'
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(private usersService: UsersService) {
constructor(private playersService: PlayersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_SECRET,
})
}
async validate(payload: { userId: number }): Promise<User> {
const user = await this.usersService.findOne(payload.userId)
if (!user) {
async validate(payload: { playerId: number }): Promise<Player> {
console.log(payload)
const player = await this.playersService.findOne(payload.playerId)
if (!player) {
throw new UnauthorizedException()
}
return user
return player
}
}

View File

@ -23,8 +23,7 @@ export class ChallengeActionsController {
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async create(@Req() request: AuthenticatedRequest, @Body() createChallengeActionDto: CreateChallengeActionDto): Promise<ChallengeActionEntity> {
const user = request.user
const challenge = await this.challengeActionsService.create(user, createChallengeActionDto)
const challenge = await this.challengeActionsService.create(request.user, createChallengeActionDto)
return new ChallengeActionEntity(challenge)
}

View File

@ -1,7 +1,7 @@
import { Injectable, NotAcceptableException } from '@nestjs/common'
import { CreateChallengeActionDto } from './dto/create-challenge-action.dto'
import { UpdateChallengeActionDto } from './dto/update-challenge-action.dto'
import { ChallengeAction, User } from '@prisma/client'
import { ChallengeAction, Player } from '@prisma/client'
import { PrismaService } from 'src/prisma/prisma.service'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { paginate } from 'src/common/utils/pagination.utils'
@ -11,8 +11,8 @@ import { FilterChallengeActionsDto } from './dto/filter-challenge-action.dto'
export class ChallengeActionsService {
constructor(private prisma: PrismaService) { }
async create(authenticatedUser: User, createChallengeActionDto: CreateChallengeActionDto): Promise<ChallengeAction> {
const data = { ...createChallengeActionDto, userId: authenticatedUser.id }
async create(authenticatedPlayer: Player, createChallengeActionDto: CreateChallengeActionDto): Promise<ChallengeAction> {
const data = { ...createChallengeActionDto, playerId: authenticatedPlayer.id }
return await this.prisma.challengeAction.create({
data: data,
})
@ -49,10 +49,10 @@ export class ChallengeActionsService {
})
}
async endCurrentChallenge(user: User, success: boolean): Promise<ChallengeAction> {
async endCurrentChallenge(player: Player, success: boolean): Promise<ChallengeAction> {
const challengeAction = await this.prisma.challengeAction.findFirst({
where: {
userId: user.id,
playerId: player.id,
active: true,
}
})

View File

@ -7,8 +7,8 @@ export class FilterChallengeActionsDto {
@IsOptional()
@IsInt()
@Type(() => Number)
@ApiProperty({ description: "Identifiant de l'utilisateur⋅rice qui effectue le défi", required: false })
userId?: number
@ApiProperty({ description: "Identifiant de læ joueur⋅se qui effectue le défi", required: false })
playerId?: number
@IsOptional()
@IsInt()

View File

@ -10,8 +10,8 @@ export class ChallengeActionEntity implements ChallengeAction {
@ApiProperty({ description: "Identifiant unique" })
id: number
@ApiProperty({ description: "Identifiant de l'utilisateur⋅rice effectuant le défi" })
userId: number
@ApiProperty({ description: "Identifiant de læ joueur⋅se effectuant le défi" })
playerId: number
@ApiProperty({ description: "Identifiant du défi rattaché à l'action" })
challengeId: number

View File

@ -1,7 +1,7 @@
import { Injectable, NotAcceptableException, NotFoundException } from '@nestjs/common'
import { CreateChallengeDto } from './dto/create-challenge.dto'
import { UpdateChallengeDto } from './dto/update-challenge.dto'
import { Challenge, User } from '@prisma/client'
import { Challenge, Player } from '@prisma/client'
import { PrismaService } from 'src/prisma/prisma.service'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { paginate } from 'src/common/utils/pagination.utils'
@ -56,10 +56,10 @@ export class ChallengesService {
})
}
async drawRandom(user: User): Promise<ChallengeEntity> {
async drawRandom(player: Player): Promise<ChallengeEntity> {
const currentChallengeAction = await this.prisma.challengeAction.findFirst({
where: {
userId: user.id,
playerId: player.id,
active: true,
}
})
@ -82,7 +82,7 @@ export class ChallengesService {
const challengeEntity: ChallengeEntity = new ChallengeEntity(challenge)
const action = await this.prisma.challengeAction.create({
data: {
userId: user.id,
playerId: player.id,
challengeId: challenge.id,
active: true,
success: false,

View File

@ -1,9 +1,9 @@
import { Type } from "class-transformer"
import { IsNumber, IsOptional } from "class-validator"
export class UserFilterDto {
export class PlayerFilterDto {
@IsOptional()
@IsNumber()
@Type(() => Number)
userId?: number
playerId?: number
}

View File

@ -9,8 +9,8 @@ export class GeolocationEntity implements Geolocation {
@ApiProperty({description: "Identifiant unique"})
id: number
@ApiProperty({description: "Utilisateur⋅rice ayant émis la géolocalisation"})
userId: number
@ApiProperty({description: "Joueur⋅se ayant émis la géolocalisation"})
playerId: number
@ApiProperty({description: "Longitude en degrés"})
longitude: number

View File

@ -7,7 +7,7 @@ import { GeolocationEntity } from './entities/geolocation.entity'
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'
import { UserFilterDto } from 'src/common/dto/user_filter.dto'
import { PlayerFilterDto } from 'src/common/dto/player_filter.dto'
@Controller('geolocations')
export class GeolocationsController {
@ -21,8 +21,7 @@ export class GeolocationsController {
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async create(@Req() request: AuthenticatedRequest, @Body() createGeolocationDto: CreateGeolocationDto): Promise<GeolocationEntity> {
const user = request.user
const geolocation = await this.geolocationsService.create(user, createGeolocationDto)
const geolocation = await this.geolocationsService.create(request.user, createGeolocationDto)
return new GeolocationEntity(geolocation)
}
@ -32,8 +31,8 @@ export class GeolocationsController {
@ApiOkResponsePaginated(GeolocationEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination?: QueryPaginationDto, @Query() userFilter?: UserFilterDto): Promise<PaginateOutputDto<GeolocationEntity>> {
const [geolocations, total] = await this.geolocationsService.findAll(queryPagination, userFilter)
async findAll(@Query() queryPagination?: QueryPaginationDto, @Query() playerFilter?: PlayerFilterDto): Promise<PaginateOutputDto<GeolocationEntity>> {
const [geolocations, total] = await this.geolocationsService.findAll(queryPagination, playerFilter)
return paginateOutput<GeolocationEntity>(geolocations.map(geolocation => new GeolocationEntity(geolocation)), total, queryPagination)
}
@ -51,17 +50,17 @@ export class GeolocationsController {
return new GeolocationEntity(geolocation)
}
@Get('/last-location/:userId')
@Get('/last-location/:playerId')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: GeolocationEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Aucune localisation trouvée" })
async findLastLocation(@Param('userId', ParseIntPipe) userId: number): Promise<GeolocationEntity> {
const geolocation = await this.geolocationsService.findLastLocation(userId)
async findLastLocation(@Param('playerId', ParseIntPipe) playerId: number): Promise<GeolocationEntity> {
const geolocation = await this.geolocationsService.findLastLocation(playerId)
if (!geolocation)
throw new NotFoundException(`Géolocalisation inexistante pour l'utilisateur⋅rice ${userId}`)
throw new NotFoundException(`Géolocalisation inexistante pour læ joueur⋅se ${playerId}`)
return new GeolocationEntity(geolocation)
}

View File

@ -1,23 +1,23 @@
import { Injectable } from '@nestjs/common'
import { CreateGeolocationDto } from './dto/create-geolocation.dto'
import { PrismaService } from 'src/prisma/prisma.service'
import { Geolocation, Prisma, User } from '@prisma/client'
import { Geolocation, Player, Prisma } from '@prisma/client'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { paginate } from 'src/common/utils/pagination.utils'
import { UserFilterDto } from 'src/common/dto/user_filter.dto'
import { PlayerFilterDto } from 'src/common/dto/player_filter.dto'
@Injectable()
export class GeolocationsService {
constructor(private prisma: PrismaService) { }
async create(authenticatedUser: User, createGeolocationDto: CreateGeolocationDto): Promise<Geolocation> {
const data = { ...createGeolocationDto, userId: authenticatedUser.id }
async create(authenticatedPlayer: Player, createGeolocationDto: CreateGeolocationDto): Promise<Geolocation> {
const data = { ...createGeolocationDto, playerId: authenticatedPlayer.id }
return await this.prisma.geolocation.create({ data: data })
}
async findAll(queryPagination?: QueryPaginationDto, userFilter?: UserFilterDto): Promise<[Geolocation[], number]> {
async findAll(queryPagination?: QueryPaginationDto, playerFilter?: PlayerFilterDto): Promise<[Geolocation[], number]> {
const filter = {
where: (userFilter?.userId ? { userId: userFilter.userId } : {}),
where: (playerFilter?.playerId ? { playerId: playerFilter.playerId } : {}),
orderBy: { timestamp: Prisma.SortOrder.desc },
}
return [
@ -33,9 +33,9 @@ export class GeolocationsService {
return await this.prisma.geolocation.findUnique({ where: { id } })
}
async findLastLocation(userId: number): Promise<Geolocation> {
async findLastLocation(playerId: number): Promise<Geolocation> {
return await this.prisma.geolocation.findFirst({
where: { userId: userId },
where: { playerId: playerId },
orderBy: { timestamp: Prisma.SortOrder.desc },
take: 1
})

View File

@ -9,8 +9,8 @@ export class MoneyUpdateEntity implements MoneyUpdate {
@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: "Joueur⋅se concerné⋅e par la mise à jour de solde" })
playerId: number
@ApiProperty({ description: "Montant de la modification du solde" })
amount: number

View File

@ -7,7 +7,7 @@ import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNotFoundRes
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 { PlayerFilterDto } from 'src/common/dto/player_filter.dto'
import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
@Controller('money-updates')
@ -32,8 +32,8 @@ export class MoneyUpdatesController {
@ApiOkResponsePaginated(MoneyUpdateEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination: QueryPaginationDto, @Query() userFilter: UserFilterDto): Promise<PaginateOutputDto<MoneyUpdateEntity>> {
const [trains, total] = await this.moneyUpdatesService.findAll(queryPagination, userFilter)
async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise<PaginateOutputDto<MoneyUpdateEntity>> {
const [trains, total] = await this.moneyUpdatesService.findAll(queryPagination, playerFilter)
return paginateOutput<MoneyUpdateEntity>(trains.map(train => new MoneyUpdateEntity(train)), total, queryPagination)
}

View File

@ -2,32 +2,32 @@ 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 { MoneyUpdate, Player } from '@prisma/client'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { UserFilterDto } from 'src/common/dto/user_filter.dto'
import { PlayerFilterDto } from 'src/common/dto/player_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<MoneyUpdate> {
async create(player: Player, createMoneyUpdateDto: CreateMoneyUpdateDto): Promise<MoneyUpdate> {
return await this.prisma.moneyUpdate.create({
data: {
...createMoneyUpdateDto,
userId: user.id,
playerId: player.id,
}
})
}
async findAll(queryPagination: QueryPaginationDto, userFilter: UserFilterDto): Promise<[MoneyUpdate[], number]> {
async findAll(queryPagination: QueryPaginationDto, playerFilter: PlayerFilterDto): Promise<[MoneyUpdate[], number]> {
return [
await this.prisma.moneyUpdate.findMany({
where: userFilter,
where: playerFilter,
...paginate(queryPagination),
}),
await this.prisma.moneyUpdate.count({
where: userFilter,
where: playerFilter,
}),
]
}

View File

@ -1,9 +1,9 @@
import { ApiProperty } from "@nestjs/swagger"
import { User } from "@prisma/client"
import { Player } from "@prisma/client"
import { Exclude } from 'class-transformer'
export class UserEntity implements User {
constructor(partial: Partial<UserEntity>) {
export class PlayerEntity implements Player {
constructor(partial: Partial<PlayerEntity>) {
Object.assign(this, partial)
}

View File

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing'
import { PlayersController } from './players.controller'
import { PlayersService } from './players.service'
describe('PlayersController', () => {
let controller: PlayersController
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PlayersController],
providers: [PlayersService],
}).compile()
controller = module.get<PlayersController>(PlayersController)
})
it('should be defined', () => {
expect(controller).toBeDefined()
})
})

View File

@ -1,40 +1,40 @@
import { Body, Controller, Get, HttpCode, NotFoundException, Param, ParseIntPipe, Patch, Query, Req, UseGuards } from '@nestjs/common'
import { UsersService } from './users.service'
import { PlayersService } from './players.service'
import { ApiBadRequestResponse, ApiBearerAuth, ApiForbiddenResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger'
import { UserEntity } from './entities/user.entity'
import { PlayerEntity } from './entities/player.entity'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { UpdatePasswordDto } from './dto/user_password.dto'
import { UpdatePasswordDto } from './dto/player_password.dto'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Controller('players')
export class PlayersController {
constructor(private readonly playersService: PlayersService) {}
@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponsePaginated(UserEntity)
@ApiOkResponsePaginated(PlayerEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination?: QueryPaginationDto): Promise<PaginateOutputDto<UserEntity>> {
const [users, total] = await this.usersService.findAll(queryPagination)
return paginateOutput<UserEntity>(users.map(user => new UserEntity(user)), total, queryPagination)
async findAll(@Query() queryPagination?: QueryPaginationDto): Promise<PaginateOutputDto<PlayerEntity>> {
const [players, total] = await this.playersService.findAll(queryPagination)
return paginateOutput<PlayerEntity>(players.map(player => new PlayerEntity(player)), total, queryPagination)
}
@Get(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: UserEntity })
@ApiOkResponse({ type: PlayerEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Utilisateur⋅rice non trouvé⋅e" })
@ApiNotFoundResponse({ description: "Joueur⋅se non trouvé⋅e" })
async findOne(@Param('id', ParseIntPipe) id: number) {
const user = await this.usersService.findOne(id)
if (!user)
throw new NotFoundException(`L'utilisateur⋅rice avec l'identifiant ${id} n'existe pas`)
return new UserEntity(user)
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)
}
@Patch('/update-password')
@ -46,7 +46,6 @@ export class UsersController {
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async updatePassword(@Req() request: AuthenticatedRequest, @Body() body: UpdatePasswordDto) {
const user = request.user
await this.usersService.updatePassword(user, body)
await this.playersService.updatePassword(request.user, body)
}
}

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common'
import { PlayersService } from './players.service'
import { PlayersController } from './players.controller'
import { PrismaModule } from 'src/prisma/prisma.module'
@Module({
controllers: [PlayersController],
providers: [PlayersService],
imports: [PrismaModule],
exports: [PlayersService],
})
export class PlayersModule {}

View File

@ -1,15 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing'
import { UsersService } from './users.service'
import { PlayersService } from './players.service'
describe('UsersService', () => {
let service: UsersService
describe('PlayersService', () => {
let service: PlayersService
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
providers: [PlayersService],
}).compile()
service = module.get<UsersService>(UsersService)
service = module.get<PlayersService>(PlayersService)
})
it('should be defined', () => {

View File

@ -1,29 +1,29 @@
import { Injectable } from '@nestjs/common'
import { User } from '@prisma/client'
import { Player } from '@prisma/client'
import { PrismaService } from 'src/prisma/prisma.service'
import * as bcrypt from 'bcrypt'
import { UpdatePasswordDto } from './dto/user_password.dto'
import { UpdatePasswordDto } from './dto/player_password.dto'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { paginate } from 'src/common/utils/pagination.utils'
@Injectable()
export class UsersService {
export class PlayersService {
constructor(private prisma: PrismaService) {}
async findAll(queryPagination?: QueryPaginationDto): Promise<[User[], number]> {
async findAll(queryPagination?: QueryPaginationDto): Promise<[Player[], number]> {
return [
await this.prisma.user.findMany({ ...paginate(queryPagination) }),
await this.prisma.user.count()
await this.prisma.player.findMany({ ...paginate(queryPagination) }),
await this.prisma.player.count()
]
}
async findOne(id: number): Promise<User> {
return await this.prisma.user.findUnique({ where: { id } })
async findOne(id: number): Promise<Player> {
return await this.prisma.player.findUnique({ where: { id: id } })
}
async updatePassword(user: User, { password }: UpdatePasswordDto): Promise<void> {
async updatePassword(user: Player, { password }: UpdatePasswordDto): Promise<void> {
const hashedPassword = await bcrypt.hash(password, 10)
await this.prisma.user.update({
await this.prisma.player.update({
where: { id: user.id },
data: { password: hashedPassword }
})

View File

@ -1,19 +1,19 @@
import { Injectable } from '@nestjs/common'
import { PrismaClient, } from '@prisma/client'
async function updateUserMoney(prisma: PrismaClient): Promise<void> {
// On calcule le solde par utilisateur⋅rice pour mettre à jour l'objet User
const moneyPerUser = await prisma.moneyUpdate.groupBy({
by: 'userId',
async function updatePlayerMoney(prisma: PrismaClient): Promise<void> {
// On calcule le solde par joueur⋅se pour mettre à jour l'objet Player
const moneyPerPlayer = await prisma.moneyUpdate.groupBy({
by: 'playerId',
_sum: {
amount: true,
}
})
for (const { userId, _sum } of moneyPerUser) {
for (const { playerId, _sum } of moneyPerPlayer) {
const { amount } = _sum
await prisma.user.update({
await prisma.player.update({
where: {
id: userId,
id: playerId,
},
data: {
money: amount
@ -21,7 +21,7 @@ async function updateUserMoney(prisma: PrismaClient): Promise<void> {
})
}
// On réinitialise le solde s'il n'y a plus aucun MoneyUpdate
await prisma.user.updateMany({
await prisma.player.updateMany({
where: {
moneyUpdates: { none: {} }
},
@ -39,7 +39,7 @@ function extendPrismaClient() {
$allOperations({ model, operation, args, query }) {
const result = query(args)
if (!operation.startsWith("find") && !['aggregate', 'count', 'groupBy'].includes(operation))
result.then(() => updateUserMoney(prisma))
result.then(() => updatePlayerMoney(prisma))
return result
}
}

View File

@ -10,8 +10,8 @@ export class CreateTrainDto {
@IsInt()
@Type(() => Number)
@ApiProperty({ description: "Identifiant de l'utilisateur⋅rice effectuant le trajet" })
userId: number
@ApiProperty({ description: "Identifiant de læ joueur⋅se effectuant le trajet" })
playerId: number
@IsNumber()
@Type(() => Number)

View File

@ -10,8 +10,8 @@ export class TrainEntity implements TrainTrip {
@ApiProperty({ description: "Identifiant du train, donné par l'identifiant de partage Interrail" })
id: string
@ApiProperty({ description: "Identifiant de l'utilisateur⋅rice effectuant le trajet" })
userId: number
@ApiProperty({ description: "Identifiant de læ joueur⋅se effectuant le trajet" })
playerId: number
@ApiProperty({ description: "Distance estimée en mètres du trajet" })
distance: number

View File

@ -9,7 +9,7 @@ import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/paginat
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
import { ImportTrainDto } from './dto/import-train.dto'
import { UserFilterDto } from 'src/common/dto/user_filter.dto'
import { PlayerFilterDto } from 'src/common/dto/player_filter.dto'
@Controller('trains')
export class TrainsController {
@ -33,8 +33,8 @@ export class TrainsController {
@ApiOkResponsePaginated(TrainEntity)
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
async findAll(@Query() queryPagination: QueryPaginationDto, @Query() userFilter: UserFilterDto): Promise<PaginateOutputDto<TrainEntity>> {
const [trains, total] = await this.trainsService.findAll(queryPagination, userFilter)
async findAll(@Query() queryPagination: QueryPaginationDto, @Query() playerFilter: PlayerFilterDto): Promise<PaginateOutputDto<TrainEntity>> {
const [trains, total] = await this.trainsService.findAll(queryPagination, playerFilter)
return paginateOutput<TrainEntity>(trains.map(train => new TrainEntity(train)), total, queryPagination)
}

View File

@ -2,13 +2,13 @@ import { Injectable, NotAcceptableException } from '@nestjs/common'
import { CreateTrainDto } from './dto/create-train.dto'
import { UpdateTrainDto } from './dto/update-train.dto'
import { PrismaService } from 'src/prisma/prisma.service'
import { TrainTrip, User } from '@prisma/client'
import { Player, TrainTrip } from '@prisma/client'
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
import { paginate } from 'src/common/utils/pagination.utils'
import { ImportTrainDto } from './dto/import-train.dto'
import { InterrailJourney, InterrailLegInfo, InterrailTravelInfo } from './dto/interrail-api.dto'
import { distanceCoordinates } from 'src/common/utils/calculus.utils'
import { UserFilterDto } from 'src/common/dto/user_filter.dto'
import { PlayerFilterDto } from 'src/common/dto/player_filter.dto'
@Injectable()
export class TrainsService {
@ -18,14 +18,14 @@ export class TrainsService {
return await this.prisma.trainTrip.create({ data: createTrainDto })
}
async findAll(queryPagination: QueryPaginationDto, userFilter: UserFilterDto): Promise<[TrainTrip[], number]> {
async findAll(queryPagination: QueryPaginationDto, playerFilter: PlayerFilterDto): Promise<[TrainTrip[], number]> {
return [
await this.prisma.trainTrip.findMany({
where: userFilter,
where: playerFilter,
...paginate(queryPagination),
}),
await this.prisma.trainTrip.count({
where: userFilter,
where: playerFilter,
}),
]
}
@ -49,7 +49,7 @@ export class TrainsService {
})
}
async import(user: User, { id: trainId }: ImportTrainDto): Promise<TrainTrip> {
async import(player: Player, { id: trainId }: ImportTrainDto): Promise<TrainTrip> {
const interrailResult: InterrailJourney = await fetch(`https://3uiwjsimnh.execute-api.eu-central-1.amazonaws.com/Prod/journey-import?id=${trainId}`)
.then(data => data.json())
if (interrailResult.data.travels.length !== 1)
@ -83,7 +83,7 @@ export class TrainsService {
},
create: {
id: trainId,
userId: user.id,
playerId: player.id,
distance: distance,
from: travel.from,
to: travel.to,
@ -92,7 +92,7 @@ export class TrainsService {
infoJson: leg.infoJson,
},
update: {
userId: user.id,
playerId: player.id,
distance: distance,
from: travel.from,
to: travel.to,

View File

@ -1,20 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
describe('UsersController', () => {
let controller: UsersController
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile()
controller = module.get<UsersController>(UsersController)
})
it('should be defined', () => {
expect(controller).toBeDefined()
})
})

View File

@ -1,12 +0,0 @@
import { Module } from '@nestjs/common'
import { UsersService } from './users.service'
import { UsersController } from './users.controller'
import { PrismaModule } from 'src/prisma/prisma.module'
@Module({
controllers: [UsersController],
providers: [UsersService],
imports: [PrismaModule],
exports: [UsersService],
})
export class UsersModule {}