From dae40b3630bff892747e2a98f26cf71094bdfbb4 Mon Sep 17 00:00:00 2001 From: nicolas-arnaud Date: Mon, 6 Mar 2023 00:01:30 +0100 Subject: [PATCH] new leader and history routes --- README.md | 19 ++++-- back/volume/src/pong/entity/result.entity.ts | 12 ++-- back/volume/src/pong/game/Game.ts | 5 +- back/volume/src/pong/game/Games.ts | 7 +-- back/volume/src/pong/pong.gateway.ts | 4 +- back/volume/src/pong/pong.module.ts | 11 ++-- back/volume/src/pong/pong.service.ts | 56 ++++++++++------- back/volume/src/users/dto/user.dto.ts | 1 - back/volume/src/users/entity/user.entity.ts | 11 ++-- back/volume/src/users/users.controller.ts | 65 ++++++++++++++------ back/volume/src/users/users.module.ts | 9 ++- back/volume/src/users/users.service.ts | 65 ++++++++++++++------ front/volume/src/App.svelte | 2 +- front/volume/src/components/Pong/Pong.svelte | 2 +- front/volume/src/components/Profile.svelte | 10 ++- 15 files changed, 180 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 9782fdd..92b78b5 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,21 @@ rename .env_sample to .env and customize it to your needs and credentials. |GET |/log/out |log out user.|☑| |GET |/all |return all users publics datas.|☒| |GET |/online |return all online users's public datas.|☒| -|GET |/:id |return ftId: id's public datas|☒| -|GET |/ |return connected user public datas|☑| -|POST|/ |update user datas.|☑| |GET |/friends |return users which are friends.|☑| |GET |/invits |return users which invited user to be friend.|☑| -|POST|/invit/:id |invit user whith ftId: id as friend.|☑| -|GET |/avatar |return the user avatar|☒| -|POST|/avatar |set a user avatar with multipart post upload.|☑| +|GET |/leader |return the global leaderboard|☑| +|GET |/leader/:id |return the user(id) place in leaderboard|☑| +|GET |/history |return the matchs results sorted by date|☑| +|GET |/history/:id |return the last user(id)'s results sorted by date|☑| +|POST|/avatar |set a user() avatar with multipart post upload.|☑| +|GET |/avatar |return the user() avatar|☒| +|GET |/user/:name |return the user(name)|☒| +|POST|/invit/:id |user() invit user(id) as friend.|☑| +|GET |/avatar/:id |return the user(id)'s avatar|☒| +|GET |/:id |return user(id) public datas|☒| +|POST|/:id |update/create user(id)|☑| +|GET |/ |return user()' public datas|☑| +|POST|/ |update/create user()|☑| ## Dependencies: diff --git a/back/volume/src/pong/entity/result.entity.ts b/back/volume/src/pong/entity/result.entity.ts index aaa7ed2..2a95b64 100644 --- a/back/volume/src/pong/entity/result.entity.ts +++ b/back/volume/src/pong/entity/result.entity.ts @@ -3,6 +3,7 @@ import { PrimaryGeneratedColumn, Column, ManyToMany, + CreateDateColumn } from 'typeorm' import User from 'src/users/entity/user.entity' @@ -10,11 +11,14 @@ import User from 'src/users/entity/user.entity' @Entity() export default class Result { @PrimaryGeneratedColumn() - id:number + id: number @ManyToMany(() => User, (player: User) => player.results) - players: (User | null)[] // TODO: change to User[] for final version + players: Array // TODO: change to User[] for final version - @Column('text', {array: true}) - public score: number[] + @Column('text', { array: true }) + public score: number[] + + @CreateDateColumn() + date: Date } diff --git a/back/volume/src/pong/game/Game.ts b/back/volume/src/pong/game/Game.ts index a414ff1..219466e 100644 --- a/back/volume/src/pong/game/Game.ts +++ b/back/volume/src/pong/game/Game.ts @@ -15,7 +15,7 @@ import { Spectator } from './Spectator' import { type MapDtoValidated } from '../dtos/MapDtoValidated' import { type GameUpdate } from '../dtos/GameUpdate' import { type GameInfo } from '../dtos/GameInfo' -import { PongService } from '../pong.service' +import { type PongService } from '../pong.service' import { Injectable, Inject } from '@nestjs/common' function gameLoop (game: Game): void { @@ -67,7 +67,6 @@ export class Game { map: MapDtoValidated, gameStoppedCallback: (name: string) => void, private readonly pongService: PongService - ) { this.id = randomUUID() this.timer = null @@ -149,7 +148,7 @@ export class Game { async stop (): Promise { if (this.timer !== null) { - await this.pongService.saveResult(this.players) + await this.pongService.saveResult(this.players) this.gameStoppedCallback(this.players[0].name) clearInterval(this.timer) diff --git a/back/volume/src/pong/game/Games.ts b/back/volume/src/pong/game/Games.ts index 13d436c..5ac910b 100644 --- a/back/volume/src/pong/game/Games.ts +++ b/back/volume/src/pong/game/Games.ts @@ -4,7 +4,7 @@ import { Point } from './utils' import { type MapDtoValidated as GameMap } from '../dtos/MapDtoValidated' import { type GameCreationDtoValidated } from '../dtos/GameCreationDtoValidated' import { type GameInfo } from '../dtos/GameInfo' -import { PongService } from '../pong.service' +import { type PongService } from '../pong.service' import { DEFAULT_BALL_SIZE, DEFAULT_PADDLE_SIZE, @@ -12,9 +12,8 @@ import { DEFAULT_WIN_SCORE } from './constants' - export class Games { - constructor (private readonly pongService : PongService) {} + constructor (private readonly pongService: PongService) {} private readonly playerNameToGameIndex = new Map() private readonly games = new Array() @@ -33,7 +32,7 @@ export class Games { names, map, this.gameStopped.bind(this, names[0]), - this.pongService, + this.pongService ) ) this.playerNameToGameIndex.set(names[0], this.games.length - 1) diff --git a/back/volume/src/pong/pong.gateway.ts b/back/volume/src/pong/pong.gateway.ts index 661301c..ce38752 100644 --- a/back/volume/src/pong/pong.gateway.ts +++ b/back/volume/src/pong/pong.gateway.ts @@ -28,9 +28,7 @@ interface WebSocketWithId extends WebSocket { @WebSocketGateway() export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { - constructor( - private readonly pongService: PongService - ) {} + constructor (private readonly pongService: PongService) {} private readonly games: Games = new Games(this.pongService) private readonly socketToPlayerName = new Map() diff --git a/back/volume/src/pong/pong.module.ts b/back/volume/src/pong/pong.module.ts index c56b8d5..594383b 100644 --- a/back/volume/src/pong/pong.module.ts +++ b/back/volume/src/pong/pong.module.ts @@ -1,15 +1,14 @@ -import { Module } from '@nestjs/common' +import { forwardRef, Module } from '@nestjs/common' import { PongGateway } from './pong.gateway' import Result from './entity/result.entity' -import {TypeOrmModule } from '@nestjs/typeorm' -import {PongService } from './pong.service' +import { TypeOrmModule } from '@nestjs/typeorm' +import { PongService } from './pong.service' import { UsersModule } from 'src/users/users.module' @Module({ imports: [ - UsersModule, - TypeOrmModule.forFeature([Result]) - ], + forwardRef(() => UsersModule), + TypeOrmModule.forFeature([Result])], providers: [PongGateway, PongService], exports: [PongService] }) diff --git a/back/volume/src/pong/pong.service.ts b/back/volume/src/pong/pong.service.ts index 73b6816..085a0b0 100644 --- a/back/volume/src/pong/pong.service.ts +++ b/back/volume/src/pong/pong.service.ts @@ -1,40 +1,52 @@ -import { Inject, Injectable } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; +import { Inject, Injectable, forwardRef } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' import { Repository } from 'typeorm' import { UsersService } from 'src/users/users.service' import Result from './entity/result.entity' -import User from 'src/users/entity/user.entity' -import { Player } from './game/Player' - +import type User from 'src/users/entity/user.entity' +import { type Player } from './game/Player' @Injectable() export class PongService { - constructor( - @InjectRepository(Result)private readonly resultsRepository: Repository, + constructor ( + @InjectRepository(Result) + private readonly resultsRepository: Repository, private readonly usersService: UsersService - ) { } + ) {} - async updatePlayer(i: number, result: Result) { - let player: User | null = result.players[i] - if (!player) return + async updatePlayer (i: number, result: Result) { + const player: User | null = result.players[i] + if (player == null) return player.matchs++ - if (result.score[i] > result.score[Math.abs(i-1)]) - player.wins++; - else - player.looses++; + if (result.score[i] > result.score[Math.abs(i - 1)]) player.wins++ + else player.looses++ + player.winrate = (100 * player.wins) / player.matchs player.results.push(result) this.usersService.save(player) - } - async saveResult(players: Player[]) { - let result = new Result; - result.players = await Promise.all(players.map(async (p): Promise => { - return await this.usersService.findUserByName(p.name) - })) - result.score = players.map((p) => p.score); + async saveResult (players: Player[]) { + const result = new Result() + result.players = await Promise.all( + players.map(async (p): Promise => { + return await this.usersService.findUserByName(p.name) + }) + ) + result.score = players.map((p) => p.score) this.updatePlayer(0, result) this.updatePlayer(1, result) this.resultsRepository.save(result) } + + async getHistory (): Promise { + return await this.resultsRepository.find({ + order: { date: 'DESC' } + }) + } + + async getHistoryById (ftId: number): Promise { + const results = await this.usersService.getResults(ftId) + return results.sort((a, b) => (a.date < b.date ? 1 : -1)) + } + } diff --git a/back/volume/src/users/dto/user.dto.ts b/back/volume/src/users/dto/user.dto.ts index 12215c0..399dc2a 100644 --- a/back/volume/src/users/dto/user.dto.ts +++ b/back/volume/src/users/dto/user.dto.ts @@ -14,7 +14,6 @@ export class UserDto { @IsOptional() readonly status: string - } export class AvatarUploadDto { diff --git a/back/volume/src/users/entity/user.entity.ts b/back/volume/src/users/entity/user.entity.ts index c224dd3..b0fda9e 100644 --- a/back/volume/src/users/entity/user.entity.ts +++ b/back/volume/src/users/entity/user.entity.ts @@ -26,17 +26,20 @@ export class User { status: string @Column({ name: 'avatar' }) - public avatar?: string + avatar: string - @Column({default: 0}) + @Column({ default: 0 }) wins: number - @Column({default: 0}) + @Column({ default: 0 }) looses: number - @Column({default: 0}) + @Column({ default: 0 }) matchs: number + @Column({ default: 0 }) + winrate: number + @ManyToMany(() => Result, (result: Result) => result.players) @JoinTable() results: Result[] diff --git a/back/volume/src/users/users.controller.ts b/back/volume/src/users/users.controller.ts index 47c57fb..81c5f14 100644 --- a/back/volume/src/users/users.controller.ts +++ b/back/volume/src/users/users.controller.ts @@ -20,6 +20,7 @@ import { diskStorage } from 'multer' import { type User } from './entity/user.entity' import { UsersService } from './users.service' import { UserDto, AvatarUploadDto } from './dto/user.dto' +import { PongService } from 'src/pong/pong.service' import { AuthenticatedGuard } from 'src/auth/42-auth.guard' import { FtUser } from 'src/auth/42.decorator' @@ -32,30 +33,58 @@ import { join } from 'path' @Controller() export class UsersController { - constructor (private readonly usersService: UsersService) {} + constructor( + private readonly usersService: UsersService, + private readonly pongService: PongService + ) { } @Get('all') - async getAllUsers (): Promise { + async getAllUsers(): Promise { return await this.usersService.findUsers() } @Get('online') - async getOnlineUsers (): Promise { + async getOnlineUsers(): Promise { return await this.usersService.findOnlineUsers() } @Get('friends') @UseGuards(AuthenticatedGuard) - async getFriends (@FtUser() profile: Profile) { + async getFriends(@FtUser() profile: Profile) { return await this.usersService.getFriends(profile.id) } @Get('invits') @UseGuards(AuthenticatedGuard) - async getInvits (@FtUser() profile: Profile) { + async getInvits(@FtUser() profile: Profile) { return await this.usersService.getInvits(profile.id) } + + @Get('leader') + @UseGuards(AuthenticatedGuard) + async getLeader() { + return await this.usersService.getLeader() + } + + @Get('leader/:id') + @UseGuards(AuthenticatedGuard) + async getRank(@Param('id', ParseIntPipe) id: number) { + return await this.usersService.getRank(id) + } + + @Get('history') + @UseGuards(AuthenticatedGuard) + async getHistory() { + return await this.pongService.getHistory() + } + + @Get('history/:id') + @UseGuards(AuthenticatedGuard) + async getHistoryById(@Param('id', ParseIntPipe) id: number) { + return this.pongService.getHistoryById(id) + } + @Post('avatar') @UseGuards(AuthenticatedGuard) @Redirect('http://localhost') @@ -78,8 +107,8 @@ export class UsersController { description: 'A new avatar for the user', type: AvatarUploadDto }) - async addAvatar ( - @FtUser() profile: Profile, + async addAvatar( + @FtUser() profile: Profile, @UploadedFile() file: Express.Multer.File ) { await this.usersService.addAvatar(profile.id, file.filename) @@ -87,30 +116,30 @@ export class UsersController { @Get('avatar') @UseGuards(AuthenticatedGuard) - async getAvatar ( - @FtUser() profile: Profile, + async getAvatar( + @FtUser() profile: Profile, @Res({ passthrough: true }) response: Response ) { return await this.getAvatarById(profile.id, response) } @Get('user/:name') - async getUserByName (@Param('name') username: string): Promise { + async getUserByName(@Param('name') username: string): Promise { return await this.usersService.findUserByName(username) } @Get('invit/:id') @UseGuards(AuthenticatedGuard) - async invitUser ( - @FtUser() profile: Profile, + async invitUser( + @FtUser() profile: Profile, @Param('id', ParseIntPipe) id: number ) { return await this.usersService.invit(profile.id, id) } @Get('avatar/:id') - async getAvatarById ( - @Param('id', ParseIntPipe) ftId: number, + async getAvatarById( + @Param('id', ParseIntPipe) ftId: number, @Res({ passthrough: true }) response: Response ) { const user = await this.usersService.findUser(ftId) @@ -125,7 +154,7 @@ export class UsersController { } @Get(':id') - async getUserById ( + async getUserById( @Param('id', ParseIntPipe) ftId: number ): Promise { return await this.usersService.findUser(ftId) @@ -133,7 +162,7 @@ export class UsersController { @Post(":id") @UseGuards(AuthenticatedGuard) - async createById (@Body() payload: UserDto) { + async createById(@Body() payload: UserDto) { const user = await this.usersService.findUser(payload.ftId) if (user != null) { return await this.usersService.update(user, payload) @@ -144,13 +173,13 @@ export class UsersController { @Get() @UseGuards(AuthenticatedGuard) - async getUser (@FtUser() profile: Profile): Promise { + async getUser(@FtUser() profile: Profile): Promise { return await this.usersService.findUser(profile.id) } @Post() @UseGuards(AuthenticatedGuard) - async create (@Body() payload: UserDto, @FtUser() profile: Profile) { + async create(@Body() payload: UserDto, @FtUser() profile: Profile) { const user = await this.usersService.findUser(profile.id) if (user != null) { return await this.usersService.update(user, payload) diff --git a/back/volume/src/users/users.module.ts b/back/volume/src/users/users.module.ts index 6bda38d..3a1b851 100644 --- a/back/volume/src/users/users.module.ts +++ b/back/volume/src/users/users.module.ts @@ -1,13 +1,16 @@ -import { Module } from '@nestjs/common' +import { forwardRef, Module } from '@nestjs/common' import { TypeOrmModule } from '@nestjs/typeorm' import { User } from './entity/user.entity' import { UsersController } from './users.controller' import { UsersService } from './users.service' +import { PongModule } from 'src/pong/pong.module' @Module({ - imports: [TypeOrmModule.forFeature([User])], + imports: [ + forwardRef(() => PongModule), + TypeOrmModule.forFeature([User])], controllers: [UsersController], providers: [UsersService], exports: [UsersService] }) -export class UsersModule {} +export class UsersModule { } diff --git a/back/volume/src/users/users.service.ts b/back/volume/src/users/users.service.ts index ce32d06..377c5af 100644 --- a/back/volume/src/users/users.service.ts +++ b/back/volume/src/users/users.service.ts @@ -4,14 +4,15 @@ import { Repository } from 'typeorm' import { User } from './entity/user.entity' import { type UserDto } from './dto/user.dto' import { type Channel } from 'src/chat/entity/channel.entity' +import Result from 'src/pong/entity/result.entity' @Injectable() export class UsersService { constructor ( - @InjectRepository(User) private readonly usersRepository: Repository + @InjectRepository(User) private readonly usersRepository: Repository, ) {} - save(user: User) { + save (user: User) { this.usersRepository.save(user) } @@ -20,13 +21,12 @@ export class UsersService { } async findUserByName (username: string): Promise { - let user = await this.usersRepository.findOne({ - where: { username: username }, - relations : {results: true} - + const user = await this.usersRepository.findOne({ + where: { username }, + relations: { results: true } }) - if (!user) return null; - else return user; + if (user == null) return null + else return user } async findUser (ftId: number): Promise { @@ -75,19 +75,43 @@ export class UsersService { friends: true } }) - if (user == null) return [] - return user.friends + if (user != null) return user.friends + return [] } - async getInvits (ftId: number) { + async getInvits (ftId: number): Promise { const user = await this.usersRepository.findOne({ where: { ftId }, relations: { followers: true } }) - if (user == null) return null - return user.followers + if (user != null) return user.followers + return [] + } + + async getResults (ftId: number): Promise { + const user = await this.usersRepository.findOne({ + where: { ftId }, + relations: { + results: true + } + }) + if (user != null) return user.results + return [] + } + + async getLeader (): Promise { + return await this.usersRepository.find({ + order: { + winrate: 'DESC' + } + }) + } + + async getRank (ftId: number): Promise { + const leader = await this.getLeader() + return leader.findIndex((user) => user.ftId == ftId) } async invit (ftId: number, targetFtId: number) { @@ -95,18 +119,20 @@ export class UsersService { where: { ftId }, relations: { followers: true, - friends: true, + friends: true } }) - if (user == null) return null - if (user.friends.findIndex( - (friend) => friend.ftId === targetFtId) != -1) + if (user == null) { + return new NotFoundException(`Error: user id ${ftId} isn't in our db.`) + } + if (user.friends.findIndex((friend) => friend.ftId === targetFtId) != -1) { return null + } const target = await this.usersRepository.findOne({ where: { ftId: targetFtId }, relations: { followers: true, - friends: true, + friends: true } }) if (target == null) { @@ -122,8 +148,7 @@ export class UsersService { `Friend relation complete between ${user.username} and ${target.username}` ) user.friends.push(target) - if (user != target) - target.friends.push(user) + if (user != target) target.friends.push(user) user.followers.slice(id, 1) this.usersRepository.save(user) } else { diff --git a/front/volume/src/App.svelte b/front/volume/src/App.svelte index ff3883e..88f4678 100644 --- a/front/volume/src/App.svelte +++ b/front/volume/src/App.svelte @@ -169,7 +169,7 @@ username={$store.username} wins={$store.wins} losses={$store.looses} - winrate={$store.matchs? $store.wins / $store.matchs : 0} + winrate={$store.winrate} rank={23} is2faEnabled={false} /> diff --git a/front/volume/src/components/Pong/Pong.svelte b/front/volume/src/components/Pong/Pong.svelte index a6fb0dc..54b39bc 100644 --- a/front/volume/src/components/Pong/Pong.svelte +++ b/front/volume/src/components/Pong/Pong.svelte @@ -8,7 +8,7 @@ import SpectateFriend from "./SpectateFriend.svelte"; import Matchmaking from "./Matchmaking.svelte"; import type { MatchmakingDto } from "./dtos/MatchmakingDto"; - import { store } from '../../Auth' + import { store } from "../../Auth"; const SERVER_URL = `ws://${import.meta.env.VITE_HOST}:${ import.meta.env.VITE_BACK_PORT diff --git a/front/volume/src/components/Profile.svelte b/front/volume/src/components/Profile.svelte index 4bac292..f8c9529 100644 --- a/front/volume/src/components/Profile.svelte +++ b/front/volume/src/components/Profile.svelte @@ -37,9 +37,13 @@
-
-
+ +