Browse Source

new leader and history routes

master
nicolas-arnaud 2 years ago
parent
commit
dae40b3630
  1. 19
      README.md
  2. 12
      back/volume/src/pong/entity/result.entity.ts
  3. 5
      back/volume/src/pong/game/Game.ts
  4. 7
      back/volume/src/pong/game/Games.ts
  5. 4
      back/volume/src/pong/pong.gateway.ts
  6. 11
      back/volume/src/pong/pong.module.ts
  7. 56
      back/volume/src/pong/pong.service.ts
  8. 1
      back/volume/src/users/dto/user.dto.ts
  9. 11
      back/volume/src/users/entity/user.entity.ts
  10. 65
      back/volume/src/users/users.controller.ts
  11. 9
      back/volume/src/users/users.module.ts
  12. 65
      back/volume/src/users/users.service.ts
  13. 2
      front/volume/src/App.svelte
  14. 2
      front/volume/src/components/Pong/Pong.svelte
  15. 10
      front/volume/src/components/Profile.svelte

19
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:

12
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<User | null> // TODO: change to User[] for final version
@Column('text', {array: true})
public score: number[]
@Column('text', { array: true })
public score: number[]
@CreateDateColumn()
date: Date
}

5
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<void> {
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)

7
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<string, number>()
private readonly games = new Array<Game>()
@ -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)

4
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<WebSocketWithId, string>()

11
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]
})

56
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<Result>,
constructor (
@InjectRepository(Result)
private readonly resultsRepository: Repository<Result>,
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<User | null> => {
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<User | null> => {
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<Result[]> {
return await this.resultsRepository.find({
order: { date: 'DESC' }
})
}
async getHistoryById (ftId: number): Promise<Result[]> {
const results = await this.usersService.getResults(ftId)
return results.sort((a, b) => (a.date < b.date ? 1 : -1))
}
}

1
back/volume/src/users/dto/user.dto.ts

@ -14,7 +14,6 @@ export class UserDto {
@IsOptional()
readonly status: string
}
export class AvatarUploadDto {

11
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[]

65
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<User[]> {
async getAllUsers(): Promise<User[]> {
return await this.usersService.findUsers()
}
@Get('online')
async getOnlineUsers (): Promise<User[]> {
async getOnlineUsers(): Promise<User[]> {
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<User | null> {
async getUserByName(@Param('name') username: string): Promise<User | null> {
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<User | null> {
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<User | null> {
async getUser(@FtUser() profile: Profile): Promise<User | null> {
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)

9
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 { }

65
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<User>
@InjectRepository(User) private readonly usersRepository: Repository<User>,
) {}
save(user: User) {
save (user: User) {
this.usersRepository.save(user)
}
@ -20,13 +21,12 @@ export class UsersService {
}
async findUserByName (username: string): Promise<User | null> {
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<User | null> {
@ -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<User[]> {
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<Result[]> {
const user = await this.usersRepository.findOne({
where: { ftId },
relations: {
results: true
}
})
if (user != null) return user.results
return []
}
async getLeader (): Promise<User[]> {
return await this.usersRepository.find({
order: {
winrate: 'DESC'
}
})
}
async getRank (ftId: number): Promise<number> {
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 {

2
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}
/>

2
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

10
front/volume/src/components/Profile.svelte

@ -37,9 +37,13 @@
<div class="overlay">
<div class="profile" on:click|stopPropagation on:keydown|stopPropagation>
<div class="profile-header">
<form action={API_URL + "/avatar"} method="post"
enctype="multipart/form-data" id= "upload_avatar">
<div class=input-avatar>
<form
action={API_URL + "/avatar"}
method="post"
enctype="multipart/form-data"
id="upload_avatar"
>
<div class="input-avatar">
<label for="avatar-input">
<img src={API_URL + "/avatar"} alt="avatar" class="profile-img" />
</label>

Loading…
Cancel
Save