vvandenb
2 years ago
36 changed files with 691 additions and 326 deletions
@ -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) |
||||
|
} |
||||
|
} |
@ -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] |
||||
|
|
||||
} |
} |
||||
} |
} |
||||
|
@ -0,0 +1,13 @@ |
|||||
|
import { IsNumber, IsOptional, IsString } from 'class-validator' |
||||
|
|
||||
|
export class ConnectionDto { |
||||
|
@IsNumber() |
||||
|
UserId: number |
||||
|
|
||||
|
@IsNumber() |
||||
|
ChannelId: number |
||||
|
|
||||
|
@IsString() |
||||
|
@IsOptional() |
||||
|
pwd: string |
||||
|
} |
@ -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 |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
import { IsNumber, IsString } from 'class-validator' |
||||
|
|
||||
|
export class CreateMessageDto { |
||||
|
@IsString() |
||||
|
text: string |
||||
|
|
||||
|
@IsNumber() |
||||
|
UserId: number |
||||
|
|
||||
|
@IsNumber() |
||||
|
ChannelId: number |
||||
|
} |
@ -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 |
|
||||
} |
|
@ -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 |
||||
|
} |
@ -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 |
|
||||
} |
|
@ -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> |
||||
|
} |
@ -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 |
||||
|
} |
@ -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[] |
||||
|
} |
@ -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) |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue