Browse Source

all linted and on road to a working chat

master
nicolas-arnaud 2 years ago
parent
commit
f9b7d453e4
  1. 107
      back/volume/src/chat/chat.controller.ts
  2. 32
      back/volume/src/chat/chat.gateway.ts
  3. 2
      back/volume/src/chat/chat.module.ts
  4. 30
      back/volume/src/chat/chat.service.ts
  5. 1
      back/volume/src/chat/dto/create-channel.dto.ts
  6. 2
      back/volume/src/chat/entity/channel.entity.ts
  7. 8
      back/volume/src/chat/entity/connection.entity.ts
  8. 3
      back/volume/src/users/entity/user.entity.ts
  9. 49
      back/volume/src/users/users.controller.ts
  10. 1
      back/volume/src/users/users.module.ts
  11. 6
      back/volume/src/users/users.service.ts
  12. 2
      docker-compose.yml
  13. 2
      front/volume/src/App.svelte
  14. 2
      front/volume/src/Auth.ts
  15. 26
      front/volume/src/components/Channels.svelte
  16. 363
      front/volume/src/components/Chat.svelte
  17. 8
      front/volume/src/components/Friends.svelte
  18. 2
      front/volume/src/components/Leaderboard.svelte
  19. 8
      front/volume/src/components/MatchHistory.svelte
  20. 8
      front/volume/src/components/NavBar.svelte
  21. 2
      front/volume/src/components/Pong/Pong.svelte
  22. 2
      front/volume/src/socket.ts

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

