From e8fe46194417d9fc22877e330a634a4e20ad7375 Mon Sep 17 00:00:00 2001 From: nicolas-arnaud Date: Tue, 14 Mar 2023 11:54:34 +0100 Subject: [PATCH] added chat service --- back/volume/src/chat/chat.controller.ts | 99 +++++++++++++++++++ back/volume/src/chat/chat.gateway.ts | 2 + back/volume/src/chat/chat.module.ts | 8 +- back/volume/src/chat/chat.service.ts | 78 +++++++-------- back/volume/src/chat/dto/connection.dto.ts | 10 ++ ...teChannel.dto.ts => create-channel.dto.ts} | 8 +- .../volume/src/chat/dto/create-message.dto.ts | 12 +++ .../volume/src/chat/dto/update-channel.dto.ts | 20 ++++ back/volume/src/chat/dto/updateChannel.dto.ts | 24 ----- back/volume/src/chat/entity/channel.entity.ts | 6 +- back/volume/src/users/users.controller.ts | 9 +- back/volume/src/users/users.service.ts | 2 +- front/volume/src/components/Friends.svelte | 1 - 13 files changed, 201 insertions(+), 78 deletions(-) create mode 100644 back/volume/src/chat/chat.controller.ts create mode 100644 back/volume/src/chat/dto/connection.dto.ts rename back/volume/src/chat/dto/{createChannel.dto.ts => create-channel.dto.ts} (70%) create mode 100644 back/volume/src/chat/dto/create-message.dto.ts create mode 100644 back/volume/src/chat/dto/update-channel.dto.ts delete mode 100644 back/volume/src/chat/dto/updateChannel.dto.ts diff --git a/back/volume/src/chat/chat.controller.ts b/back/volume/src/chat/chat.controller.ts new file mode 100644 index 0000000..cd925a0 --- /dev/null +++ b/back/volume/src/chat/chat.controller.ts @@ -0,0 +1,99 @@ +import { + Body, + Controller, + Delete, + Get, + NotFoundException, + Param, + Post, +} from "@nestjs/common"; +//import { channel, Channel } from "diagnostics_channel"; +import { Channel } from './entity/channel.entity'; +import { ChannelService } from "./chat.service"; +import { CreateChannelDto } from "./dto/create-channel.dto"; +import { UsersService } from "src/users/users.service"; +import { UpdateChannelDto } from "./dto/update-channel.dto"; +import { User } from "src/users/entity/user.entity"; + +@Controller("chat") +export class ChatController { + private readonly channelService: ChannelService; + private readonly usersService: UsersService; + + @Get("channels/:id") + getChannelsForUser(@Param("id") id: number): Promise> { + return this.channelService.getChannelsForUser(id); + } + + @Post("channels") + async createChannel(@Body() channel: CreateChannelDto) { + return await this.channelService.createChannel(channel); + } + + @Delete("channels/:id") + async deleteChannel(@Param("id") id: number) { + return await this.channelService.removeChannel(id); + } + + @Post("channels/:id/owner") + async moveOwner(@Param("id") id: number, @Body() userId: number) { + const channel = await this.channelService.getChannel(id); + const user: User | null = await this.usersService.findUser(userId); + if (user == null) throw new NotFoundException(`User #${userId} not found`); + channel.owner = user; + this.channelService.update(channel); + } + + @Post("channels/:id/admin") + async addAdmin(@Param("id") id: number, @Body() userId: number) { + const channel = await this.channelService.getChannel(id); + const user: User | null = await this.usersService.findUser(userId); + if (user == null) throw new NotFoundException(`User #${userId} not found`); + channel.admins.push(user); + this.channelService.update(channel); + } + + @Delete("channels/:id/admin") + async removeAdmin(@Param("id") id: number, @Body() userId: number) { + const channel = await this.channelService.getChannel(id); + channel.admins = channel.admins.filter((a) => { + return a.ftId !== userId; + }); + this.channelService.update(channel); + } + + @Post("channels/:id/ban") + async addBan(@Param("id") id: number, @Body() userId: number) { + const channel = await this.channelService.getChannel(id); + const user: User | null = await this.usersService.findUser(userId); + if (user == null) throw new NotFoundException(`User #${userId} not found`); + channel.banned.push(user); + this.channelService.update(channel); + } + + @Delete("channels/:id/ban") + async removeBan(@Param("id") id: number, @Body() userId: number) { + const channel = await this.channelService.getChannel(id); + channel.banned = channel.banned.filter((a) => { + return a.ftId !== userId; + }); + this.channelService.update(channel); + } + + @Post("channels/:id/mute") + async addMute(@Param("id") id: number, @Body() userId: number) { + const channel = await this.channelService.getChannel(id); + const user: User | null = await this.usersService.findUser(userId); + if (user == null) throw new NotFoundException(`User #${userId} not found`); + channel.mute.push(user); + this.channelService.update(channel); + } + @Delete("channels/:id/mute") + async removeMute(@Param("id") id: number, @Body() userId: number) { + const channel = await this.channelService.getChannel(id); + channel.mute = channel.mute.filter((a) => { + return a.ftId !== userId; + }); + this.channelService.update(channel); + } +} diff --git a/back/volume/src/chat/chat.gateway.ts b/back/volume/src/chat/chat.gateway.ts index 0d47de3..3e54192 100644 --- a/back/volume/src/chat/chat.gateway.ts +++ b/back/volume/src/chat/chat.gateway.ts @@ -1,3 +1,4 @@ +/* import { UnauthorizedException, UseGuards } from '@nestjs/common' import { type OnGatewayConnection, @@ -99,3 +100,4 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { /// TODO: Send message to users } } +*/ diff --git a/back/volume/src/chat/chat.module.ts b/back/volume/src/chat/chat.module.ts index 930ebb4..4f69607 100644 --- a/back/volume/src/chat/chat.module.ts +++ b/back/volume/src/chat/chat.module.ts @@ -3,8 +3,9 @@ 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 { ChatService } from './chat.service' +//import { ChatGateway } from './chat.gateway' +import { ChatController } from './chat.controller' +import { ChannelService } from './chat.service' import { Channel } from './entity/channel.entity' import { Message } from './entity/message.entity' @@ -15,6 +16,7 @@ import { Message } from './entity/message.entity' TypeOrmModule.forFeature([Channel]), TypeOrmModule.forFeature([Message]) ], - providers: [ChatGateway, ChatService] + controllers: [ChatController], + providers: [/*ChatGateway, */ChannelService] }) export class ChatModule {} diff --git a/back/volume/src/chat/chat.service.ts b/back/volume/src/chat/chat.service.ts index 525949d..ab5c270 100644 --- a/back/volume/src/chat/chat.service.ts +++ b/back/volume/src/chat/chat.service.ts @@ -1,56 +1,56 @@ -import { Injectable } from '@nestjs/common' -import { InjectRepository } from '@nestjs/typeorm' -import { Repository } from 'typeorm' -import { type User } from 'src/users/entity/user.entity' -import { Channel } from './entity/channel.entity' -import { Message } from './entity/message.entity' +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Channel } from './entity/channel.entity'; +import { User } from 'src/users/entity/user.entity'; +import { Repository } from 'typeorm'; +import { CreateChannelDto } from './dto/create-channel.dto'; +import { UsersService } from 'src/users/users.service'; @Injectable() -export class ChatService { - constructor ( +export class ChannelService { + constructor( @InjectRepository(Channel) private readonly ChannelRepository: Repository, - @InjectRepository(Message) - private readonly MessageRepository: Repository + private readonly usersService: UsersService, ) {} - async createChannel (Channel: Channel, creator: User): Promise { - const newChannel = await this.addCreatorToChannel(Channel, creator) - return await this.ChannelRepository.save(newChannel) + async createChannel(channel: CreateChannelDto): Promise { + const newChannel = this.ChannelRepository.create({ + name: channel.name, + password: channel.password, + }); + let user: User| null = await this.usersService.findUser(channel.owner); + if (user == null) throw new NotFoundException(`User #${channel.owner} not found`) + newChannel.owner = user; + return await this.ChannelRepository.save(newChannel); } - async getChannelsForUser (userId: number): Promise { - return await this.ChannelRepository.find({}) // where userId is in User[] of channel? + async getChannelsForUser(ftId: number): Promise> { + const query = await this.ChannelRepository.createQueryBuilder('room') + .innerJoin('room.users', 'users') + .where('users.ftId = :ftId', { ftId }) + .leftJoinAndSelect('room.users', 'all_users') + .orderBy('room.id', 'DESC') // TODO: order by last message + .getRawMany(); + return query; //where userId is in User[] of channel? } - async addCreatorToChannel (Channel: Channel, creator: User): Promise { - Channel.users.push(creator) - return Channel + async addUserToChannel(channel: Channel, user: User): Promise { + channel.owner = user; + return await this.ChannelRepository.save(channel); } - async createMessage (message: Message): Promise { - return await this.MessageRepository.save( - this.MessageRepository.create(message) - ) + async getChannel(id: number): Promise { + const channel = await this.ChannelRepository.findOneBy({ id }); + if (!channel) throw new NotFoundException(`Channel #${id} not found`); + return channel; } - async deleteBySocketId (socketId: string) { - return await this.ChannelRepository.delete({}) // for disconnect + async update(channel: Channel) { + this.ChannelRepository.update(channel.id, channel); } - - async getChannel (id: number): Promise { - return await this.ChannelRepository.findOneBy({ id }) - } - - 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() + async removeChannel(id: number) { + await this.ChannelRepository.delete(id); } } + diff --git a/back/volume/src/chat/dto/connection.dto.ts b/back/volume/src/chat/dto/connection.dto.ts new file mode 100644 index 0000000..7f9e2b6 --- /dev/null +++ b/back/volume/src/chat/dto/connection.dto.ts @@ -0,0 +1,10 @@ +import { IsNumber } from 'class-validator'; + +export class ConnectionDto { + @IsNumber() + UserId: number; + @IsNumber() + ChannelId: number; + @IsNumber() + SocketId: number; +} diff --git a/back/volume/src/chat/dto/createChannel.dto.ts b/back/volume/src/chat/dto/create-channel.dto.ts similarity index 70% rename from back/volume/src/chat/dto/createChannel.dto.ts rename to back/volume/src/chat/dto/create-channel.dto.ts index cc3fbff..ab7d4ec 100644 --- a/back/volume/src/chat/dto/createChannel.dto.ts +++ b/back/volume/src/chat/dto/create-channel.dto.ts @@ -1,13 +1,13 @@ -import { IsPositive, IsAlpha, IsString, IsOptional } from 'class-validator' +import { IsPositive, IsAlpha, IsString, IsOptional } from 'class-validator'; export class CreateChannelDto { @IsString() @IsAlpha() - name: string + name: string; @IsPositive() - owner: number + owner: number; @IsOptional() - password: string + password: string; } diff --git a/back/volume/src/chat/dto/create-message.dto.ts b/back/volume/src/chat/dto/create-message.dto.ts new file mode 100644 index 0000000..6abbfb9 --- /dev/null +++ b/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; +} diff --git a/back/volume/src/chat/dto/update-channel.dto.ts b/back/volume/src/chat/dto/update-channel.dto.ts new file mode 100644 index 0000000..bdf4c87 --- /dev/null +++ b/back/volume/src/chat/dto/update-channel.dto.ts @@ -0,0 +1,20 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateChannelDto } from './create-channel.dto'; +import { IsOptional, IsString } from 'class-validator'; + +export class UpdateChannelDto extends PartialType(CreateChannelDto) { + id: number; + @IsOptional() + users: [number]; + @IsOptional() + messages: [number]; + @IsOptional() + owners: [number]; //user id + @IsOptional() + banned: [number]; //user id + @IsOptional() + muted: [number]; //user id + @IsString() + @IsOptional() + password: string; +} diff --git a/back/volume/src/chat/dto/updateChannel.dto.ts b/back/volume/src/chat/dto/updateChannel.dto.ts deleted file mode 100644 index dc2b5ff..0000000 --- a/back/volume/src/chat/dto/updateChannel.dto.ts +++ /dev/null @@ -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 -} diff --git a/back/volume/src/chat/entity/channel.entity.ts b/back/volume/src/chat/entity/channel.entity.ts index bc06699..624473e 100644 --- a/back/volume/src/chat/entity/channel.entity.ts +++ b/back/volume/src/chat/entity/channel.entity.ts @@ -24,6 +24,10 @@ export class Channel { @JoinTable() owner: User + @ManyToMany(() => User) + @JoinTable() + admins: User[] + @ManyToMany(() => User) @JoinTable() users: User[] @@ -35,7 +39,7 @@ export class Channel { banned: User[] @OneToMany(() => User, (user: User) => user.id) // refuse post - muted: User[] + mute: User[] @Column({ select: false }) password: string diff --git a/back/volume/src/users/users.controller.ts b/back/volume/src/users/users.controller.ts index 5899424..73d9d64 100644 --- a/back/volume/src/users/users.controller.ts +++ b/back/volume/src/users/users.controller.ts @@ -137,10 +137,9 @@ export class UsersController { return user } - @Get('invit/:username') - @UseGuards(AuthenticatedGuard) + @Get('invit/:id/:username') async invitUser ( - @Profile42() profile: Profile, + @Param('id', ParseIntPipe) id: number, @Param('username') username: string ): Promise { const target: User | null = await this.usersService.findUserByName( @@ -149,10 +148,10 @@ export class UsersController { if (target === null) { throw new BadRequestException(`User ${username} not found.`) } - if (+profile.id === target.ftId) { + if (id === target.ftId) { 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(id, target.ftId) if (ret !== 'OK') throw new BadRequestException(ret) } diff --git a/back/volume/src/users/users.service.ts b/back/volume/src/users/users.service.ts index 64f971d..137d90a 100644 --- a/back/volume/src/users/users.service.ts +++ b/back/volume/src/users/users.service.ts @@ -167,7 +167,7 @@ export class UsersService { ) { user.friends.push(target) target.friends.push(user) - user.followers.slice(id, 1) + user.followers = user.followers.slice(id, 1) await this.usersRepository.save(user) } else target.followers.push(user) await this.usersRepository.save(target) diff --git a/front/volume/src/components/Friends.svelte b/front/volume/src/components/Friends.svelte index f88b256..566205c 100644 --- a/front/volume/src/components/Friends.svelte +++ b/front/volume/src/components/Friends.svelte @@ -73,7 +73,6 @@ {friend.username} is {friend.status} {/each} - /> {:else}

No friends to display