diff --git a/back/volume/src/chat/chat.controller.ts b/back/volume/src/chat/chat.controller.ts index f43d86e..a774a0e 100644 --- a/back/volume/src/chat/chat.controller.ts +++ b/back/volume/src/chat/chat.controller.ts @@ -6,13 +6,12 @@ import { 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 { ChatService } from './chat.service' import { CreateChannelDto } from './dto/create-channel.dto' import { IdDto, PasswordDto, MuteDto } from './dto/updateUser.dto' @@ -25,7 +24,7 @@ import { Profile } from 'passport-42' @Controller('channels') export class ChatController { constructor ( - private readonly channelService: ChannelService, + private readonly channelService: ChatService, private readonly usersService: UsersService ) {} @@ -135,7 +134,8 @@ export class ChatController { 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) + let newMute: Array = [mute.data[0], Date.now() + mute.data[1] * 1000] + channel.muted.push(newMute) this.channelService.save(channel) } diff --git a/back/volume/src/chat/chat.gateway.ts b/back/volume/src/chat/chat.gateway.ts index 3e54192..8128b2f 100644 --- a/back/volume/src/chat/chat.gateway.ts +++ b/back/volume/src/chat/chat.gateway.ts @@ -1,22 +1,25 @@ -/* -import { UnauthorizedException, UseGuards } from '@nestjs/common' import { type OnGatewayConnection, type OnGatewayDisconnect, - MessageBody, SubscribeMessage, WebSocketGateway, - WebSocketServer + WebSocketServer, + WsException } from '@nestjs/websockets' import { Socket, Server } from 'socket.io' - +// import { User } from 'users/user.entity'; +import { UsersService } from 'src/users/users.service' +import { BadRequestException } from '@nestjs/common' import { ChatService } from './chat.service' +import Message from './entity/message.entity' +import * as bcrypt from 'bcrypt' +import { MessageService } from './message.service' import { type User } from 'src/users/entity/user.entity' -import { UsersService } from 'src/users/users.service' -import { Channel } from './entity/channel.entity' -import { Message } from './entity/message.entity' - -import { CreateChannelDto } from './dto/createChannel.dto' +import { CreateMessageDto } from './dto/create-message.dto' +import { InjectRepository } from '@nestjs/typeorm' +import { Repository } from 'typeorm' +import ConnectedUser from './entity/connection.entity' +import { ConnectionDto } from './dto/connection.dto' @WebSocketGateway({ cors: { origin: /^(http|ws):\/\/localhost(:\d+)?$/ } @@ -27,77 +30,89 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { constructor ( private readonly userService: UsersService, - private readonly chatService: ChatService - ) {} - - async handleConnection (socket: Socket) { - try { - const user: User | null = await this.userService.findUser( - socket.data.user.ftId - ) - if (user == null) { - socket.emit('Error', new UnauthorizedException()) - // socket.disconnect(); - return - } else { - socket.data.user = user - const channels = await this.chatService.getChannelsForUser(user.id) - // Only emit rooms to the specific connected client - return this.server.to(socket.id).emit('channel', channels) - } - } catch { - socket.emit('Error', new UnauthorizedException()) - // socket.disconnect(); - } + private readonly messageService: MessageService, + private readonly chatService: ChatService, + @InjectRepository(ConnectedUser) + private readonly connectedUserRepository: Repository + ) { } - handleDisconnect (socket: Socket) { - // socket.disconnect() + async handleConnection (socket: Socket): Promise { + // console.log(socket.handshake.headers) + // const cookie = socket.handshake.headers.cookie as string + // const { authentication: authenticationToken } = parse(cookie) + // console.log(authenticationToken) + // const user = await this.userService.find(authenticationToken) + // if (!user) { + // this.handleDisconnect(socket) + // throw new WsException('Invalid credentials.') + // } + // return user } - @SubscribeMessage('createChannel') - async onCreateChannel ( - socket: Socket, - @MessageBody() channeldto: CreateChannelDto - ): Promise { - const channel = new Channel() - channel.name = channeldto.name - // const owner = await this.userService.findUser(channeldto.owner) - // if (owner == null) return null - // channel.owners.push(owner) - channel.password = channeldto.password - /// .../// - return await this.chatService.createChannel(channel, socket.data.user) + handleDisconnect (socket: Socket): void { + socket.disconnect() } @SubscribeMessage('joinChannel') - async onJoinChannel (socket: Socket, channel: Channel) { - // add user to channel - const messages = await this.chatService.findMessagesInChannelForUser( + async onJoinChannel (socket: Socket, connect: ConnectionDto): Promise { + const channel = await this.chatService.getChannel(connect.ChannelId) + if (channel.banned.find((e) => e.id == connect.UserId) != null) { + throw new WsException('You are banned from entering this channel') + } + const user = (await this.userService.findUser(connect.UserId)) as User + if ( + channel.users.find((e) => e.id === user.id) == null && + channel.password !== '' + ) { + if (!(await bcrypt.compare(channel.password, connect.pwd))) { + throw new BadRequestException() + } + } else await this.chatService.addUserToChannel(channel, user) + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + { + const conUser = new ConnectedUser() + conUser.user = user + conUser.channel = channel + conUser.socket = socket.id + await this.connectedUserRepository.save(conUser) + } + const messages = await this.messageService.findMessagesInChannelForUser( channel, - socket.data.user + user ) this.server.to(socket.id).emit('messages', messages) + await socket.join(channel.name) } @SubscribeMessage('leaveChannel') - async onLeaveChannel (socket: Socket) { - await this.chatService.deleteBySocketId(socket.id) + async onLeaveChannel (socket: Socket): Promise { + const id = socket.id as any + await this.connectedUserRepository.delete({ socket: id }) + socket.disconnect() } @SubscribeMessage('addMessage') - async onAddMessage (socket: Socket, message: Message) { - const createdMessage: Message = await this.chatService.createMessage({ - ...message, - author: socket.data.user - }) - const channel = await this.chatService.getChannel( - createdMessage.channel.id + async onAddMessage (socket: Socket, message: CreateMessageDto): Promise { + const channel = await this.chatService.getChannel(message.ChannelId) + if (await this.chatService.getMuteDuration(channel.id, message.UserId) > 0) { + throw new WsException('You are muted') + } + const createdMessage: Message = await this.messageService.createMessage( + message ) - if (channel != null) { - const users = await this.userService.findOnlineInChannel(channel) + socket.in(channel.name).emit('newMessage', createdMessage) + } + + @SubscribeMessage('kickUser') + async onKickUser (socket: Socket, chan: number, from: number, to: number): Promise { + const channel = await this.chatService.getChannel(chan) + if ( + channel.owner.id !== from || + channel.admins.find((e) => e.id === from) == null + ) { + throw new WsException('You do not have the required privileges') } - /// TODO: Send message to users + await this.onLeaveChannel(socket) } } -*/ diff --git a/back/volume/src/chat/chat.module.ts b/back/volume/src/chat/chat.module.ts index 8fcaa1f..a64dbdf 100644 --- a/back/volume/src/chat/chat.module.ts +++ b/back/volume/src/chat/chat.module.ts @@ -1,23 +1,26 @@ -import { Module } from '@nestjs/common' +import { forwardRef, Module } from '@nestjs/common' import { TypeOrmModule } from '@nestjs/typeorm' import { AuthModule } from 'src/auth/auth.module' import { UsersModule } from 'src/users/users.module' -// import { ChatGateway } from './chat.gateway' +import { ChatGateway } from './chat.gateway' import { ChatController } from './chat.controller' -import { ChannelService } from './chat.service' +import { ChatService } from './chat.service' +import { MessageService } from './message.service' import Channel from './entity/channel.entity' import Message from './entity/message.entity' +import ConnectedUser from './entity/connection.entity' @Module({ imports: [ - AuthModule, UsersModule, - TypeOrmModule.forFeature([Channel]), - TypeOrmModule.forFeature([Message]) + AuthModule, + TypeOrmModule.forFeature([Channel, Message, ConnectedUser]), ], controllers: [ChatController], - providers: [ChannelService] + providers: [ChatService, ChatGateway, MessageService], + exports: [ChatService] + }) export class ChatModule {} diff --git a/back/volume/src/chat/chat.service.ts b/back/volume/src/chat/chat.service.ts index 1ee459d..3179232 100644 --- a/back/volume/src/chat/chat.service.ts +++ b/back/volume/src/chat/chat.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException } from '@nestjs/common' +import { Inject, Injectable, NotFoundException } from '@nestjs/common' import { InjectRepository } from '@nestjs/typeorm' import { Repository } from 'typeorm' @@ -7,9 +7,10 @@ import { UsersService } from 'src/users/users.service' import type User from 'src/users/entity/user.entity' import Channel from './entity/channel.entity' +import { Cron } from '@nestjs/schedule' @Injectable() -export class ChannelService { +export class ChatService { constructor ( @InjectRepository(Channel) private readonly ChannelRepository: Repository, @@ -30,6 +31,8 @@ export class ChannelService { return await this.ChannelRepository.save(newChannel) } + + async updatePassword (id: number, password: string) { let channel: Channel | null = await this.ChannelRepository.findOneBy({id}) if (channel === null) { throw new NotFoundException(`Channel #${id} not found`) } @@ -56,6 +59,15 @@ export class ChannelService { return rooms } + @Cron('*/6 * * * * *') + async updateMutelists(): Promise { + let channels = await this.ChannelRepository.find({}) + channels.forEach((channel) => { + channel.muted = channel.muted.filter((data) => { return (data[0] - Date.now()) > 0;}); + this.ChannelRepository.save(channel); + }) + } + async addUserToChannel (channel: Channel, user: User): Promise { channel.owner = user return await this.ChannelRepository.save(channel) diff --git a/back/volume/src/chat/entity/channel.entity.ts b/back/volume/src/chat/entity/channel.entity.ts index 6ab26bf..47eabfd 100644 --- a/back/volume/src/chat/entity/channel.entity.ts +++ b/back/volume/src/chat/entity/channel.entity.ts @@ -56,7 +56,6 @@ export default class Channel { @JoinTable() banned: User[] - @ManyToMany(() => User) // refuse post - @JoinTable() - muted: Array> + @Column('text', {array: true}) + muted: number[][] } diff --git a/back/volume/src/chat/entity/connection.entity.ts b/back/volume/src/chat/entity/connection.entity.ts index fc949f9..270ee37 100644 --- a/back/volume/src/chat/entity/connection.entity.ts +++ b/back/volume/src/chat/entity/connection.entity.ts @@ -1,16 +1,17 @@ -import { Column, Entity, OneToOne } from 'typeorm' +import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm' import Channel from './channel.entity' import User from 'src/users/entity/user.entity' @Entity() -export class connectedUser { +export default class ConnectedUser { @OneToOne(() => User) user: User - @OneToOne(() => Channel, (channel) => channel.id) + @OneToOne(() => Channel) + @JoinColumn() channel: Channel - @Column() + @PrimaryGeneratedColumn() socket: string } diff --git a/back/volume/src/chat/message.service.ts b/back/volume/src/chat/message.service.ts new file mode 100644 index 0000000..7d25f26 --- /dev/null +++ b/back/volume/src/chat/message.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { ChatService } from './chat.service'; +import { UsersService } from 'src/users/users.service'; + +import { type CreateMessageDto } from './dto/create-message.dto'; +import User from 'src/users/entity/user.entity'; +import Channel from './entity/channel.entity'; +import Message from './entity/message.entity'; + +@Injectable() +export class MessageService { + constructor( + @InjectRepository(Message) + private readonly MessageRepository: Repository, + private readonly channelService: ChatService, + private readonly usersService: UsersService, + ) {} + + async createMessage(message: CreateMessageDto): Promise { + const msg = new Message(); + msg.text = message.text; + msg.channel = await this.channelService.getChannel(message.ChannelId); + msg.author = (await this.usersService.findUser(message.UserId)) as User; + msg.channel.messages.push(msg); + return await this.MessageRepository.save( + this.MessageRepository.create(msg), + ); + } + + async findMessagesInChannelForUser( + channel: Channel, + user: User, + ): Promise { + return await this.MessageRepository.createQueryBuilder('message') + .where('message.channel = :chan', { chan: channel }) + .andWhere('message.author NOT IN (:...blocked)', { + blocked: user.blocked, + }) + .getMany(); + } +} diff --git a/back/volume/src/users/users.controller.ts b/back/volume/src/users/users.controller.ts index a4b04a5..e473e10 100644 --- a/back/volume/src/users/users.controller.ts +++ b/back/volume/src/users/users.controller.ts @@ -65,11 +65,6 @@ export class UsersController { return await this.usersService.getLeaderboard() } - @Get(':id/rank') - @UseGuards(AuthenticatedGuard) - async getRank (@Param('id', ParseIntPipe) id: number): Promise { - return await this.usersService.getRank(id) - } @Post('avatar') @UseGuards(AuthenticatedGuard) @Redirect('http://localhost') diff --git a/back/volume/src/users/users.module.ts b/back/volume/src/users/users.module.ts index f4f5190..7406029 100644 --- a/back/volume/src/users/users.module.ts +++ b/back/volume/src/users/users.module.ts @@ -4,9 +4,12 @@ import { User } from './entity/user.entity' import { UsersController } from './users.controller' import { UsersService } from './users.service' import { PongModule } from 'src/pong/pong.module' +import { ChatModule } from 'src/chat/chat.module' @Module({ - imports: [forwardRef(() => PongModule), TypeOrmModule.forFeature([User])], + imports: [ + forwardRef(() => PongModule), + TypeOrmModule.forFeature([User])], controllers: [UsersController], providers: [UsersService], exports: [UsersService] diff --git a/back/volume/src/users/users.service.ts b/back/volume/src/users/users.service.ts index 858d86f..f69eec8 100644 --- a/back/volume/src/users/users.service.ts +++ b/back/volume/src/users/users.service.ts @@ -12,7 +12,7 @@ import User from './entity/user.entity' @Catch(QueryFailedError, EntityNotFoundError) export class UsersService { constructor ( - @InjectRepository(User) private readonly usersRepository: Repository + @InjectRepository(User) private readonly usersRepository: Repository, ) {} async save (user: User): Promise { @@ -31,7 +31,6 @@ export class UsersService { relations: { results: true } }) if (user == null) throw new BadRequestException('User not found.') - user.rank = (await this.getRank(user.ftId)) + 1; return user } @@ -45,12 +44,12 @@ export class UsersService { this.usersRepository.save(usr).catch((err) => console.log(err)) } }) + this.getLeaderboard(); } async findUser (ftId: number): Promise { const user = await this.usersRepository.findOneBy({ ftId }) if (user == null) return null - user.rank = (await this.getRank(user.ftId)) + 1; user.lastAccess = Date.now() if (user.status === 'offline') user.status = 'online' await this.usersRepository.save(user) @@ -117,21 +116,17 @@ export class UsersService { async getLeaderboard (): Promise { const leaderboard = await this.usersRepository.find({ order: { - winrate: 'DESC' + winrate: 'ASC' } }) - let ret = leaderboard.filter((user) => user.rank !== 0) - ret.forEach((follower) => follower.socketKey = '') - return ret - } - - async getRank (ftId: number): Promise { - const leaderboard = await this.usersRepository.find({ - order: { - winrate: 'DESC' - } + let ret = leaderboard.filter((user) => user.matchs !== 0) + let r = 0 + ret.forEach((usr) => { + usr.rank = r++ + this.usersRepository.save(usr) + usr.socketKey = '' }) - return leaderboard.findIndex((user) => user.ftId === ftId) + return ret } async invit (ftId: number, targetFtId: number): Promise {