@ -33,31 +33,34 @@ export class ChatController {
@Post(':id/invite') @Post(':id/invite')
async addUser ( async addUser (
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() target: IdDto, @Body() target: IdDto,
@Profile42() profile: Profile @Profile42() profile: Profile
): Promise<void> { ): Promise<void> {
const channel = await this.channelService.getFullChannel(id) const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id) const user: User | null = await this.usersService.findUser(target.id)
if (user == null) if (user == null) {
throw new NotFoundException(`User #${target.id} not found`) throw new NotFoundException(`User #${target.id} not found`)
}
if (!(await this.channelService.isUser(channel.id, +profile.id))) { if (!(await this.channelService.isUser(channel.id, +profile.id))) {
throw new BadRequestException( throw new BadRequestException(
'You are not allowed to invite users to this channel' 'You are not allowed to invite users to this channel'
) )
} }
if (await this.channelService.isUser(channel.id, target.id)) if (await this.channelService.isUser(channel.id, target.id)) {
throw new BadRequestException('User is already in this channel') throw new BadRequestException('User is already in this channel')
if (await this.channelService.isBanned(channel.id, target.id)) }
if (await this.channelService.isBanned(channel.id, target.id)) {
throw new BadRequestException('User is banned from this channel') throw new BadRequestException('User is banned from this channel')
}
channel.users.push(user) channel.users.push(user)
this.channelService.save(channel) await this.channelService.save(channel)
} }
@Delete(':id/kick') @Delete(':id/kick')
async removeUser ( async removeUser (
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() target: IdDto, @Body() target: IdDto,
@Profile42() profile: Profile @Profile42() profile: Profile
): Promise<void> { ): Promise<void> {
const channel = await this.channelService.getFullChannel(id) const channel = await this.channelService.getFullChannel(id)
if (!(await this.channelService.isAdmin(channel.id, +profile.id))) { if (!(await this.channelService.isAdmin(channel.id, +profile.id))) {
@ -65,118 +68,136 @@ export class ChatController {
'You are not allowed to kick users from this channel' 'You are not allowed to kick users from this channel'
) )
} }
if (!(await this.channelService.isUser(channel.id, target.id))) if (!(await this.channelService.isUser(channel.id, target.id))) {
throw new BadRequestException('User is not in this channel') throw new BadRequestException('User is not in this channel')
if (await this.channelService.isOwner(channel.id, target.id)) }
if (await this.channelService.isOwner(channel.id, target.id)) {
throw new BadRequestException('You cannot kick the owner of the channel') throw new BadRequestException('You cannot kick the owner of the channel')
}
channel.users = channel.users.filter((usr: User) => { channel.users = channel.users.filter((usr: User) => {
return usr.ftId !== target.id return usr.ftId !== target.id
}) })
this.channelService.save(channel) await this.channelService.save(channel)
} }
@Post(':id/admin') @Post(':id/admin')
async addAdmin ( async addAdmin (
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() target: IdDto, @Body() target: IdDto,
@Profile42() profile: Profile @Profile42() profile: Profile
): Promise<void> { ): Promise<void> {
const channel = await this.channelService.getFullChannel(id) const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id) const user: User | null = await this.usersService.findUser(target.id)
if (user == null) if (user == null) {
throw new NotFoundException(`User #${target.id} not found`) throw new NotFoundException(`User #${target.id} not found`)
if (!(await this.channelService.isOwner(channel.id, +profile.id))) }
if (!(await this.channelService.isOwner(channel.id, +profile.id))) {
throw new BadRequestException('You are not the owner of this channel') throw new BadRequestException('You are not the owner of this channel')
if (!(await this.channelService.isUser(channel.id, target.id))) }
if (!(await this.channelService.isUser(channel.id, target.id))) {
throw new BadRequestException('User is not in this channel') throw new BadRequestException('User is not in this channel')
if (await this.channelService.isAdmin(channel.id, target.id)) }
if (await this.channelService.isAdmin(channel.id, target.id)) {
throw new BadRequestException('User is already an admin of this channel') throw new BadRequestException('User is already an admin of this channel')
}
channel.admins.push(user) channel.admins.push(user)
this.channelService.save(channel) await this.channelService.save(channel)
} }
@Delete(':id/admin') @Delete(':id/admin')
async removeAdmin ( async removeAdmin (
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() target: IdDto, @Body() target: IdDto,
@Profile42() profile: Profile @Profile42() profile: Profile
): Promise<void> { ): Promise<void> {
const channel = await this.channelService.getFullChannel(id) const channel = await this.channelService.getFullChannel(id)
if (!(await this.channelService.isOwner(channel.id, +profile.id))) if (!(await this.channelService.isOwner(channel.id, +profile.id))) {
throw new BadRequestException('You are not the owner of this channel') throw new BadRequestException('You are not the owner of this channel')
if (!(await this.channelService.isAdmin(channel.id, target.id))) }
if (!(await this.channelService.isAdmin(channel.id, target.id))) {
throw new BadRequestException('User is not an admin of this channel') throw new BadRequestException('User is not an admin of this channel')
}
channel.admins = channel.admins.filter((usr: User) => { channel.admins = channel.admins.filter((usr: User) => {
return usr.ftId !== target.id return usr.ftId !== target.id
}) })
this.channelService.save(channel) await this.channelService.save(channel)
} }
@Post(':id/ban') @Post(':id/ban')
async addBan ( async addBan (
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() target: IdDto, @Body() target: IdDto,
@Profile42() profile: Profile @Profile42() profile: Profile
):Promise<void> { ): Promise<void> {
const channel = await this.channelService.getFullChannel(id) const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id) const user: User | null = await this.usersService.findUser(target.id)
if (user == null) if (user == null) {
throw new NotFoundException(`User #${target.id} not found`) throw new NotFoundException(`User #${target.id} not found`)
}
if (!(await this.channelService.isAdmin(channel.id, +profile.id))) { if (!(await this.channelService.isAdmin(channel.id, +profile.id))) {
throw new BadRequestException( throw new BadRequestException(
'You are not allowed to ban users from this channel' 'You are not allowed to ban users from this channel'
) )
} }
if (await this.channelService.isOwner(channel.id, target.id)) if (await this.channelService.isOwner(channel.id, target.id)) {
throw new BadRequestException('You cannot ban the owner of the channel') throw new BadRequestException('You cannot ban the owner of the channel')
if (await this.channelService.isBanned(channel.id, target.id)) }
if (await this.channelService.isBanned(channel.id, target.id)) {
throw new BadRequestException('User is already banned from this channel') throw new BadRequestException('User is already banned from this channel')
}
channel.banned.push(user) channel.banned.push(user)
this.channelService.save(channel) await this.channelService.save(channel)
} }
@Post(':id/mute') @Post(':id/mute')
async addMute ( async addMute (
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() mute: MuteDto, // [userId, duration] @Body() mute: MuteDto, // [userId, duration]
@Profile42() profile: Profile @Profile42() profile: Profile
): Promise<void> { ): Promise<void> {
const channel = await this.channelService.getFullChannel(id) const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(mute.data[0]) const user: User | null = await this.usersService.findUser(mute.data[0])
if (user == null) if (user == null) {
throw new NotFoundException(`User #${mute.data[0]} not found`) throw new NotFoundException(`User #${mute.data[0]} not found`)
}
if (!(await this.channelService.isAdmin(channel.id, +profile.id))) { if (!(await this.channelService.isAdmin(channel.id, +profile.id))) {
throw new BadRequestException( throw new BadRequestException(
'You are not allowed to mute users from this channel' 'You are not allowed to mute users from this channel'
) )
} }
if (await this.channelService.isOwner(channel.id, mute.data[0])) if (await this.channelService.isOwner(channel.id, mute.data[0])) {
throw new BadRequestException('You cannot mute the owner of the channel') throw new BadRequestException('You cannot mute the owner of the channel')
if (await this.channelService.getMuteDuration(channel.id, mute.data[0]) > 0) }
if (
(await this.channelService.getMuteDuration(channel.id, mute.data[0])) > 0
) {
throw new BadRequestException('User is already muted from this channel') throw new BadRequestException('User is already muted from this channel')
}
const newMute: number[] = [mute.data[0], Date.now() + mute.data[1] * 1000] const newMute: number[] = [mute.data[0], Date.now() + mute.data[1] * 1000]
channel.muted.push(newMute) channel.muted.push(newMute)
this.channelService.save(channel) await this.channelService.save(channel)
} }
@Delete(':id') @Delete(':id')
async deleteChannel ( async deleteChannel (
@Profile42() profile: Profile, @Profile42() profile: Profile,
@Param('id', ParseIntPipe) id: number @Param('id', ParseIntPipe) id: number
): Promise<void> { ): Promise<void> {
if (!(await this.channelService.isOwner(id, +profile.id))) if (!(await this.channelService.isOwner(id, +profile.id))) {
throw new BadRequestException('You are not the owner of this channel') throw new BadRequestException('You are not the owner of this channel')
}
await this.channelService.removeChannel(id) await this.channelService.removeChannel(id)
} }
@Post(':id/password') @Post(':id/password')
async updatePassword ( async updatePassword (
@Profile42() profile: Profile, @Profile42() profile: Profile,
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() data: PasswordDto @Body() data: PasswordDto
): Promise<void> { ): Promise<void> {
if (await this.channelService.isOwner(id, +profile.id)) if (await this.channelService.isOwner(id, +profile.id)) {
throw new BadRequestException('You are not the owner of this channel') throw new BadRequestException('You are not the owner of this channel')
}
await this.channelService.updatePassword(id, data.password) await this.channelService.updatePassword(id, data.password)
} }

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

