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 { TypeOrmModule } from '@nestjs/typeorm' |
|||
import { Message } from './entities/message.entity' |
|||
import { Channel } from './entities/channel.entity' |
|||
import { UsersModule } from 'src/users/users.module' |
|||
import { 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 { ChatService } from './chat.service'; |
|||
import { UsersService } from 'src/users/users.service'; |
|||
import { Channel } from './model/channel.entity'; |
|||
import { Message } from './model/message.entity'; |
|||
|
|||
@Module({ |
|||
imports: [ |
|||
TypeOrmModule.forFeature([Channel, Message]), |
|||
forwardRef(() => UsersModule) |
|||
] |
|||
// providers: [ChatService]
|
|||
AuthModule, |
|||
UsersModule, |
|||
TypeOrmModule.forFeature([Channel]), |
|||
TypeOrmModule.forFeature([Message]), |
|||
], |
|||
providers: [ChatGateway, ChatService], |
|||
}) |
|||
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() |
|||
export class User { |
|||
@PrimaryGeneratedColumn() |
|||
id: number |
|||
id: number; |
|||
|
|||
@Column({ unique: true }) |
|||
id_42: number |
|||
id_42: number; |
|||
|
|||
@Column({ unique: true }) |
|||
username: string |
|||
username: string; |
|||
|
|||
@Column() |
|||
avatar: string |
|||
@Column({ default: '' }) |
|||
avatar: string; |
|||
|
|||
@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