nicolas-arnaud
2 years ago
21 changed files with 1142 additions and 134 deletions
File diff suppressed because it is too large
@ -0,0 +1,90 @@ |
|||||
|
import { |
||||
|
OnGatewayConnection, |
||||
|
OnGatewayDisconnect, |
||||
|
SubscribeMessage, |
||||
|
WebSocketGateway, |
||||
|
WebSocketServer, |
||||
|
} from '@nestjs/websockets'; |
||||
|
import { Socket, Server } from 'socket.io'; |
||||
|
import { User } from 'src/users/user.entity'; |
||||
|
import { UsersService } from 'src/users/users.service'; |
||||
|
import { UnauthorizedException } from '@nestjs/common'; |
||||
|
import { ChatService } from './chat.service'; |
||||
|
import { Channel } from './model/channel.entity'; |
||||
|
import { Message } from './model/message.entity'; |
||||
|
|
||||
|
import { CreateChannelDto } from './model/create-channel.dto' |
||||
|
|
||||
|
@WebSocketGateway({ |
||||
|
cors: { |
||||
|
origin: [ |
||||
|
'http://localhost:5000', |
||||
|
'http://localhost:80', |
||||
|
'http://localhost:8080', |
||||
|
], |
||||
|
}, |
||||
|
}) |
||||
|
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { |
||||
|
@WebSocketServer() |
||||
|
server: Server; |
||||
|
|
||||
|
constructor( |
||||
|
private userService: UsersService, |
||||
|
private chatservice: ChatService, |
||||
|
) { } |
||||
|
|
||||
|
async handleConnection(socket: Socket) { |
||||
|
try { |
||||
|
const user: User = await this.userService.findOne(socket.data.user.id); |
||||
|
if (!user) { |
||||
|
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(); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleDisconnect(socket: Socket) { |
||||
|
socket.disconnect(); |
||||
|
} |
||||
|
|
||||
|
async onCreateChannel(socket: Socket, channel: CreateChannelDto): Promise<Channel> { |
||||
|
return this.chatservice.createChannel(channel, socket.data.user); |
||||
|
} |
||||
|
|
||||
|
@SubscribeMessage('joinChannel') |
||||
|
async onJoinChannel(socket: Socket, channel: Channel) { |
||||
|
//add user to channel
|
||||
|
const messages = await this.chatservice.findMessagesInChannelForUser( |
||||
|
channel, |
||||
|
socket.data.user, |
||||
|
); |
||||
|
this.server.to(socket.id).emit('messages', messages); |
||||
|
} |
||||
|
|
||||
|
@SubscribeMessage('leaveChannel') |
||||
|
async onLeaveChannel(socket: Socket) { |
||||
|
await this.chatservice.deleteBySocketId(socket.id); |
||||
|
} |
||||
|
|
||||
|
@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, |
||||
|
); |
||||
|
//send new Message to all joined Users currently online of the channel
|
||||
|
} |
||||
|
} |
@ -1,15 +1,20 @@ |
|||||
import { Module, forwardRef } 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 { Message } from './entities/message.entity' |
import { UsersModule } from 'src/users/users.module'; |
||||
import { Channel } from './entities/channel.entity' |
import { ChatGateway } from './chat.gateway'; |
||||
import { UsersModule } from 'src/users/users.module' |
import { ChatService } from './chat.service'; |
||||
|
import { UsersService } from 'src/users/users.service'; |
||||
|
import { Channel } from './model/channel.entity'; |
||||
|
import { Message } from './model/message.entity'; |
||||
|
|
||||
@Module({ |
@Module({ |
||||
imports: [ |
imports: [ |
||||
TypeOrmModule.forFeature([Channel, Message]), |
AuthModule, |
||||
forwardRef(() => UsersModule) |
UsersModule, |
||||
] |
TypeOrmModule.forFeature([Channel]), |
||||
// providers: [ChatService]
|
TypeOrmModule.forFeature([Message]), |
||||
|
], |
||||
|
providers: [ChatGateway, ChatService], |
||||
}) |
}) |
||||
export class ChatModule {} |
export class ChatModule {} |
||||
|
@ -0,0 +1,61 @@ |
|||||
|
import { Injectable } from '@nestjs/common'; |
||||
|
import { InjectRepository } from '@nestjs/typeorm'; |
||||
|
import { Repository } from 'typeorm'; |
||||
|
import * as bcrypt from 'bcrypt' |
||||
|
|
||||
|
import { Channel } from 'src/chat/model/channel.entity'; |
||||
|
import { User } from 'src/users/user.entity'; |
||||
|
import { Message } from './model/message.entity'; |
||||
|
import { CreateChannelDto } from './model/create-channel.dto' |
||||
|
|
||||
|
@Injectable() |
||||
|
export class ChatService { |
||||
|
constructor( |
||||
|
@InjectRepository(Channel) |
||||
|
private readonly ChannelRepository: Repository<Channel>, |
||||
|
@InjectRepository(Message) |
||||
|
private readonly MessageRepository: Repository<Message>, |
||||
|
) { } |
||||
|
|
||||
|
async createChannel(channelDatas: CreateChannelDto, creator: User): Promise<Channel> { |
||||
|
channelDatas.password = await bcrypt.hash(channelDatas.password, 10); |
||||
|
const newChannel = this.ChannelRepository.create(channelDatas); |
||||
|
await this.addCreatorToChannel(newChannel, creator); |
||||
|
this.ChannelRepository.save(newChannel); |
||||
|
newChannel.password = undefined; |
||||
|
return newChannel; |
||||
|
} |
||||
|
|
||||
|
async getChannelsForUser(userId: number): Promise<Channel[]> { |
||||
|
return this.ChannelRepository.find({}); //where userId is in User[] of channel?
|
||||
|
} |
||||
|
|
||||
|
async addCreatorToChannel(Channel: Channel, creator: User): Promise<Channel> { |
||||
|
Channel.users.push(creator); |
||||
|
return Channel; |
||||
|
} |
||||
|
|
||||
|
async createMessage(message: Message): Promise<Message> { |
||||
|
return this.MessageRepository.save(this.MessageRepository.create(message)); |
||||
|
} |
||||
|
|
||||
|
async deleteBySocketId(socketId: string) { |
||||
|
return this.ChannelRepository.delete({}); // for disconnect
|
||||
|
} |
||||
|
|
||||
|
async getChannel(id: number): Promise<Channel | null> { |
||||
|
return this.ChannelRepository.findOneBy({ id }); |
||||
|
} |
||||
|
|
||||
|
async findMessagesInChannelForUser( |
||||
|
channel: Channel, |
||||
|
user: User, |
||||
|
): Promise<Message> { |
||||
|
return this.MessageRepository.findOne({ |
||||
|
where: { |
||||
|
channel: { id: channel.id } |
||||
|
}, |
||||
|
relations: { channel: true }, |
||||
|
}) |
||||
|
} |
||||
|
} |
@ -1,33 +0,0 @@ |
|||||
import { |
|
||||
Entity, |
|
||||
PrimaryGeneratedColumn, |
|
||||
JoinTable, |
|
||||
OneToMany, |
|
||||
ManyToMany |
|
||||
} from 'typeorm' |
|
||||
|
|
||||
import { Message } from './message.entity' |
|
||||
import { User } from 'src/users/user.entity' |
|
||||
|
|
||||
@Entity('channel') |
|
||||
export class Channel { |
|
||||
@PrimaryGeneratedColumn() |
|
||||
id: number |
|
||||
|
|
||||
@OneToMany(() => Message, (message) => message.channel) |
|
||||
message: Message[] |
|
||||
|
|
||||
@ManyToMany(() => User) |
|
||||
@JoinTable({ |
|
||||
name: 'users_id', |
|
||||
joinColumn: { |
|
||||
name: 'channel', |
|
||||
referencedColumnName: 'id' |
|
||||
}, |
|
||||
inverseJoinColumn: { |
|
||||
name: 'user', |
|
||||
referencedColumnName: 'id' |
|
||||
} |
|
||||
}) |
|
||||
user: User[] |
|
||||
} |
|
@ -1,28 +0,0 @@ |
|||||
import { |
|
||||
BaseEntity, |
|
||||
Entity, |
|
||||
PrimaryGeneratedColumn, |
|
||||
Column, |
|
||||
JoinColumn, |
|
||||
ManyToOne |
|
||||
} from 'typeorm' |
|
||||
|
|
||||
import { Channel } from './channel.entity' |
|
||||
import { User } from 'src/users/user.entity' |
|
||||
|
|
||||
@Entity('message') |
|
||||
export class Message extends BaseEntity { |
|
||||
@PrimaryGeneratedColumn() |
|
||||
public id: number |
|
||||
|
|
||||
@Column() |
|
||||
public content: string |
|
||||
|
|
||||
@ManyToOne(() => User) |
|
||||
@JoinColumn({ name: 'author_id' }) |
|
||||
public author: User |
|
||||
|
|
||||
@ManyToOne(() => Channel) |
|
||||
@JoinColumn({ name: 'channel_id' }) |
|
||||
public channel: Channel |
|
||||
} |
|
@ -0,0 +1,49 @@ |
|||||
|
import { User } from 'src/users/user.entity'; |
||||
|
import { |
||||
|
BeforeInsert, |
||||
|
Column, |
||||
|
Entity, |
||||
|
JoinTable, |
||||
|
ManyToMany, |
||||
|
OneToMany, |
||||
|
PrimaryGeneratedColumn, |
||||
|
} from 'typeorm'; |
||||
|
import { Message } from './message.entity'; |
||||
|
import * as bcrypt from 'bcrypt'; |
||||
|
|
||||
|
@Entity() |
||||
|
export class Channel { |
||||
|
@PrimaryGeneratedColumn() |
||||
|
id: number; |
||||
|
|
||||
|
@Column() |
||||
|
name: string; |
||||
|
|
||||
|
@ManyToMany(() => User) |
||||
|
@JoinTable() |
||||
|
owners: User[]; |
||||
|
|
||||
|
@ManyToMany(() => User) |
||||
|
@JoinTable() |
||||
|
users: User[]; |
||||
|
|
||||
|
@OneToMany(() => Message, (message: Message) => message.channel) |
||||
|
messages: Message[]; |
||||
|
|
||||
|
@OneToMany(() => User, (user: User) => user.id) //refuse connection
|
||||
|
banned: User[]; |
||||
|
|
||||
|
@OneToMany(() => User, (user: User) => user.id) //refuse post
|
||||
|
muted: User[]; |
||||
|
|
||||
|
@Column({ select: false }) |
||||
|
password: string; |
||||
|
|
||||
|
@BeforeInsert() |
||||
|
async hashPassword() { |
||||
|
this.password = await bcrypt.hash( |
||||
|
this.password, |
||||
|
Number(process.env.HASH_SALT), |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
import { IsPositive, IsAlpha, IsString, IsOptional } from 'class-validator'; |
||||
|
|
||||
|
export class CreateChannelDto { |
||||
|
@IsString() |
||||
|
@IsAlpha() |
||||
|
name: string; |
||||
|
|
||||
|
@IsPositive() |
||||
|
owner: number; |
||||
|
|
||||
|
@IsOptional() |
||||
|
password: string; |
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
import { User } from 'src/users/user.entity'; |
||||
|
import { |
||||
|
Column, |
||||
|
CreateDateColumn, |
||||
|
Entity, |
||||
|
JoinColumn, |
||||
|
JoinTable, |
||||
|
ManyToOne, |
||||
|
PrimaryGeneratedColumn, |
||||
|
} from 'typeorm'; |
||||
|
import { Channel } from './channel.entity'; |
||||
|
|
||||
|
@Entity() |
||||
|
export class Message { |
||||
|
@PrimaryGeneratedColumn() |
||||
|
id: number; |
||||
|
|
||||
|
@Column() |
||||
|
text: string; |
||||
|
|
||||
|
@ManyToOne(() => User, (author: User) => author.messages) |
||||
|
@JoinColumn() |
||||
|
author: User; |
||||
|
|
||||
|
@ManyToOne(() => Channel, (channel: Channel) => channel.messages) |
||||
|
@JoinTable() |
||||
|
channel: Channel; |
||||
|
|
||||
|
@CreateDateColumn() |
||||
|
created_at: Date; |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
import { PartialType } from '@nestjs/mapped-types'; |
||||
|
import { CreateChannelDto } from './create-channel.dto'; |
||||
|
import { Message } from './message.entity'; |
||||
|
import { User } from 'src/users/user.entity'; |
||||
|
import { IsString } from 'class-validator'; |
||||
|
|
||||
|
export class UpdateChannelDto extends PartialType(CreateChannelDto) { |
||||
|
id: number; |
||||
|
|
||||
|
users: [User]; |
||||
|
|
||||
|
messages: [Message]; |
||||
|
|
||||
|
owners: [number]; //user id
|
||||
|
|
||||
|
banned: [number]; //user id
|
||||
|
|
||||
|
muted: [number]; //user id
|
||||
|
|
||||
|
@IsString() |
||||
|
password: string; |
||||
|
} |
@ -1,19 +1,39 @@ |
|||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' |
import { |
||||
|
Column, |
||||
|
Entity, |
||||
|
ManyToMany, |
||||
|
OneToMany, |
||||
|
PrimaryGeneratedColumn, |
||||
|
} from 'typeorm'; |
||||
|
import { Message } from 'src/chat/model/message.entity'; |
||||
|
import { Channel } from 'src/chat/model/channel.entity'; |
||||
|
|
||||
@Entity() |
@Entity() |
||||
export class User { |
export class User { |
||||
@PrimaryGeneratedColumn() |
@PrimaryGeneratedColumn() |
||||
id: number |
id: number; |
||||
|
|
||||
@Column({ unique: true }) |
@Column({ unique: true }) |
||||
id_42: number |
id_42: number; |
||||
|
|
||||
@Column({ unique: true }) |
@Column({ unique: true }) |
||||
username: string |
username: string; |
||||
|
|
||||
@Column() |
@Column({ default: '' }) |
||||
avatar: string |
avatar: string; |
||||
|
|
||||
@Column({ default: 'online' }) |
@Column({ default: 'online' }) |
||||
status: string |
status: string; |
||||
|
|
||||
|
@OneToMany(() => Message, (message: Message) => message.author) |
||||
|
messages: Message[]; |
||||
|
|
||||
|
@ManyToMany(() => Channel, (channel: Channel) => channel.users) |
||||
|
rooms: Channel[]; |
||||
|
|
||||
|
@OneToMany(() => User, (user) => user.id) //filter messages
|
||||
|
blocked: User[]; |
||||
|
|
||||
|
//@Column({ default: { wr: -1, place: -1 } })
|
||||
|
//rank: { wr: number; place: number };
|
||||
} |
} |
||||
|
@ -1,6 +0,0 @@ |
|||||
{ |
|
||||
"name": "transcendance", |
|
||||
"lockfileVersion": 3, |
|
||||
"requires": true, |
|
||||
"packages": {} |
|
||||
} |
|
Loading…
Reference in new issue