vvandenb 2 years ago
parent
commit
b01afbb5ec
  1. 177
      back/volume/src/chat/chat.controller.ts
  2. 2
      back/volume/src/chat/chat.gateway.ts
  3. 13
      back/volume/src/chat/chat.module.ts
  4. 145
      back/volume/src/chat/chat.service.ts
  5. 13
      back/volume/src/chat/dto/connection.dto.ts
  6. 28
      back/volume/src/chat/dto/create-channel.dto.ts
  7. 12
      back/volume/src/chat/dto/create-message.dto.ts
  8. 13
      back/volume/src/chat/dto/createChannel.dto.ts
  9. 30
      back/volume/src/chat/dto/update-channel.dto.ts
  10. 24
      back/volume/src/chat/dto/updateChannel.dto.ts
  11. 16
      back/volume/src/chat/dto/updateUser.dto.ts
  12. 54
      back/volume/src/chat/entity/channel.entity.ts
  13. 16
      back/volume/src/chat/entity/connection.entity.ts
  14. 15
      back/volume/src/chat/entity/dm.entity.ts
  15. 15
      back/volume/src/chat/entity/message.entity.ts
  16. 16
      back/volume/src/pong/game/Ball.ts
  17. 14
      back/volume/src/pong/game/Game.ts
  18. 33
      back/volume/src/pong/pong.controller.ts
  19. 2
      back/volume/src/pong/pong.gateway.ts
  20. 2
      back/volume/src/pong/pong.module.ts
  21. 15
      back/volume/src/pong/pong.service.ts
  22. 6
      back/volume/src/users/entity/user.entity.ts
  23. 31
      back/volume/src/users/users.controller.ts
  24. 27
      back/volume/src/users/users.service.ts
  25. 13
      front/volume/src/App.svelte
  26. 4
      front/volume/src/Auth.ts
  27. 2
      front/volume/src/FakeLogin.svelte
  28. 147
      front/volume/src/components/Channels.svelte
  29. 64
      front/volume/src/components/Chat.svelte
  30. 7
      front/volume/src/components/Friends.svelte
  31. 2
      front/volume/src/components/Leaderboard.svelte
  32. 8
      front/volume/src/components/MatchHistory.svelte
  33. 2
      front/volume/src/components/NavBar.svelte
  34. 2
      front/volume/src/components/Pong/Paddle.ts
  35. 1
      front/volume/src/components/Pong/Pong.svelte
  36. 20
      front/volume/src/components/Profile.svelte

177
back/volume/src/chat/chat.controller.ts

@ -0,0 +1,177 @@
import {
BadRequestException,
Body,
Controller,
Delete,
Get,
NotFoundException,
Param,
ParseIntPipe,
Post,
UseGuards
} from '@nestjs/common'
import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import { UsersService } from 'src/users/users.service'
import { ChannelService } from './chat.service'
import { CreateChannelDto } from './dto/create-channel.dto'
import { IdDto, PasswordDto, MuteDto } from './dto/updateUser.dto'
import type User from 'src/users/entity/user.entity'
import type Channel from './entity/channel.entity'
import { Profile42 } from 'src/auth/42.decorator'
import { Profile } from 'passport-42'
@Controller('channels')
export class ChatController {
constructor (
private readonly channelService: ChannelService,
private readonly usersService: UsersService
) {}
@Post(':id/invite')
@UseGuards(AuthenticatedGuard)
async addUser (
@Param('id') id: number, @Body() target: IdDto,
@Profile42() profile: Profile) {
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id)
if (user == null) throw new NotFoundException(`User #${target.id} not found`)
if (!await this.channelService.isUser(channel.id, +profile.id))
throw new BadRequestException('You are not allowed to invite users to this channel')
if (await this.channelService.isUser(channel.id, target.id))
throw new BadRequestException('User is already in this channel')
if (await this.channelService.isBanned(channel.id, target.id))
throw new BadRequestException('User is banned from this channel')
channel.users.push(user)
this.channelService.save(channel)
}
@Delete(':id/kick')
@UseGuards(AuthenticatedGuard)
async removeUser (
@Param('id') id: number, @Body() target: IdDto,
@Profile42() profile: Profile) {
const channel = await this.channelService.getFullChannel(id)
if (!await this.channelService.isAdmin(channel.id, +profile.id))
throw new BadRequestException('You are not allowed to kick users from this channel')
if (!await this.channelService.isUser(channel.id, target.id))
throw new BadRequestException('User is not in this channel')
if (await this.channelService.isOwner(channel.id, target.id))
throw new BadRequestException('You cannot kick the owner of the channel')
channel.users = channel.users.filter((usr: User) => {
return usr.ftId !== target.id
})
this.channelService.save(channel)
}
@Post(':id/admin')
@UseGuards(AuthenticatedGuard)
async addAdmin (
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile) {
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id)
if (user == null) throw new NotFoundException(`User #${target.id} not found`)
if (!await this.channelService.isOwner(channel.id, +profile.id))
throw new BadRequestException('You are not the owner of this channel')
if (!await this.channelService.isUser(channel.id, target.id))
throw new BadRequestException('User is not in this channel')
if (await this.channelService.isAdmin(channel.id, target.id))
throw new BadRequestException('User is already an admin of this channel')
channel.admins.push(user)
this.channelService.save(channel)
}
@Delete(':id/admin')
@UseGuards(AuthenticatedGuard)
async removeAdmin (
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile) {
const channel = await this.channelService.getFullChannel(id)
if (!await this.channelService.isOwner(channel.id, +profile.id))
throw new BadRequestException('You are not the owner of this channel')
if (!await this.channelService.isAdmin(channel.id, target.id))
throw new BadRequestException('User is not an admin of this channel')
channel.admins = channel.admins.filter((usr: User) => {
return usr.ftId !== target.id
})
this.channelService.save(channel)
}
@Post(':id/ban')
@UseGuards(AuthenticatedGuard)
async addBan (
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile) {
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id)
if (user == null) throw new NotFoundException(`User #${target.id} not found`)
if (!await this.channelService.isAdmin(channel.id, +profile.id))
throw new BadRequestException('You are not allowed to ban users from this channel')
if (await this.channelService.isOwner(channel.id, target.id))
throw new BadRequestException('You cannot ban the owner of the channel')
if (await this.channelService.isBanned(channel.id, target.id))
throw new BadRequestException('User is already banned from this channel')
channel.banned.push(user)
this.channelService.save(channel)
}
@Post(':id/mute')
@UseGuards(AuthenticatedGuard)
async addMute (
@Param('id') id: number,
@Body() mute: MuteDto, // [userId, duration]
@Profile42() profile: Profile) {
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(mute.data[0])
if (user == null) throw new NotFoundException(`User #${mute.data[0]} not found`)
if (!await this.channelService.isAdmin(channel.id, +profile.id))
throw new BadRequestException('You are not allowed to mute users from this channel')
if (await this.channelService.isOwner(channel.id, mute.data[0]))
throw new BadRequestException('You cannot mute the owner of the channel')
if (await this.channelService.getMuteDuration(channel.id, mute.data[0]) > 0)
throw new BadRequestException('User is already muted from this channel')
channel.muted.push(mute.data)
this.channelService.save(channel)
}
@Delete(':id')
@UseGuards(AuthenticatedGuard)
async deleteChannel (
@Profile42() profile: Profile,
@Param('id') id: number
) {
if (!await this.channelService.isOwner(id, +profile.id))
throw new BadRequestException('You are not the owner of this channel')
await this.channelService.removeChannel(id)
return
}
@Post(':id/password')
@UseGuards(AuthenticatedGuard)
async updatePassword (
@Profile42() profile: Profile,
@Param('id') id: number,
@Body() data: PasswordDto
) {
if (await this.channelService.isOwner(id, +profile.id))
throw new BadRequestException('You are not the owner of this channel')
await this.channelService.updatePassword(id, data.password)
}
@Get()
@UseGuards(AuthenticatedGuard)
async getChannelsForUser (@Profile42() profile: Profile): Promise<Channel[]> {
return await this.channelService.getChannelsForUser(+profile.id)
}
@Post()
async createChannel (@Body() channel: CreateChannelDto) {
return await this.channelService.createChannel(channel)
}
}