@ -4,7 +4,7 @@ import {
SubscribeMessage, SubscribeMessage,
WebSocketGateway, WebSocketGateway,
WebSocketServer, WebSocketServer,
WsException, WsException
} from '@nestjs/websockets' } from '@nestjs/websockets'
import { Socket, Server } from 'socket.io' import { Socket, Server } from 'socket.io'
// import { User } from 'users/user.entity'; // import { User } from 'users/user.entity';
@ -20,7 +20,6 @@ import { Repository } from 'typeorm'
import ConnectedUser from './entity/connection.entity' import ConnectedUser from './entity/connection.entity'
import { ConnectionDto } from './dto/connection.dto' import { ConnectionDto } from './dto/connection.dto'
@WebSocketGateway({ @WebSocketGateway({
cors: { origin: /^(http|ws):\/\/localhost(:\d+)?$/ } cors: { origin: /^(http|ws):\/\/localhost(:\d+)?$/ }
}) })
@ -36,7 +35,9 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
private readonly connectedUserRepository: Repository<ConnectedUser> private readonly connectedUserRepository: Repository<ConnectedUser>
) {} ) {}
async handleConnection (socket: Socket): Promise<void> {} async handleConnection (socket: Socket): Promise<void> {
console.log('Client connected: ' + socket.id)
}
handleDisconnect (socket: Socket): void { handleDisconnect (socket: Socket): void {
socket.disconnect() socket.disconnect()
@ -44,20 +45,25 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@SubscribeMessage('joinChannel') @SubscribeMessage('joinChannel')
async onJoinChannel (socket: Socket, connect: ConnectionDto): Promise<void> { async onJoinChannel (socket: Socket, connect: ConnectionDto): Promise<void> {
const channel = await this.chatService.getChannel(connect.ChannelId) console.log(connect.ChannelId, connect.UserId, connect.pwd)
if (channel.banned.find((ban) => ban.id === connect.UserId) !== null) const channel = await this.chatService.getFullChannel(connect.ChannelId)
if (channel.banned.find((ban) => ban.id === connect.UserId) !== null) {
throw new WsException('You are banned from entering this channel') throw new WsException('You are banned from entering this channel')
}
const user = (await this.userService.findUser(connect.UserId)) as User const user = (await this.userService.findUser(connect.UserId)) as User
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// We don't need to verify if the user is already in imo // We don't need to verify if the user is already in imo
// //
//if ( // if (
// channel.users.find((usr) => usr.id === user.id) == null && // channel.users.find((usr) => usr.id === user.id) == null &&
// channel.password !== '' // channel.password !== ''
//) { // ) {
if (channel.password !== '' && !(await bcrypt.compare(channel.password, connect.pwd))) if (
throw new WsException('Wrong password') channel.password !== '' &&
else await this.chatService.addUserToChannel(channel, user) !(await bcrypt.compare(channel.password, connect.pwd))
) {
throw new WsException('Wrong password')
} else await this.chatService.addUserToChannel(channel, user)
{ {
const conUser = new ConnectedUser() const conUser = new ConnectedUser()
@ -67,8 +73,10 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
await this.connectedUserRepository.save(conUser) await this.connectedUserRepository.save(conUser)
} }
const messages = const messages = await this.messageService.findMessagesInChannelForUser(
await this.messageService.findMessagesInChannelForUser(channel, user) channel,
user
)
this.server.to(socket.id).emit('messages', messages) this.server.to(socket.id).emit('messages', messages)
await socket.join(channel.name) await socket.join(channel.name)
} }

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

@ -1,4 +1,4 @@
import { forwardRef, Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm' import { TypeOrmModule } from '@nestjs/typeorm'
import { AuthModule } from 'src/auth/auth.module' import { AuthModule } from 'src/auth/auth.module'

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

@ -32,7 +32,7 @@ export class ChatService {
return await this.ChannelRepository.save(newChannel) return await this.ChannelRepository.save(newChannel)
} }
async updatePassword (id: number, password: string) { async updatePassword (id: number, password: string): Promise<void> {
const channel: Channel | null = await this.ChannelRepository.findOneBy({ const channel: Channel | null = await this.ChannelRepository.findOneBy({
id id
}) })
@ -89,7 +89,7 @@ export class ChatService {
async getFullChannel (id: number): Promise<Channel> { async getFullChannel (id: number): Promise<Channel> {
const channel = await this.ChannelRepository.findOne({ const channel = await this.ChannelRepository.findOne({
where: { id }, where: { id },
relations: ['users', 'admins', 'banned', 'muted', 'owner'] relations: ['users', 'admins', 'banned', 'owner']
}) })
if (channel == null) { if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`) throw new NotFoundException(`Channel #${id} not found`)
@ -97,15 +97,15 @@ export class ChatService {
return channel return channel
} }
async update (channel: Channel) { async update (channel: Channel): Promise<void> {
await this.ChannelRepository.update(channel.id, channel) await this.ChannelRepository.update(channel.id, channel)
} }
async save (channel: Channel) { async save (channel: Channel): Promise<void> {
await this.ChannelRepository.save(channel) await this.ChannelRepository.save(channel)
} }
async removeChannel (channelId: number) { async removeChannel (channelId: number): Promise<void> {
await this.ChannelRepository.delete(channelId) await this.ChannelRepository.delete(channelId)
} }
@ -114,7 +114,7 @@ export class ChatService {
where: { id }, where: { id },
relations: { owner: true } relations: { owner: true }
}) })
if (channel == null) { if (channel === null) {
throw new NotFoundException(`Channel #${id} not found`) throw new NotFoundException(`Channel #${id} not found`)
} }
return channel.owner.ftId === userId return channel.owner.ftId === userId
@ -125,10 +125,10 @@ export class ChatService {
where: { id }, where: { id },
relations: { admins: true } relations: { admins: true }
}) })
if (channel == null) { if (channel === null) {
throw new NotFoundException(`Channel #${id} not found`) throw new NotFoundException(`Channel #${id} not found`)
} }
return channel.admins.findIndex((user) => user.ftId === userId) != -1 return channel.admins.findIndex((user) => user.ftId === userId) !== -1
} }
async isUser (id: number, userId: number): Promise<boolean> { async isUser (id: number, userId: number): Promise<boolean> {
@ -136,10 +136,10 @@ export class ChatService {
where: { id }, where: { id },
relations: { users: true } relations: { users: true }
}) })
if (channel == null) { if (channel === null) {
throw new NotFoundException(`Channel #${id} not found`) throw new NotFoundException(`Channel #${id} not found`)
} }
return channel.users.findIndex((user) => user.ftId === userId) != -1 return channel.users.findIndex((user) => user.ftId === userId) !== -1
} }
async isBanned (id: number, userId: number): Promise<boolean> { async isBanned (id: number, userId: number): Promise<boolean> {
@ -147,10 +147,10 @@ export class ChatService {
where: { id }, where: { id },
relations: { banned: true } relations: { banned: true }
}) })
if (channel == null) { if (channel === null) {
throw new NotFoundException(`Channel #${id} not found`) throw new NotFoundException(`Channel #${id} not found`)
} }
return channel.banned.findIndex((user) => user.ftId === userId) != -1 return channel.banned.findIndex((user) => user.ftId === userId) !== -1
} }
async getMuteDuration (id: number, userId: number): Promise<number> { async getMuteDuration (id: number, userId: number): Promise<number> {
@ -158,16 +158,14 @@ export class ChatService {
where: { id }, where: { id },
relations: { muted: true } relations: { muted: true }
}) })
if (channel == null) { if (channel === null) {
throw new NotFoundException(`Channel #${id} not found`) throw new NotFoundException(`Channel #${id} not found`)
} }
const mutation: number[] | undefined = channel.muted.find( const mutation: number[] | undefined = channel.muted.find(
(mutation) => mutation[0] === userId (mutation) => mutation[0] === userId
) )
if (mutation == null) { if (mutation == null) return 0
return 0
}
return mutation[1] return mutation[1]
} }
} }

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

