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 { 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 { type CreateChannelDto } from './dto/create-channel.dto' |
|||
import { UsersService } from 'src/users/users.service' |
|||
|
|||
import type User from 'src/users/entity/user.entity' |
|||
import Channel from './entity/channel.entity' |
|||
|
|||
@Injectable() |
|||
export class ChatService { |
|||
export class ChannelService { |
|||
constructor ( |
|||
@InjectRepository(Channel) |
|||
private readonly ChannelRepository: Repository<Channel>, |
|||
@InjectRepository(Message) |
|||
private readonly MessageRepository: Repository<Message> |
|||
private readonly usersService: UsersService |
|||
) {} |
|||
|
|||
async createChannel (Channel: Channel, creator: User): Promise<Channel> { |
|||
const newChannel = await this.addCreatorToChannel(Channel, creator) |
|||
async createChannel (channel: CreateChannelDto): Promise<Channel> { |
|||
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) |
|||
} |
|||
|
|||
async getChannelsForUser (userId: number): Promise<Channel[]> { |
|||
return await this.ChannelRepository.find({}) // where userId is in User[] of channel?
|
|||
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`) } |
|||
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 addCreatorToChannel (Channel: Channel, creator: User): Promise<Channel> { |
|||
Channel.users.push(creator) |
|||
return 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 createMessage (message: Message): Promise<Message> { |
|||
return await this.MessageRepository.save( |
|||
this.MessageRepository.create(message) |
|||
) |
|||
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 deleteBySocketId (socketId: string) { |
|||
return await this.ChannelRepository.delete({}) // for disconnect
|
|||
async update (channel: Channel) { |
|||
await this.ChannelRepository.update(channel.id, channel) |
|||
} |
|||
|
|||
async getChannel (id: number): Promise<Channel | null> { |
|||
return await this.ChannelRepository.findOneBy({ id }) |
|||
async save (channel: Channel) { |
|||
await this.ChannelRepository.save(channel) |
|||
} |
|||
|
|||
async findMessagesInChannelForUser ( |
|||
channel: Channel, |
|||
user: User |
|||
): Promise<Message[]> { |
|||
return await this.MessageRepository.createQueryBuilder('message') |
|||
.where('message.channel = :chan', { chan: channel }) |
|||
.andWhere('message.author NOT IN (:...blocked)', { |
|||
blocked: user.blocked |
|||
}) |
|||
.getMany() |
|||
async removeChannel (channelId: number) { |
|||
await this.ChannelRepository.delete(channelId) |
|||
} |
|||
|
|||
async isOwner (id: number, userId: number): Promise<boolean> { |
|||
const channel = await this.ChannelRepository.findOne({ |
|||
where: { id }, |
|||
relations: { owner: true } |
|||
}) |
|||
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) } |
|||
return channel.owner.ftId === userId |
|||
} |
|||
|
|||
async isAdmin (id: number, userId: number): Promise<boolean> { |
|||
const channel = await this.ChannelRepository.findOne({ |
|||
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 isUser (id: number, userId: number): Promise<boolean> { |
|||
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 isBanned (id: number, userId: number): Promise<boolean> { |
|||
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 getMuteDuration (id: number, userId: number): Promise<number> { |
|||
const channel = await this.ChannelRepository.findOne({ |
|||
where: { id }, |
|||
relations: { muted: true } |
|||
}) |
|||
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