2
back/volume/src/chat/chat.gateway.ts

@ -1,3 +1,4 @@
/*
import { UnauthorizedException, UseGuards } from '@nestjs/common' import { UnauthorizedException, UseGuards } from '@nestjs/common'
import { import {
type OnGatewayConnection, type OnGatewayConnection,
@ -99,3 +100,4 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
/// TODO: Send message to users /// TODO: Send message to users
} }
} }
*/

13
back/volume/src/chat/chat.module.ts

@ -3,10 +3,12 @@ import { TypeOrmModule } from '@nestjs/typeorm'
import { AuthModule } from 'src/auth/auth.module' import { AuthModule } from 'src/auth/auth.module'
import { UsersModule } from 'src/users/users.module' import { UsersModule } from 'src/users/users.module'
import { ChatGateway } from './chat.gateway' // import { ChatGateway } from './chat.gateway'
import { ChatService } from './chat.service' import { ChatController } from './chat.controller'
import { Channel } from './entity/channel.entity' import { ChannelService } from './chat.service'
import { Message } from './entity/message.entity'
import Channel from './entity/channel.entity'
import Message from './entity/message.entity'
@Module({ @Module({
imports: [ imports: [
@ -15,6 +17,7 @@ import { Message } from './entity/message.entity'
TypeOrmModule.forFeature([Channel]), TypeOrmModule.forFeature([Channel]),
TypeOrmModule.forFeature([Message]) TypeOrmModule.forFeature([Message])
], ],
providers: [ChatGateway, ChatService] controllers: [ChatController],
providers: [ChannelService]
}) })
export class ChatModule {} export class ChatModule {}

145
back/volume/src/chat/chat.service.ts

@ -1,56 +1,139 @@
import { Injectable } from '@nestjs/common' import { Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm' import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm' import { Repository } from 'typeorm'
import { type User } from 'src/users/entity/user.entity'
import { Channel } from './entity/channel.entity' import { type CreateChannelDto } from './dto/create-channel.dto'
import { Message } from './entity/message.entity' import { UsersService } from 'src/users/users.service'
import type User from 'src/users/entity/user.entity'
import Channel from './entity/channel.entity'
@Injectable() @Injectable()
export class ChatService { export class ChannelService {
constructor ( constructor (
@InjectRepository(Channel) @InjectRepository(Channel)
private readonly ChannelRepository: Repository<Channel>, private readonly ChannelRepository: Repository<Channel>,
@InjectRepository(Message) private readonly usersService: UsersService
private readonly MessageRepository: Repository<Message>
) {} ) {}
async createChannel (Channel: Channel, creator: User): Promise<Channel> { async createChannel (channel: CreateChannelDto): Promise<Channel> {
const newChannel = await this.addCreatorToChannel(Channel, creator) const user: User | null = await this.usersService.findUser(channel.owner)
if (user == null)
throw new NotFoundException(`User #${channel.owner} not found`)
const newChannel = new Channel()
newChannel.owner = user
newChannel.users = [user]
newChannel.admins = [user]
newChannel.name = channel.name
newChannel.isPrivate = channel.isPrivate
newChannel.password = channel.password
return await this.ChannelRepository.save(newChannel) return await this.ChannelRepository.save(newChannel)
} }
async getChannelsForUser (userId: number): Promise<Channel[]> { async updatePassword (id: number, password: string) {
return await this.ChannelRepository.find({}) // where userId is in User[] of channel? let channel: Channel | null = await this.ChannelRepository.findOneBy({id})
if (channel === null) { throw new NotFoundException(`Channel #${id} not found`) }
channel.password = password
await this.ChannelRepository.save(channel)
}
async getChannelsForUser (ftId: number): Promise<Channel[]> {
let rooms: Channel[] = []
rooms = [
...(await this.ChannelRepository.createQueryBuilder('room')
.where('room.isPrivate = false')
.getMany())
]
rooms = [
...rooms,
...(await this.ChannelRepository.createQueryBuilder('room')
.innerJoin('room.users', 'users')
.where('room.isPrivate = true')
.andWhere('users.ftId = :ftId', { ftId })
.getMany())
]
return rooms
}
async addUserToChannel (channel: Channel, user: User): Promise<Channel> {
channel.owner = user
return await this.ChannelRepository.save(channel)
}
async getChannel (id: number): Promise<Channel> {
const channel = await this.ChannelRepository.findOneBy({ id })
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel
}
async getFullChannel (id: number): Promise<Channel> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: ['users', 'admins', 'banned', 'muted', 'owner']
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel
}
async update (channel: Channel) {
await this.ChannelRepository.update(channel.id, channel)
}
async save (channel: Channel) {
await this.ChannelRepository.save(channel)
}
async removeChannel (channelId: number) {
await this.ChannelRepository.delete(channelId)
} }
async addCreatorToChannel (Channel: Channel, creator: User): Promise<Channel> { async isOwner (id: number, userId: number): Promise<boolean> {
Channel.users.push(creator) const channel = await this.ChannelRepository.findOne({
return Channel where: { id },
relations: { owner: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.owner.ftId === userId
} }
async createMessage (message: Message): Promise<Message> { async isAdmin (id: number, userId: number): Promise<boolean> {
return await this.MessageRepository.save( const channel = await this.ChannelRepository.findOne({
this.MessageRepository.create(message) where: { id },
) relations: { admins: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.admins.findIndex((user) => user.ftId === userId) != -1
} }
async deleteBySocketId (socketId: string) { async isUser (id: number, userId: number): Promise<boolean> {
return await this.ChannelRepository.delete({}) // for disconnect const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { users: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.users.findIndex((user) => user.ftId === userId) != -1
} }
async getChannel (id: number): Promise<Channel | null> { async isBanned (id: number, userId: number): Promise<boolean> {
return await this.ChannelRepository.findOneBy({ id }) const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { banned: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.banned.findIndex((user) => user.ftId === userId) != -1
} }
async findMessagesInChannelForUser ( async getMuteDuration (id: number, userId: number): Promise<number> {
channel: Channel, const channel = await this.ChannelRepository.findOne({
user: User where: { id },
): Promise<Message[]> { relations: { muted: true }
return await this.MessageRepository.createQueryBuilder('message')
.where('message.channel = :chan', { chan: channel })
.andWhere('message.author NOT IN (:...blocked)', {
blocked: user.blocked
}) })
.getMany() if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
const mutation: Array<number> | undefined = channel.muted.find((mutation) => mutation[0] === userId)
if (mutation == null) { return 0 }
return mutation[1]
} }
} }

13
back/volume/src/chat/dto/connection.dto.ts

@ -0,0 +1,13 @@
import { IsNumber, IsOptional, IsString } from 'class-validator'
export class ConnectionDto {
@IsNumber()
UserId: number
@IsNumber()
ChannelId: number
@IsString()
@IsOptional()
pwd: string
}

28
back/volume/src/chat/dto/create-channel.dto.ts

@ -0,0 +1,28 @@
import { Transform } from 'class-transformer'
import {
IsPositive,
IsAlpha,
IsString,
IsOptional,
IsNumber,
IsBoolean
} from 'class-validator'
export class CreateChannelDto {
@IsOptional()
@IsPositive()
id: number
@IsString()
name: string
@IsNumber()
owner: number
@IsOptional()
password: string
@IsBoolean()
@Transform(({ value }) => value === 'true')
isPrivate: boolean
}

12
back/volume/src/chat/dto/create-message.dto.ts

@ -0,0 +1,12 @@
import { IsNumber, IsString } from 'class-validator'
export class CreateMessageDto {
@IsString()
text: string
@IsNumber()
UserId: number
@IsNumber()
ChannelId: number
}

13
back/volume/src/chat/dto/createChannel.dto.ts

@ -1,13 +0,0 @@
import { IsPositive, IsAlpha, IsString, IsOptional } from 'class-validator'
export class CreateChannelDto {
@IsString()
@IsAlpha()
name: string
@IsPositive()
owner: number
@IsOptional()
password: string
}

30
back/volume/src/chat/dto/update-channel.dto.ts

@ -0,0 +1,30 @@
import { PartialType } from '@nestjs/mapped-types'
import { CreateChannelDto } from './create-channel.dto'
import { IsNumber, IsOptional, IsString } from 'class-validator'
export class UpdateChannelDto extends PartialType(CreateChannelDto) {
id: number
@IsOptional()
@IsNumber()
users: [number]
@IsOptional()
@IsNumber()
messages: [number]
@IsOptional()
@IsNumber()
owners: [number] // user id
@IsOptional()
@IsNumber()
banned: [number] // user id
@IsOptional()
@IsNumber()
muted: [number] // user id
@IsString()
@IsOptional()
password: string
}

24
back/volume/src/chat/dto/updateChannel.dto.ts

@ -1,24 +0,0 @@
import { PartialType } from '@nestjs/mapped-types'
import { CreateChannelDto } from './createChannel.dto'
import { type Message } from '../entity/message.entity'
import { type User } from 'src/users/entity/user.entity'
import { IsString } from 'class-validator'
export class UpdateChannelDto extends PartialType(CreateChannelDto) {
id: number
users: [User]
messages: [Message]
owners: [number] // ftId
admins: [number]
banned: [number] // ftId
muted: [number] // ftId
@IsString()
password: string
}

16
back/volume/src/chat/dto/updateUser.dto.ts

@ -0,0 +1,16 @@
import { IsNumber, IsString} from 'class-validator'
export class IdDto {
@IsNumber()
id: number
}
export class PasswordDto {
@IsString()
password: string
}
export class MuteDto {
data: Array<number>
}

54
back/volume/src/chat/entity/channel.entity.ts

@ -2,27 +2,40 @@ import {
BeforeInsert, BeforeInsert,
Column, Column,
Entity, Entity,
JoinColumn,
JoinTable, JoinTable,
ManyToMany, ManyToMany,
ManyToOne,
OneToMany, OneToMany,
OneToOne,
PrimaryGeneratedColumn PrimaryGeneratedColumn
} from 'typeorm' } from 'typeorm'
import User from 'src/users/entity/user.entity'
import Message from './message.entity'
import * as bcrypt from 'bcrypt' import * as bcrypt from 'bcrypt'
import { User } from 'src/users/entity/user.entity'
import { Message } from './message.entity'
@Entity() @Entity()
export class Channel { export default class Channel {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number id: number
@Column() @Column()
name: string name: string
@ManyToMany(() => User) @Column({ default: false })
@JoinTable() isPrivate: boolean
owner: User
@Column({ select: false, default: '' })
password: string
@BeforeInsert()
async hashPassword () {
if (this.password === '') return
this.password = await bcrypt.hash(
this.password,
Number(process.env.HASH_SALT)
)
}
@ManyToMany(() => User) @ManyToMany(() => User)
@JoinTable() @JoinTable()
@ -31,22 +44,19 @@ export class Channel {
@OneToMany(() => Message, (message: Message) => message.channel) @OneToMany(() => Message, (message: Message) => message.channel)
messages: Message[] messages: Message[]
@OneToMany(() => User, (user: User) => user.id) // refuse connection @ManyToOne(() => User)
banned: User[] @JoinColumn()
owner: User
@OneToMany(() => User, (user: User) => user.id) // refuse post @ManyToMany(() => User)
muted: User[] @JoinTable()
admins: User[]
@Column({ select: false }) @ManyToMany(() => User) // refuse connection
password: string @JoinTable()
banned: User[]
@BeforeInsert() @ManyToMany(() => User) // refuse post
async hashPassword () { @JoinTable()
this.password = await bcrypt.hash( muted: Array<Array<number>>
this.password,
Number(process.env.HASH_SALT)
)
}
} }
export default Channel

16
back/volume/src/chat/entity/connection.entity.ts

@ -0,0 +1,16 @@
import { Column, Entity, OneToOne } from 'typeorm'
import Channel from './channel.entity'
import User from 'src/users/entity/user.entity'
@Entity()
export class connectedUser {
@OneToOne(() => User)
user: User
@OneToOne(() => Channel, (channel) => channel.id)
channel: Channel
@Column()
socket: string
}

15
back/volume/src/chat/entity/dm.entity.ts

@ -0,0 +1,15 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
import Message from './message.entity'
import type User from 'src/users/entity/user.entity'
@Entity()
export class Channel {
@PrimaryGeneratedColumn()
id: number
@Column()
users: User[]
@OneToMany(() => Message, (message) => message.channel)
messages: Message[]
}

15
back/volume/src/chat/entity/message.entity.ts

@ -7,28 +7,25 @@ import {
ManyToOne, ManyToOne,
PrimaryGeneratedColumn PrimaryGeneratedColumn
} from 'typeorm' } from 'typeorm'
import User from 'src/users/entity/user.entity'
import { User } from 'src/users/entity/user.entity' import Channel from './channel.entity'
import { Channel } from './channel.entity'
@Entity() @Entity()
export class Message { export default class Message {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number id: number
@Column() @Column()
text: string text: string
@ManyToOne(() => User, (author: User) => author.messages) @ManyToOne(() => User)
@JoinColumn() @JoinColumn()
author: User author: User
@ManyToOne(() => Channel, (channel: Channel) => channel.messages) @ManyToOne(() => Channel, (channel) => channel.messages, { cascade: true })
@JoinTable() @JoinTable()
channel: Channel channel: Channel
@CreateDateColumn() @CreateDateColumn()
createdAt: Date created_at: Date
} }
export default Message

16
back/volume/src/pong/game/Ball.ts

@ -20,7 +20,7 @@ export class Ball {
constructor ( constructor (
spawn: Point, spawn: Point,
size: Point = DEFAULT_BALL_SIZE, size: Point = DEFAULT_BALL_SIZE,
speed: Point = DEFAULT_BALL_INITIAL_SPEED speed: Point = DEFAULT_BALL_INITIAL_SPEED.clone()
) { ) {
this.rect = new Rect(spawn, size) this.rect = new Rect(spawn, size)
this.speed = speed this.speed = speed
@ -93,21 +93,23 @@ export class Ball {
playerScored (): number { playerScored (): number {
let indexPlayerScored: number let indexPlayerScored: number
if (this.rect.center.x <= this.spawn.x) { if (this.rect.center.x <= this.spawn.x) {
indexPlayerScored = 1 indexPlayerScored = 1
this.speed.x = this.initial_speed.x
} else { } else {
indexPlayerScored = 0 indexPlayerScored = 0
this.speed.x = -this.initial_speed.x
} }
this.rect.center = this.spawn.clone() if (this.speed.y < 0) {
if (this.speed.x < 0) {
this.speed.x = this.initial_speed.x
this.speed.y = -this.initial_speed.y
} else {
this.speed.x = -this.initial_speed.x
this.speed.y = this.initial_speed.y this.speed.y = this.initial_speed.y
} else {
this.speed.y = -this.initial_speed.y
} }
this.rect.center = this.spawn.clone()
return indexPlayerScored return indexPlayerScored
} }
} }

14
back/volume/src/pong/game/Game.ts

@ -21,7 +21,6 @@ export class Game {
map: MapDtoValidated map: MapDtoValidated
ball: Ball ball: Ball
players: Player[] = [] players: Player[] = []
playing: boolean
ranked: boolean ranked: boolean
waitingForTimeout: boolean waitingForTimeout: boolean
gameStoppedCallback: (name: string) => void gameStoppedCallback: (name: string) => void
@ -37,7 +36,6 @@ export class Game {
) { ) {
this.id = randomUUID() this.id = randomUUID()
this.timer = null this.timer = null
this.playing = false
this.ranked = ranked this.ranked = ranked
this.waitingForTimeout = false this.waitingForTimeout = false
this.map = map this.map = map
@ -99,7 +97,6 @@ export class Game {
void this.pongService.setInGame(p.name) void this.pongService.setInGame(p.name)
p.newGame() p.newGame()
}) })
this.playing = true
this.broadcastGame(GAME_EVENTS.START_GAME) this.broadcastGame(GAME_EVENTS.START_GAME)
this.timer = setInterval(this.gameLoop.bind(this), 1000 / GAME_TICKS) this.timer = setInterval(this.gameLoop.bind(this), 1000 / GAME_TICKS)
console.log(`Game ${this.id} starting in 3 seconds`) console.log(`Game ${this.id} starting in 3 seconds`)
@ -111,12 +108,12 @@ export class Game {
} }
stop (): void { stop (): void {
if (this.timer !== null && this.playing) { if (this.timer !== null) {
this.playing = false
clearInterval(this.timer) clearInterval(this.timer)
}
this.timer = null this.timer = null
this.pongService this.pongService
.saveResult(this.players, this.ranked) .saveResult(this.players, this.ranked, DEFAULT_WIN_SCORE)
.then(() => { .then(() => {
this.gameStoppedCallback(this.players[0].name) this.gameStoppedCallback(this.players[0].name)
this.players = [] this.players = []
@ -126,7 +123,6 @@ export class Game {
this.players = [] this.players = []
}) })
} }
}
movePaddle (name: string | undefined, position: Point): void { movePaddle (name: string | undefined, position: Point): void {
const playerIndex: number = this.players.findIndex((p) => p.name === name) const playerIndex: number = this.players.findIndex((p) => p.name === name)
@ -142,10 +138,6 @@ export class Game {
}) })
} }
isPlaying (): boolean {
return this.playing
}
private gameLoop (): void { private gameLoop (): void {
if (this.waitingForTimeout) { if (this.waitingForTimeout) {
return return

33
back/volume/src/pong/pong.controller.ts

@ -0,0 +1,33 @@
import {
Controller,
Get,
Param,
ParseIntPipe,
UseGuards
} from '@nestjs/common'
import { Paginate, type Paginated, PaginateQuery } from 'nestjs-paginate'
import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import type Result from './entity/result.entity'
import { PongService } from './pong.service'
@Controller('results')
export class PongController {
constructor (private readonly pongService: PongService) {}
@Get('global')
@UseGuards(AuthenticatedGuard)
async getGlobalHistory (
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, 0)
}
@Get(':id')
@UseGuards(AuthenticatedGuard)
async getHistoryById (
@Param('id', ParseIntPipe) id: number,
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, id)
}
}

2
back/volume/src/pong/pong.gateway.ts

@ -46,7 +46,7 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
): void { ): void {
const name: string | undefined = this.socketToPlayerName.get(client) const name: string | undefined = this.socketToPlayerName.get(client)
const game: Game | undefined = this.games.playerGame(name) const game: Game | undefined = this.games.playerGame(name)
if (game?.isPlaying() !== undefined) { if (game !== undefined) {
game.stop() game.stop()
} }
if (name !== undefined) { if (name !== undefined) {

2
back/volume/src/pong/pong.module.ts

@ -4,10 +4,12 @@ import Result from './entity/result.entity'
import { TypeOrmModule } from '@nestjs/typeorm' import { TypeOrmModule } from '@nestjs/typeorm'
import { PongService } from './pong.service' import { PongService } from './pong.service'
import { UsersModule } from 'src/users/users.module' import { UsersModule } from 'src/users/users.module'
import { PongController } from './pong.controller'
@Module({ @Module({
imports: [forwardRef(() => UsersModule), TypeOrmModule.forFeature([Result])], imports: [forwardRef(() => UsersModule), TypeOrmModule.forFeature([Result])],
providers: [PongGateway, PongService], providers: [PongGateway, PongService],
controllers: [PongController],
exports: [PongService] exports: [PongService]
}) })
export class PongModule {} export class PongModule {}

15
back/volume/src/pong/pong.service.ts

@ -15,18 +15,17 @@ export class PongService {
private readonly usersService: UsersService private readonly usersService: UsersService
) {} ) {}
async updateStats (player: User, i: number, result: Result): Promise<void> { async updateStats (player: User, i: number, result: Result, maxScore: number): Promise<void> {
player.matchs++ player.matchs++
if (result.score[i] > result.score[Math.abs(i - 1)]) player.wins++ if (result.score[i] === maxScore) player.wins++
else player.looses++ else player.looses++
player.winrate = (100 * player.wins) / player.matchs player.winrate = (100 * player.wins) / player.matchs
player.rank = (await this.usersService.getRank(player.ftId)) + 1
} }
async updatePlayer (i: number, result: Result): Promise<void> { async updatePlayer (i: number, result: Result, maxScore: number): Promise<void> {
const player: User | null = result.players[i] const player: User | null = result.players[i]
if (player == null) return if (player == null) return
if (result.ranked) await this.updateStats(player, i, result) if (result.ranked) await this.updateStats(player, i, result, maxScore)
player.results.push(result) player.results.push(result)
player.status = 'online' player.status = 'online'
await this.usersService.save(player) await this.usersService.save(player)
@ -38,7 +37,7 @@ export class PongService {
await this.usersService.save(player) await this.usersService.save(player)
} }
async saveResult (players: Player[], ranked: boolean): Promise<void> { async saveResult (players: Player[], ranked: boolean, maxScore: number): Promise<void> {
const result = new Result() const result = new Result()
const ply = new Array<User | null>() const ply = new Array<User | null>()
ply.push(await this.usersService.findUserByName(players[0].name)) ply.push(await this.usersService.findUserByName(players[0].name))
@ -47,8 +46,8 @@ export class PongService {
result.players = ply result.players = ply
result.score = [players[0].score, players[1].score] result.score = [players[0].score, players[1].score]
await this.resultsRepository.save(result) await this.resultsRepository.save(result)
await this.updatePlayer(0, result) await this.updatePlayer(0, result, maxScore)
await this.updatePlayer(1, result) await this.updatePlayer(1, result, maxScore)
} }
async getHistory ( async getHistory (

6
back/volume/src/users/entity/user.entity.ts

@ -65,12 +65,6 @@ export class User {
@JoinTable() @JoinTable()
results: Result[] results: Result[]
@OneToMany(() => Message, (message: Message) => message.author)
messages: Message[]
@ManyToMany(() => Channel, (channel: Channel) => channel.users)
rooms: Channel[]
@ManyToMany(() => User) @ManyToMany(() => User)
@JoinTable() @JoinTable()
blocked: User[] blocked: User[]

31
back/volume/src/users/users.controller.ts

@ -13,7 +13,6 @@ import {
BadRequestException, BadRequestException,
Redirect Redirect
} from '@nestjs/common' } from '@nestjs/common'
import { PaginateQuery, type Paginated, Paginate } from 'nestjs-paginate'
import { FileInterceptor } from '@nestjs/platform-express' import { FileInterceptor } from '@nestjs/platform-express'
import { diskStorage } from 'multer' import { diskStorage } from 'multer'
@ -31,13 +30,11 @@ import { ApiBody, ApiConsumes } from '@nestjs/swagger'
import { type Request, Response } from 'express' import { type Request, Response } from 'express'
import { createReadStream } from 'fs' import { createReadStream } from 'fs'
import { join } from 'path' import { join } from 'path'
import type Result from 'src/pong/entity/result.entity'
@Controller() @Controller("users")
export class UsersController { export class UsersController {
constructor ( constructor (
private readonly usersService: UsersService, private readonly usersService: UsersService,
private readonly pongService: PongService
) {} ) {}
@Get('all') @Get('all')
@ -68,29 +65,11 @@ export class UsersController {
return await this.usersService.getLeaderboard() return await this.usersService.getLeaderboard()
} }
@Get('rank/:id') @Get(':id/rank')
@UseGuards(AuthenticatedGuard) @UseGuards(AuthenticatedGuard)
async getRank (@Param('id', ParseIntPipe) id: number): Promise<number> { async getRank (@Param('id', ParseIntPipe) id: number): Promise<number> {
return await this.usersService.getRank(id) return await this.usersService.getRank(id)
} }
@Get('globalHistory')
@UseGuards(AuthenticatedGuard)
async getGlobalHistory (
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, 0)
}
@Get('history/:id')
@UseGuards(AuthenticatedGuard)
async getHistoryById (
@Param('id', ParseIntPipe) id: number,
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, id)
}
@Post('avatar') @Post('avatar')
@UseGuards(AuthenticatedGuard) @UseGuards(AuthenticatedGuard)
@Redirect('http://localhost') @Redirect('http://localhost')
@ -130,7 +109,7 @@ export class UsersController {
return await this.getAvatarById(profile.id, response) return await this.getAvatarById(profile.id, response)
} }
@Get('user/:name') @Get(':name/byname')
async getUserByName (@Param('name') username: string): Promise<User> { async getUserByName (@Param('name') username: string): Promise<User> {
const user = await this.usersService.findUserByName(username) const user = await this.usersService.findUserByName(username)
user.socketKey = '' user.socketKey = ''
@ -149,14 +128,14 @@ export class UsersController {
if (target === null) { if (target === null) {
throw new BadRequestException(`User ${username} not found.`) throw new BadRequestException(`User ${username} not found.`)
} }
if (+profile.id === target.ftId) { if (+profile.id === +target.ftId) {
throw new BadRequestException("You can't invite yourself.") throw new BadRequestException("You can't invite yourself.")
} }
const ret: string = await this.usersService.invit(profile.id, target.ftId) const ret: string = await this.usersService.invit(profile.id, target.ftId)
if (ret !== 'OK') throw new BadRequestException(ret) if (ret !== 'OK') throw new BadRequestException(ret)
} }
@Get('avatar/:id') @Get(':id/avatar')
async getAvatarById ( async getAvatarById (
@Param('id', ParseIntPipe) ftId: number, @Param('id', ParseIntPipe) ftId: number,
@Res({ passthrough: true }) response: Response @Res({ passthrough: true }) response: Response

27
back/volume/src/users/users.service.ts

@ -1,12 +1,13 @@
import { BadRequestException, Catch, Injectable } from '@nestjs/common' import { BadRequestException, Catch, Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm' import { InjectRepository } from '@nestjs/typeorm'
import { EntityNotFoundError, QueryFailedError, Repository } from 'typeorm' import { EntityNotFoundError, QueryFailedError, 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 { Cron } from '@nestjs/schedule' import { Cron } from '@nestjs/schedule'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
import { type UserDto } from './dto/user.dto'
import type Channel from 'src/chat/entity/channel.entity'
import User from './entity/user.entity'
@Injectable() @Injectable()
@Catch(QueryFailedError, EntityNotFoundError) @Catch(QueryFailedError, EntityNotFoundError)
export class UsersService { export class UsersService {
@ -20,9 +21,7 @@ export class UsersService {
async findUsers (): Promise<User[]> { async findUsers (): Promise<User[]> {
const users = await this.usersRepository.find({}) const users = await this.usersRepository.find({})
users.forEach((usr) => { users.forEach((usr) => usr.socketKey = '')
usr.socketKey = ''
})
return users return users
} }
@ -32,6 +31,7 @@ export class UsersService {
relations: { results: true } relations: { results: true }
}) })
if (user == null) throw new BadRequestException('User not found.') if (user == null) throw new BadRequestException('User not found.')
user.rank = (await this.getRank(user.ftId)) + 1;
return user return user
} }
@ -42,9 +42,7 @@ export class UsersService {
if (Date.now() - usr.lastAccess > 60000) { if (Date.now() - usr.lastAccess > 60000) {
usr.isVerified = false usr.isVerified = false
usr.status = 'offline' usr.status = 'offline'
this.usersRepository.save(usr).catch((err) => { this.usersRepository.save(usr).catch((err) => console.log(err))
console.log(err)
})
} }
}) })
} }
@ -52,6 +50,7 @@ export class UsersService {
async findUser (ftId: number): Promise<User | null> { async findUser (ftId: number): Promise<User | null> {
const user = await this.usersRepository.findOneBy({ ftId }) const user = await this.usersRepository.findOneBy({ ftId })
if (user == null) return null if (user == null) return null
user.rank = (await this.getRank(user.ftId)) + 1;
user.lastAccess = Date.now() user.lastAccess = Date.now()
if (user.status === 'offline') user.status = 'online' if (user.status === 'offline') user.status = 'online'
await this.usersRepository.save(user) await this.usersRepository.save(user)
@ -62,9 +61,7 @@ export class UsersService {
const users = await this.usersRepository.find({ const users = await this.usersRepository.find({
where: { status: 'online' } where: { status: 'online' }
}) })
users.forEach((usr) => { users.forEach((usr) => usr.socketKey = '')
usr.socketKey = ''
})
return users return users
} }
@ -101,6 +98,7 @@ export class UsersService {
relations: { friends: true } relations: { friends: true }
}) })
if (user == null) throw new BadRequestException('User not found.') if (user == null) throw new BadRequestException('User not found.')
user.friends.forEach((friend) => friend.socketKey = '')
return user.friends return user.friends
} }
@ -112,6 +110,7 @@ export class UsersService {
} }
}) })
if (user == null) throw new BadRequestException('User not found.') if (user == null) throw new BadRequestException('User not found.')
user.followers.forEach((follower) => follower.socketKey = '')
return user.followers return user.followers
} }
@ -121,7 +120,9 @@ export class UsersService {
winrate: 'DESC' winrate: 'DESC'
} }
}) })
return leaderboard.filter((user) => user.rank !== 0) let ret = leaderboard.filter((user) => user.rank !== 0)
ret.forEach((follower) => follower.socketKey = '')
return ret
} }
async getRank (ftId: number): Promise<number> { async getRank (ftId: number): Promise<number> {

13
front/volume/src/App.svelte

@ -61,7 +61,7 @@
setAppState(APPSTATE.PROFILE); setAppState(APPSTATE.PROFILE);
} }
let profileUsername: string; let profileUsername: string = "";
async function openIdProfile(event: CustomEvent<string>) { async function openIdProfile(event: CustomEvent<string>) {
profileUsername = event.detail; profileUsername = event.detail;
setAppState(APPSTATE.PROFILE_ID); setAppState(APPSTATE.PROFILE_ID);
@ -71,10 +71,6 @@
setAppState(APPSTATE.HISTORY); setAppState(APPSTATE.HISTORY);
} }
async function openIdHistory() {
setAppState(APPSTATE.HISTORY_ID);
}
async function clickFriends() { async function clickFriends() {
setAppState(APPSTATE.FRIENDS); setAppState(APPSTATE.FRIENDS);
} }
@ -86,7 +82,6 @@
function clickChannels() { function clickChannels() {
setAppState(APPSTATE.CHANNELS); setAppState(APPSTATE.CHANNELS);
} }
let channels: Array<ChannelsType> = [];
let selectedChannel: ChannelsType; let selectedChannel: ChannelsType;
const handleSelectChannel = (channel: ChannelsType) => { const handleSelectChannel = (channel: ChannelsType) => {
selectedChannel = channel; selectedChannel = channel;
@ -148,7 +143,7 @@
</div> </div>
{:else} {:else}
<div on:click={resetAppState} on:keydown={resetAppState}> <div on:click={resetAppState} on:keydown={resetAppState}>
<Channels {channels} onSelectChannel={handleSelectChannel} /> <Channels onSelectChannel={handleSelectChannel} />
</div> </div>
{/if} {/if}
{/if} {/if}
@ -177,7 +172,7 @@
{/if} {/if}
{#if appState === APPSTATE.PROFILE} {#if appState === APPSTATE.PROFILE}
<div on:click={resetAppState} on:keydown={resetAppState}> <div on:click={resetAppState} on:keydown={resetAppState}>
<Profile on:view-history={openIdHistory} /> <Profile on:view-history={() => setAppState(APPSTATE.HISTORY_ID)} />
</div> </div>
{/if} {/if}
{#if appState === APPSTATE.PROFILE_ID} {#if appState === APPSTATE.PROFILE_ID}
@ -187,7 +182,7 @@
on:keydown={() => on:keydown={() =>
setAppState(APPSTATE.CHANNELS + "#" + selectedChannel.name)} setAppState(APPSTATE.CHANNELS + "#" + selectedChannel.name)}
> >
<Profile username={profileUsername} on:view-history={openIdHistory} /> <Profile username={profileUsername} on:view-history={() => setAppState(APPSTATE.HISTORY_ID)} />
</div> </div>
{/if} {/if}

4
front/volume/src/Auth.ts

@ -11,8 +11,8 @@ export const API_URL = `http://${import.meta.env.VITE_HOST}:${
import.meta.env.VITE_BACK_PORT import.meta.env.VITE_BACK_PORT
}`; }`;
export async function getUser() { export async function getUser(bypass: boolean = false) {
const res = await fetch(API_URL, { const res = await fetch(API_URL + "/users", {
method: "get", method: "get",
mode: "cors", mode: "cors",
cache: "no-cache", cache: "no-cache",

2
front/volume/src/FakeLogin.svelte

@ -5,7 +5,7 @@
export let ftId; export let ftId;
async function doPost() { async function doPost() {
await fetch(API_URL + "/" + ftId, { await fetch(API_URL + "/users/" + ftId, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",

147
front/volume/src/components/Channels.svelte

@ -1,34 +1,31 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import type { chatMessagesType } from "./Chat.svelte";
export interface ChannelsType { export interface ChannelsType {
id: string; id: number;
name: string; name: string;
privacy: string; isPrivate: boolean;
password: string; password: string;
owner: string; owner: number;
} }
import { onMount } from "svelte"; import { onMount } from "svelte";
import { API_URL, store } from "../Auth"; import { API_URL, store } from "../Auth";
import { dataset_dev } from "svelte/internal";
</script> </script>
<script lang="ts"> <script lang="ts">
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
export let channels: Array<ChannelsType> = []; let channels: Array<ChannelsType> = [];
// onMount(async () => { onMount(async () => {
// const res = await fetch(API_URL + "/channels" + $store.ftId, { const res = await fetch(API_URL + "/channels", {
// method: "GET", credentials: "include",
// mode: "cors", mode: "cors",
// }); });
// const data = await res.json(); if (res.ok) channels = await res.json();
// channels = data; });
// });
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
export let onSelectChannel: (channel: ChannelsType) => void; export let onSelectChannel: (channel: ChannelsType) => void;
const selectChat = (id: string) => { const selectChat = (id: number) => {
const channel = channels.find((c) => c.id === id); const channel = channels.find((c) => c.id === id);
if (channel) { if (channel) {
onSelectChannel(channel); onSelectChannel(channel);
@ -45,56 +42,99 @@
); );
if (privacy !== "public" && privacy !== "private") { if (privacy !== "public" && privacy !== "private") {
alert("Invalid privacy setting"); alert("Invalid privacy setting");
return;
} }
let password = ""; let password = "";
if (privacy === "private") {
password = prompt("Enter a password for the new channel:"); password = prompt("Enter a password for the new channel:");
if (!password) { const response = await fetch(API_URL + "/channels", {
alert("Invalid password"); credentials: "include",
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: name,
owner: $store.ftId,
password: password,
isPrivate: privacy === "private",
}),
});
if (response.ok) {
channels.push(await response.json());
} else {
alert("Error creating channel");
} }
} }
if (privacy === "public" || password) {
const newChannel: ChannelsType = {
id: Math.random().toString(),
name,
owner: $store.username,
password,
privacy,
}; };
// const response = await fetch(API_URL + "/channels" + $store.ftId , {
// method: "POST", //--------------------------------------------------------------------------------/
// mode: "cors",
// body: JSON.stringify(newChannel), const removeChannel = async (id: number) => {
// }); let string = prompt("type 'delete' to delete this channel");
// const data = await response.json(); if (string === "delete") {
// if (data.ok) { const response = await fetch(API_URL + "/channels/" + id, {
// channels = [newChannel, ...channels]; credentials: "include",
// } else { method: "DELETE",
// alert("Error creating channel"); mode: "cors",
// } });
channels = [newChannel, ...channels]; if (response.ok) channels = channels.filter((c) => c.id !== id);
else alert("Error deleting channel");
} }
};
//--------------------------------------------------------------------------------/
const inviteChannel = async (id: number) => {
let string = prompt("Enter the username of the user you want to invite");
const response = await fetch(API_URL + "/users/" + string + "/byname", {
credentials: "include",
method: "GET",
mode: "cors",
});
if (response.ok) {
const user = await response.json();
const response2 = await fetch(API_URL + "/channels/" + id + "/invite", {
credentials: "include",
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user.ftId,
}),
});
if (response2.ok) {
channels.push(await response2.json());
} else {
alert("Error inviting user");
}
} else {
alert("Error getting user infos");
} }
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const removeChannel = async (id: string) => { const changePassword = async (id: number) => {
let string = prompt("type 'delete' to delete this channel"); let string = prompt("Enter the new password for this channel (leave empty to remove password) :");
if (string === "delete") { const response = await fetch(API_URL + "/channels/" + id + "/password", {
// const response = await fetch(API_URL + "/channels" + $store.ftId + "/" + id, { credentials: "include",
// method: "DELETE", method: "POST",
// mode: "cors", mode: "cors",
// }); headers: {
// const data = await response.json(); "Content-Type": "application/json",
// if (data.ok) { },
// channels = channels.filter((c) => c.id !== id); body: JSON.stringify({
// } else { password: string,
// alert("Error deleting channel"); }),
// } });
channels = channels.filter((c) => c.id !== id); if (response.ok) {
channels.push(await response.json());
} else {
alert("Error changing password");
} }
// TODO: save to database
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
@ -113,6 +153,8 @@
on:click={() => removeChannel(_channels.id)} on:click={() => removeChannel(_channels.id)}
on:keydown={() => removeChannel(_channels.id)}>delete</button on:keydown={() => removeChannel(_channels.id)}>delete</button
> >
<button on:click={() => inviteChannel(_channels.id)}>invite</button>
<button on:click={() => changePassword(_channels.id)}>Set - Change - Remove Password</button>
</li>{/each} </li>{/each}
{:else} {:else}
<p>No channels available</p> <p>No channels available</p>
@ -134,7 +176,6 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.channels { .channels {
background-color: #fff; background-color: #fff;
border: 1px solid #ccc; border: 1px solid #ccc;

64
front/volume/src/components/Chat.svelte

@ -34,7 +34,6 @@
messagesDiv.scrollTop = messagesDiv.scrollHeight; messagesDiv.scrollTop = messagesDiv.scrollHeight;
} }
} }
// TODO: save to database
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
@ -46,7 +45,6 @@
showProfileMenu = true; showProfileMenu = true;
selectedUser = username; selectedUser = username;
showChatMembers = false; showChatMembers = false;
showChannelSettings = false;
} }
function closeProfileMenu() { function closeProfileMenu() {
showProfileMenu = false; showProfileMenu = false;
@ -59,7 +57,6 @@
let showChatMembers = false; let showChatMembers = false;
function toggleChatMembers() { function toggleChatMembers() {
showChatMembers = !showChatMembers; showChatMembers = !showChatMembers;
showChannelSettings = false;
} }
let chatMembers: Array<User> = [ let chatMembers: Array<User> = [
{ username: "user1" }, { username: "user1" },
@ -75,7 +72,7 @@
// let chatMembers: Array<Player> = []; // let chatMembers: Array<Player> = [];
// async function getChatMembers() { // async function getChatMembers() {
// console.log("Getting chat members"); // console.log("Getting chat members");
// const res = await fetch(API_URL + "/chat/members", { // const res = await fetch(API_URL + "/channels/members", {
// mode: "cors", // mode: "cors",
// }); // });
// chatMembers = await res.json(); // chatMembers = await res.json();
@ -83,25 +80,17 @@
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
let showChannelSettings = false;
function toggleChannelSettings() {
showChannelSettings = !showChannelSettings;
showChatMembers = false;
}
//--------------------------------------------------------------------------------/
const blockUser = async (username: string) => {}; const blockUser = async (username: string) => {};
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const banUser = async (username: string) => { const banUser = async (username: string) => {
// const prompt = window.prompt("Enter ban duration in seconds"); // const prompt = window.prompt("Enter ban duration in seconds");
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/ban", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/ban", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -117,11 +106,11 @@
const kickUser = async (username: string) => { const kickUser = async (username: string) => {
// set-up channel joining and kicking // set-up channel joining and kicking
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/kick", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/kick", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -138,11 +127,11 @@
const muteUser = async (username: string) => { const muteUser = async (username: string) => {
// use minutes prompt to determine mute duration // use minutes prompt to determine mute duration
// const prompt = window.prompt("Enter mute duration in seconds"); // const prompt = window.prompt("Enter mute duration in seconds");
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/mute", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/mute", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -157,11 +146,11 @@
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const adminUser = async (username: string) => { const adminUser = async (username: string) => {
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/admin", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/admin", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -176,11 +165,11 @@
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const removeAdminUser = async (username: string) => { const removeAdminUser = async (username: string) => {
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/admin", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/admin", {
// method: "DELETE", // method: "DELETE",
// mode: "cors", // mode: "cors",
// }); // });
@ -291,23 +280,6 @@
</div> </div>
</div> </div>
{/if} {/if}
<button
on:click|stopPropagation={toggleChannelSettings}
on:keydown|stopPropagation>Channel Settings</button
>
{#if showChannelSettings}
<div
class="channelSettings"
on:click|stopPropagation
on:keydown|stopPropagation
>
<div>
<button>Change Password</button>
<button>Set Password</button>
<button>Remove Password</button>
</div>
</div>
{/if}
</div> </div>
</div> </div>
@ -356,18 +328,4 @@
.chatMembers button { .chatMembers button {
width: 6rem; width: 6rem;
} }
.channelSettings {
position: absolute;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
max-height: 100px;
overflow-y: scroll;
}
.channelSettings button {
width: 5rem;
}
</style> </style>

7
front/volume/src/components/Friends.svelte

@ -12,7 +12,7 @@
? event.target.querySelector('input[type="text"]').value ? event.target.querySelector('input[type="text"]').value
: event.detail; : event.detail;
const response = await fetch(API_URL + "/invit/" + username, { const response = await fetch(API_URL + "/users/invit/" + username, {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
@ -34,14 +34,14 @@
let friendsInterval: ReturnType<typeof setInterval>; let friendsInterval: ReturnType<typeof setInterval>;
async function getFriends(): Promise<void> { async function getFriends(): Promise<void> {
let response = await fetch(API_URL + "/friends", { let response = await fetch(API_URL + "/users/friends", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
friends = await response.json(); friends = await response.json();
} }
async function getInvits(): Promise<void> { async function getInvits(): Promise<void> {
let response = await fetch(API_URL + "/invits", { let response = await fetch(API_URL + "/users/invits", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
@ -73,7 +73,6 @@
<span>{friend.username} is {friend.status}</span> <span>{friend.username} is {friend.status}</span>
</li> </li>
{/each} {/each}
/>
</div> </div>
{:else} {:else}
<p>No friends to display</p> <p>No friends to display</p>

2
front/volume/src/components/Leaderboard.svelte

@ -6,7 +6,7 @@
let leaderboard: Array<Player> = []; let leaderboard: Array<Player> = [];
async function getLeader(): Promise<void> { async function getLeader(): Promise<void> {
let response = await fetch(API_URL + "/leaderboard", { let response = await fetch(API_URL + "/users/leaderboard", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });

8
front/volume/src/components/MatchHistory.svelte

@ -25,17 +25,17 @@
let newBatch: Array<Match> = []; let newBatch: Array<Match> = [];
async function fetchData() { async function fetchData() {
let response; let response: Response;
if (username === "Global") { if (username === "Global") {
response = await fetch(`${API_URL}/globalHistory?page=${page}`, { response = await fetch(`${API_URL}/results/global?page=${page}`, {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
} else { } else {
response = await fetch(`${API_URL}/user/${username}`); response = await fetch(`${API_URL}/users/${username}/byname`);
if (response.ok) { if (response.ok) {
let user = await response.json(); let user = await response.json();
response = await fetch(`${API_URL}/history/${user.ftId}?page=${page}`, { response = await fetch(`${API_URL}/results/${user.ftId}?page=${page}`, {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });

2
front/volume/src/components/NavBar.svelte

@ -50,7 +50,7 @@
{#if link.text === "Profile"} {#if link.text === "Profile"}
<li> <li>
<button on:click={clickProfile}> <button on:click={clickProfile}>
<img src={api + "/avatar"} alt="avatar" /> <img src={api + "/users/avatar"} alt="avatar" />
</button> </button>
</li> </li>
{/if} {/if}

2
front/volume/src/components/Pong/Paddle.ts

@ -19,7 +19,7 @@ export class Paddle {
new Point(this.rect.center.x + offset, this.rect.center.y), new Point(this.rect.center.x + offset, this.rect.center.y),
new Point(this.rect.size.x / 3, this.rect.size.y) new Point(this.rect.size.x / 3, this.rect.size.y)
); );
render_rect.draw(context, "yellow"); render_rect.draw(context, color);
} }
move(e: MouseEvent) { move(e: MouseEvent) {

1
front/volume/src/components/Pong/Pong.svelte

@ -70,6 +70,7 @@
} }
if (data.yourPaddleIndex !== -2) { if (data.yourPaddleIndex !== -2) {
gamePlaying = true; gamePlaying = true;
game.ranked = data.ranked;
game.setInfo(data); game.setInfo(data);
} }
} }

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

@ -24,7 +24,7 @@
async function getUser() { async function getUser() {
if (username !== $store.username) { if (username !== $store.username) {
edit = false; edit = false;
const res = await fetch(API_URL + "/user/" + username, { const res = await fetch(API_URL + "/users/" + username + "/byname", {
mode: "cors", mode: "cors",
}); });
user = res.json(); user = res.json();
@ -37,7 +37,7 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
async function handleSubmit() { async function handleSubmit() {
let response = await fetch(API_URL, { let response = await fetch(API_URL + "/users", {
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
method: "POST", method: "POST",
body: JSON.stringify({ username: user.username }), body: JSON.stringify({ username: user.username }),
@ -52,7 +52,7 @@
async function handle2fa(event: Event) { async function handle2fa(event: Event) {
event.preventDefault(); event.preventDefault();
user.twoFA = !user.twoFA; user.twoFA = !user.twoFA;
let response = await fetch(API_URL, { let response = await fetch(API_URL + "/users", {
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
method: "POST", method: "POST",
body: JSON.stringify(user), body: JSON.stringify(user),
@ -68,11 +68,11 @@
<div class="profile" on:click|stopPropagation on:keydown|stopPropagation> <div class="profile" on:click|stopPropagation on:keydown|stopPropagation>
<h3>===| <mark>{user.username}'s Profile</mark> |===</h3> <h3>===| <mark>{user.username}'s Profile</mark> |===</h3>
<div class="profile-header"> <div class="profile-header">
{#if edit} {#if !edit}
<img src={API_URL + "/avatar"} alt="avatar" class="profile-img" /> <img src={API_URL + "/users/avatar"} alt="avatar" class="profile-img" />
{:else} {:else}
<form <form
action={`${API_URL}/avatar`} action={`${API_URL}/users/avatar`}
method="post" method="post"
enctype="multipart/form-data" enctype="multipart/form-data"
bind:this={avatarForm} bind:this={avatarForm}
@ -86,13 +86,17 @@
/> />
</form> </form>
<label class="img-class" for="avatar-input"> <label class="img-class" for="avatar-input">
<img src={API_URL + "/avatar"} alt="avatar" class="profile-img" /> <img
src={API_URL + "/users/avatar"}
alt="avatar"
class="profile-img"
/>
</label> </label>
{/if} {/if}
</div> </div>
<div class="profile-body"> <div class="profile-body">
<p> <p>
<button on:click={() => dispatch("view-history", user.ftId)} <button on:click={() => dispatch("view-history", user.username)}
>View History</button >View History</button
> >
</p> </p>

Loading…
Cancel
Save