@ -1,7 +1,6 @@
import { Transform } from 'class-transformer' import { Transform } from 'class-transformer'
import { import {
IsPositive, IsPositive,
IsAlpha,
IsString, IsString,
IsOptional, IsOptional,
IsNumber, IsNumber,

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

@ -28,7 +28,7 @@ export default class Channel {
password: string password: string
@BeforeInsert() @BeforeInsert()
async hashPassword () { async hashPassword (): Promise<void> {
if (this.password === '') return if (this.password === '') return
this.password = await bcrypt.hash( this.password = await bcrypt.hash(
this.password, this.password,

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

@ -1,10 +1,4 @@
import { import { Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
Column,
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn
} from 'typeorm'
import Channel from './channel.entity' import Channel from './channel.entity'
import User from 'src/users/entity/user.entity' import User from 'src/users/entity/user.entity'

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

@ -2,13 +2,10 @@ import {
Entity, Entity,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
Column, Column,
OneToMany,
ManyToMany, ManyToMany,
JoinTable JoinTable
} from 'typeorm' } from 'typeorm'
import Message from 'src/chat/entity/message.entity'
import Channel from 'src/chat/entity/channel.entity'
import Result from 'src/pong/entity/result.entity' import Result from 'src/pong/entity/result.entity'
@Entity() @Entity()

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

@ -11,16 +11,16 @@ import {
Res, Res,
StreamableFile, StreamableFile,
BadRequestException, BadRequestException,
Redirect Redirect,
Delete
} from '@nestjs/common' } from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express' import { FileInterceptor } from '@nestjs/platform-express'
import { diskStorage } from 'multer' import { diskStorage } from 'multer'
import { type User } from "./entity/user.entity"; import { type User } from './entity/user.entity'
import { UsersService } from "./users.service"; import { UsersService } from './users.service'
import { UserDto, AvatarUploadDto } from "./dto/user.dto"; import { UserDto, AvatarUploadDto } from './dto/user.dto'
import { PongService } from "src/pong/pong.service";
import { AuthenticatedGuard } from 'src/auth/42-auth.guard' import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import { Profile42 } from 'src/auth/42.decorator' import { Profile42 } from 'src/auth/42.decorator'
@ -35,24 +35,33 @@ import { join } from 'path'
export class UsersController { export class UsersController {
constructor (private readonly usersService: UsersService) {} constructor (private readonly usersService: UsersService) {}
@Post('block/:id') @Get('block/:id')
@UseGuards(AuthenticatedGuard) @UseGuards(AuthenticatedGuard)
@Post("block/:id") async blockUser (
async blockUser(@Profile42() profile :Profile, @Param('id') id:number) { @Profile42() profile: Profile,
const user = await this.usersService.findUser(id) as User @Param('id') id: number
user.blocked.push((await this.usersService.findUser(+profile.id)) as User) ): Promise<void> {
this.usersService.save(user) const user = await this.usersService.findUser(profile.id)
const target = await this.usersService.findUser(id)
if (user === null || target === null) {
throw new BadRequestException('User not found')
}
user.blocked.push(target)
await this.usersService.save(user)
} }
@Post('unblock/:id') @Delete('block/:id')
@UseGuards(AuthenticatedGuard) @UseGuards(AuthenticatedGuard)
@Post("unblock/:id") async unblockUser (
async unblockUser(@Profile42() profile :Profile, @Param('id') id:number) { @Profile42() profile: Profile,
const user = await this.usersService.findUser(id) as User @Param('id') id: number
user.blocked = user.blocked.filter((usr: User) => { ): Promise<void> {
const user = await this.usersService.findUser(profile.id)
if (user === null) throw new BadRequestException('User not found')
user.blocked = user.blocked.filter((usr: User) => {
return usr.id !== id return usr.id !== id
}) })
this.usersService.save(user) await this.usersService.save(user)
} }
@Get('all') @Get('all')
@ -100,10 +109,10 @@ export class UsersController {
} }
}) })
) )
@ApiConsumes("multipart/form-data") @ApiConsumes('multipart/form-data')
@ApiBody({ @ApiBody({
description: "A new avatar for the user", description: 'A new avatar for the user',
type: AvatarUploadDto, type: AvatarUploadDto
}) })
async changeAvatar ( async changeAvatar (
@Profile42() profile: Profile, @Profile42() profile: Profile,

1
back/volume/src/users/users.module.ts

@ -4,7 +4,6 @@ import { User } from './entity/user.entity'
import { UsersController } from './users.controller' import { UsersController } from './users.controller'
import { UsersService } from './users.service' import { UsersService } from './users.service'
import { PongModule } from 'src/pong/pong.module' import { PongModule } from 'src/pong/pong.module'
import { ChatModule } from 'src/chat/chat.module'
@Module({ @Module({
imports: [forwardRef(() => PongModule), TypeOrmModule.forFeature([User])], imports: [forwardRef(() => PongModule), TypeOrmModule.forFeature([User])],

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

@ -46,7 +46,7 @@ export class UsersService {
}) })
} }
}) })
this.getLeaderboard() await this.getLeaderboard()
} }
async findUser (ftId: number): Promise<User | null> { async findUser (ftId: number): Promise<User | null> {
@ -125,7 +125,9 @@ export class UsersService {
let r = 0 let r = 0
ret.forEach((usr) => { ret.forEach((usr) => {
usr.rank = r++ usr.rank = r++
this.usersRepository.save(usr) this.usersRepository.save(usr).catch((err) => {
console.log(err)
})
usr.socketKey = '' usr.socketKey = ''
}) })
return ret return ret

2
docker-compose.yml

@ -34,4 +34,4 @@ services:
- ./postgres:/var/lib/postgresql/data - ./postgres:/var/lib/postgresql/data
networks: [transcendence] networks: [transcendence]
restart: always restart: always
env_file: .env env_file: .env

2
front/volume/src/App.svelte

@ -244,4 +244,4 @@
outline: none; outline: none;
box-shadow: 0 0 0 2px rgba(25, 135, 84, 0.25); box-shadow: 0 0 0 2px rgba(25, 135, 84, 0.25);
} }
</style> </style>

2
front/volume/src/Auth.ts

@ -11,7 +11,7 @@ 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(bypass: boolean = false) { export async function getUser() {
const res = await fetch(API_URL + "/users", { const res = await fetch(API_URL + "/users", {
method: "get", method: "get",
mode: "cors", mode: "cors",

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

@ -14,10 +14,13 @@
<script lang="ts"> <script lang="ts">
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
let channelMode = ""; let channelMode = "";
const channelOptions = ['public','private','direct']; const channelOptions = ["public", "private", "direct"];
const joinChannel = async (id: number) => { const joinChannel = async (id: number) => {
socket.emit("joinChannel", id, $store.ftId); socket.emit("joinChannel", {
UserId: $store.ftId,
ChannelId: id,
});
}; };
let channels: Array<ChannelsType> = []; let channels: Array<ChannelsType> = [];
@ -46,7 +49,7 @@
const name = prompt("Enter a name for the new channel:"); const name = prompt("Enter a name for the new channel:");
if (name) { if (name) {
let password = ""; let password = "";
if (channelMode !== 'direct') if (channelMode !== "direct")
password = prompt("Enter a password for the new channel:"); password = prompt("Enter a password for the new channel:");
const response = await fetch(API_URL + "/channels", { const response = await fetch(API_URL + "/channels", {
credentials: "include", credentials: "include",
@ -166,13 +169,16 @@
<p>No channels available</p> <p>No channels available</p>
{/if} {/if}
<div> <div>
<select bind:value={channelMode} > <select bind:value={channelMode}>
{#each channelOptions as option} {#each channelOptions as option}
<option value={option} selected={channelMode === option}>{option}</option> <option value={option} selected={channelMode === option}
>{option}</option
>
{/each} {/each}
</select> </select>
{#if channelMode!= ''} {#if channelMode != ""}
<button class="button" on:click={createChannel}>Create Channel</button> <button class="button" on:click={createChannel}>Create Channel</button
>
{/if} {/if}
</div> </div>
</div> </div>
@ -223,7 +229,7 @@
} }
button { button {
background-color: #6B8E23; background-color: #6b8e23;
color: #ffffff; color: #ffffff;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
@ -248,7 +254,7 @@
} }
.button { .button {
background-color: #6B8E23; background-color: #6b8e23;
color: #ffffff; color: #ffffff;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
@ -258,4 +264,4 @@
outline: none; outline: none;
width: 100%; width: 100%;
} }
</style> </style>

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

@ -1,29 +1,28 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
export interface chatMessagesType { export interface chatMessagesType {
id: number; id: number;
author: string; author: string;
text: string; text: string;
} }
import { createEventDispatcher, onDestroy, onMount } from "svelte"; import { createEventDispatcher, onDestroy, onMount } from "svelte";
import { store, API_URL } from "../Auth"; import { store, API_URL } from "../Auth";
import { socket } from "../socket" import { socket } from "../socket";
import type { ChannelsType } from "./Channels.svelte"; import type { ChannelsType } from "./Channels.svelte";
import type User from "./Profile.svelte"; import type User from "./Profile.svelte";
</script> </script>
<script lang="ts"> <script lang="ts">
let blockedUsers: Array<User> = [];
let blockedUsers: Array<User> = []; let chatMembers: Array<User> = [];
let chatMembers: Array<User> = []; let chatMessages: Array<chatMessagesType> = [];
let chatMessages: Array<chatMessagesType> = []; export let channel: ChannelsType;
export let channel: ChannelsType; let newText = "";
let newText = ""; onMount(async () => {
onMount(async () => { let res = await fetch(API_URL + "/users/block/" + $store.ftId, {
let res = await fetch(API_URL + "/users/" + $store.ftId + "/blocked", { credentials: "include",
credentials: "include", mode: "cors",
mode: "cors", });
}); if (res.ok) blockedUsers = await res.json();
if (res.ok) blockedUsers = await res.json();
socket.on("messages", (msgs: Array<chatMessagesType>) => { socket.on("messages", (msgs: Array<chatMessagesType>) => {
chatMessages = msgs; chatMessages = msgs;
@ -42,11 +41,13 @@ import type User from "./Profile.svelte";
const sendMessage = () => { const sendMessage = () => {
if (newText !== "") { if (newText !== "") {
/*
const newMessage = { const newMessage = {
id: chatMessages.length + 1, id: chatMessages.length + 1,
author: $store.username, author: $store.username,
text: newText, text: newText,
}; };
*/
chatMessages = [...chatMessages.slice(-5 + 1)]; chatMessages = [...chatMessages.slice(-5 + 1)];
socket.emit("addMessage", channel.id, $store.ftId, newText); socket.emit("addMessage", channel.id, $store.ftId, newText);
newText = ""; newText = "";
@ -83,111 +84,154 @@ import type User from "./Profile.svelte";
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const blockUser = async (username: string) => { const blockUser = async (username: string) => {
const res1 = await fetch(API_URL + "/users/" + username + "/byname", { let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include",
mode: "cors",
});
const data1 = await res1.json();
const res2 = await fetch(API_URL + "/users/block/" + data1.ftId, {
credentials: "include", credentials: "include",
method: "POST",
mode: "cors", mode: "cors",
}); });
if (res2.ok) { if (response.ok) {
alert("User blocked"); const target = await response.json();
} else { response = await fetch(API_URL + "/users/" + target.ftId + "/block", {
alert("Failed to block user"); credentials: "include",
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: target.ftId }),
});
} }
if (response.ok) alert("User blocked");
else alert("Failed to block user");
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const unblockUser = async (username: string) => { const unblockUser = async (username: string) => {
const res1 = await fetch(API_URL + "/users/" + username + "/byname", { let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include",
mode: "cors",
});
const data1 = await res1.json();
const res2 = await fetch(API_URL + "/users/unblock/" + data1.ftId, {
credentials: "include", credentials: "include",
method: "DELETE",
mode: "cors", mode: "cors",
}); });
if (res2.ok) { if (response.ok) {
alert("User unblocked"); const target = await response.json();
} else { response = await fetch(API_URL + "/users/" + target.ftId + "/block", {
alert("Failed to unblock user"); credentials: "include",
method: "DELETE",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: target.ftId }),
});
} }
if (response.ok) alert("User blocked");
else alert("Failed to block user");
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const banUser = async (username: string) => { const banUser = async (username: string) => {
const prompt = window.prompt("Enter ban duration in seconds"); let response = await fetch(API_URL + "/users/" + username + "/byname", {
const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
const data1 = await res1.json(); if (response.ok) {
const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/ban", { const target = await response.json();
response = await fetch(API_URL + "/channels/" + channel.id + "/ban", {
credentials: "include",
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: target.ftId }),
});
socket.emit("kickUser", channel.id, $store.ftId, target.ftId);
}
if (response.ok) {
alert("User banned");
} else alert("Failed to ban user");
};
//--------------------------------------------------------------------------------/
const unbanUser = async (username: string) => {
let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include", credentials: "include",
method: "POST",
mode: "cors", mode: "cors",
}); });
if (res2.ok) { if (response.ok) {
socket.emit("kickUser", channel.id, $store.ftId, data1.ftId); const target = await response.json();
alert("User banned"); response = await fetch(API_URL + "/channels/" + channel.id + "/ban", {
} else { credentials: "include",
alert("Failed to ban user"); method: "DELETE",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: target.ftId }),
});
} }
if (response.ok) alert("User unbanned");
else alert("Failed to unban user");
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const kickUser = async (username: string) => { const kickUser = async (username: string) => {
const res = await fetch(API_URL + "/users/" + username + "/byname", { const response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
const kickedUser = await res.json(); if (response.ok) {
socket.emit("kickUser", channel.id, $store.ftId, kickedUser.ftId); const target = await response.json();
socket.emit("kickUser", channel.id, $store.ftId, target.ftId);
}
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const muteUser = async (username: string) => { const muteUser = async (username: string) => {
const prompt = window.prompt("Enter mute duration in seconds"); const prompt = window.prompt("Enter mute duration in seconds");
const res1 = await fetch(API_URL + "/users/" + username + "/byname", { let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
const data1 = await res1.json(); const target = await response.json();
const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/mute", { if (response.ok) {
credentials: "include", response = await fetch(API_URL + "/channels/" + channel.id + "/mute", {
method: "POST", credentials: "include",
mode: "cors", method: "POST",
}); mode: "cors",
if (res2.ok) { headers: {
alert("User muted"); "Content-Type": "application/json",
} else { },
alert("Failed to mute user"); body: JSON.stringify({ data: [target.ftId, +prompt] }),
});
} }
if (response.ok) alert("User muted");
else alert("Failed to mute user");
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const adminUser = async (username: string) => { const adminUser = async (username: string) => {
const res1 = await fetch(API_URL + "/users/" + username + "/byname", { let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
const data1 = await res1.json(); if (response.ok) {
const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/admin", { const target = await response.json();
credentials: "include", response = await fetch(API_URL + "/channels/" + channel.id + "/admin", {
method: "POST", credentials: "include",
mode: "cors", method: "POST",
}); mode: "cors",
if (res2.ok) { headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: target.ftId }),
});
}
if (response.ok) {
alert("User admined"); alert("User admined");
} else { } else {
alert("Failed to admin user"); alert("Failed to admin user");
@ -197,99 +241,124 @@ import type User from "./Profile.svelte";
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const removeAdminUser = async (username: string) => { const removeAdminUser = async (username: string) => {
const res1 = await fetch(API_URL + "/users/" + username + "/byname", { let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
const data1 = await res1.json(); if (response.ok) {
const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/admin", { const target = await response.json();
credentials: "include", response = await fetch(API_URL + "/channels/" + channel.id + "/admin", {
method: "DELETE", credentials: "include",
mode: "cors", method: "DELETE",
}); mode: "cors",
if (res2.ok) { headers: {
alert("User admin removed"); "Content-Type": "application/json",
},
body: JSON.stringify({ id: target.ftId }),
});
}
if (response.ok) {
alert("User admined");
} else { } else {
alert("Failed to remove admin user"); alert("Failed to admin user");
} }
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
</script> </script>
<div class="overlay">
<div class="overlay" >
<div class="chat" on:click|stopPropagation on:keydown|stopPropagation> <div class="chat" on:click|stopPropagation on:keydown|stopPropagation>
<div class="messages" > <div class="messages">
{ #each chatMessages as message } {#each chatMessages as message}
<p class="message" > <p class="message">
{ #if !blockedUsers.filter((user) => user.username == message.author).length } {#if !blockedUsers.filter((user) => user.username == message.author).length}
<span <span
class="message-name" class="message-name"
on:click = {() => openProfile(message.author)} on:click={() => openProfile(message.author)}
on:keydown = {() => openProfile(message.author)} on:keydown={() => openProfile(message.author)}
style = "cursor: pointer;" style="cursor: pointer;"
> >
{ message.author } {message.author}
</span>: {message.text} </span>: {message.text}
{/if} {/if}
</p> </p>
{/each} {/each}
</div> </div>
{ #if showProfileMenu } {#if showProfileMenu}
<div <div
class="profile-menu" class="profile-menu"
on:click|stopPropagation on:click|stopPropagation
on:keydown|stopPropagation on:keydown|stopPropagation
> >
<ul> <ul>
<li> <li>
<button on:click = {() => dispatch("send-message", selectedUser)}> Send Message </button> <button on:click={() => dispatch("send-message", selectedUser)}>
</li> Send Message
<li> </button>
<button on:click = {() => dispatch("view-profile", selectedUser)}> View Profile </button> </li>
</li> <li>
<li> <button on:click={() => dispatch("view-profile", selectedUser)}>
<button on:click = {() => dispatch("add-friend", selectedUser)}> Add Friend </button> View Profile
</li> </button>
<li> </li>
<button on:click = {() => dispatch("invite-to-game", selectedUser)}> Invite to Game </button> <li>
</li> <button on:click={() => dispatch("add-friend", selectedUser)}>
<li> Add Friend
{ #if !blockedUsers.filter((user) => user.username = selectedUser).length } </button>
<button on:click = {() => blockUser(selectedUser)}> Block User </button> </li>
{:else } <li>
<button on:click = {() => unblockUser(selectedUser)}> Unblock User </button> <button on:click={() => dispatch("invite-to-game", selectedUser)}>
{/if} Invite to Game
</li> </button>
<li> <button on:click = { closeProfileMenu } > Close </button></li > </li>
</ul> <li>
</div> {#if !blockedUsers.filter((user) => (user.username = selectedUser)).length}
<button on:click={() => blockUser(selectedUser)}>
Block User
</button>
{:else}
<button on:click={() => unblockUser(selectedUser)}>
Unblock User
</button>
{/if}
</li>
<li><button on:click={closeProfileMenu}> Close </button></li>
</ul>
</div>
{/if} {/if}
<form on:submit|preventDefault={ sendMessage }> <form on:submit|preventDefault={sendMessage}>
<input type="text" placeholder = "Type a message..." bind:value={ newText } /> <input type="text" placeholder="Type a message..." bind:value={newText} />
<button> <button>
<img src="img/send.png" alt = "send" /> <img src="img/send.png" alt="send" />
</button> </button>
</form> </form>
<button on:click|stopPropagation={ toggleChatMembers } on:keydown|stopPropagation > Chat Members </button> <button
{ #if showChatMembers } on:click|stopPropagation={toggleChatMembers}
on:keydown|stopPropagation
>
Chat Members
</button>
{#if showChatMembers}
<div <div
class="chatMembers" class="chatMembers"
on:click|stopPropagation on:click|stopPropagation
on:keydown|stopPropagation on:keydown|stopPropagation
> />
</div>
<ul> <ul>
{ #each chatMembers as member } {#each chatMembers as member}
<li> <li>
<p> <p>
{ member.username } {member.username}
<button on:click = {() => banUser(member.username)}> ban </button> <button on:click={() => banUser(member.username)}> ban </button>
<button on:click = {() => kickUser(member.username)}> kick </button> <button on:click={() => kickUser(member.username)}> kick </button>
<button on:click = {() => muteUser(member.username)}> mute </button> <button on:click={() => muteUser(member.username)}> mute </button>
<button on:click = {() => adminUser(member.username)}> promote </button> <button on:click={() => adminUser(member.username)}>
<button on:click = {() => removeAdminUser(member.username)}> demote </button> promote
</button>
<button on:click={() => removeAdminUser(member.username)}>
demote
</button>
</p> </p>
</li> </li>
{/each} {/each}
@ -351,7 +420,7 @@ import type User from "./Profile.svelte";
} }
button { button {
background-color: #6B8E23; background-color: #6b8e23;
color: #ffffff; color: #ffffff;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
@ -396,4 +465,4 @@ import type User from "./Profile.svelte";
li:last-child { li:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
</style> </style>

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

@ -123,7 +123,8 @@
color: #e8e6e3; color: #e8e6e3;
} }
h2, h3 { h2,
h3 {
color: #e8e6e3; color: #e8e6e3;
} }
@ -133,7 +134,8 @@
max-height: 200px; max-height: 200px;
} }
input[type="text"], button { input[type="text"],
button {
background-color: #198754; background-color: #198754;
border: none; border: none;
color: #e8e6e3; color: #e8e6e3;
@ -168,4 +170,4 @@
button { button {
flex-shrink: 0; flex-shrink: 0;
} }
</style> </style>

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

@ -101,4 +101,4 @@
table tbody tr:nth-child(odd) { table tbody tr:nth-child(odd) {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
} }
</style> </style>

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

@ -14,12 +14,6 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
export let username: string = "Global"; export let username: string = "Global";
function formatDate(str: string) {
const splitT = str.split("T");
const splitDate = splitT[0].split("-");
const splitDot = splitT[1].split(".");
return `${splitDate[1]}/${splitDate[2]}-${splitDot[0]}`;
}
let page: number = 1; let page: number = 1;
let data: Array<Match> = []; let data: Array<Match> = [];
let newBatch: Array<Match> = []; let newBatch: Array<Match> = [];
@ -159,4 +153,4 @@
p { p {
color: #e8e6e3; color: #e8e6e3;
} }
</style> </style>

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

@ -1,5 +1,9 @@
<script lang="ts"> <script lang="ts">
let api = "http://" + import.meta.env.VITE_HOST + ":" + import.meta.env.VITE_BACK_PORT; let api =
"http://" +
import.meta.env.VITE_HOST +
":" +
import.meta.env.VITE_BACK_PORT;
export let links = [ export let links = [
{ text: "Home" }, { text: "Home" },
@ -157,4 +161,4 @@
display: none; display: none;
} }
} }
</style> </style>

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

@ -22,7 +22,7 @@
export let appState: string; export let appState: string;
export let setAppState: (newState: APPSTATE | string) => void; export let setAppState: (newState: APPSTATE | string) => void;
const SERVER_URL = `ws://${import.meta.env.VITE_HOST}:${ const SERVER_URL = `http://${import.meta.env.VITE_HOST}:${
import.meta.env.VITE_BACK_PORT import.meta.env.VITE_BACK_PORT
}`; }`;

2
front/volume/src/socket.ts

@ -1,3 +1,3 @@
import { io } from "socket.io-client"; import { io, Socket } from "socket.io-client";
export const socket: Socket = io("http://localhost:3001"); export const socket: Socket = io("http://localhost:3001");

Loading…
Cancel
Save