message storing, conversation storing
This commit is contained in:
parent
538198840c
commit
d05fb7c9f6
@ -77,7 +77,6 @@ export class ChatGateway
|
||||
|
||||
// tell everyone else someone joined
|
||||
client.broadcast.emit('serverNotice', `[+] ${payload.username} joined`);
|
||||
|
||||
} catch (err) {
|
||||
// st-4: Disconnect unauthorized clients
|
||||
this.logger.warn(`[!] Rejected connection: ${err}`);
|
||||
@ -104,28 +103,26 @@ export class ChatGateway
|
||||
@MessageBody() body: JoinLeaveRoomDto, //roomId
|
||||
@ConnectedSocket() client: Socket,
|
||||
) {
|
||||
|
||||
// extract user
|
||||
const username=client.data.user.username;
|
||||
const username = client.data.user.username;
|
||||
|
||||
// join user in the room(front-end will provide roomId(standard appraoch))
|
||||
client.join(body.roomId)
|
||||
client.join(body.roomId);
|
||||
|
||||
// tell the joiner he successfully joined
|
||||
client.emit("roomJoined",{
|
||||
roomId:body.roomId,
|
||||
message:`You joined room: ${body.roomId}`,
|
||||
})
|
||||
client.emit('roomJoined', {
|
||||
roomId: body.roomId,
|
||||
message: `You joined room: ${body.roomId}`,
|
||||
});
|
||||
|
||||
// also tell others in the room except the new joiner
|
||||
client.to(body.roomId).emit("roomNotice",{
|
||||
client.to(body.roomId).emit('roomNotice', {
|
||||
roomId: body.roomId,
|
||||
message: `[+] ${username} joined the room`,
|
||||
})
|
||||
});
|
||||
|
||||
// return acknowledgement
|
||||
return { success: true, roomId: body.roomId };
|
||||
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@ -135,10 +132,10 @@ export class ChatGateway
|
||||
@ConnectedSocket() client: Socket,
|
||||
) {
|
||||
// extract user
|
||||
const username=client.data.user.username;
|
||||
const username = client.data.user.username;
|
||||
|
||||
// remove the user from the room
|
||||
client.leave(body.roomId)
|
||||
client.leave(body.roomId);
|
||||
|
||||
// confirm to the leaver
|
||||
client.emit('roomLeft', { roomId: body.roomId });
|
||||
@ -154,31 +151,24 @@ export class ChatGateway
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('roomMessage')
|
||||
handleRoomMessage(
|
||||
@MessageBody() body:RoomMessageDto, //text:string
|
||||
@ConnectedSocket() sender:Socket
|
||||
){
|
||||
// extract user
|
||||
const username=sender.data.user.username
|
||||
// Check if user is in room
|
||||
const isMember = sender.rooms.has(body.roomId);
|
||||
if (!isMember) {
|
||||
sender.emit('roomError', {
|
||||
message: 'You are not allowed',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`[ROOM:${body.roomId}] ${username}: ${body.text}`);
|
||||
async handleRoomMessage(
|
||||
@MessageBody() body: RoomMessageDto, //text:string
|
||||
@ConnectedSocket() sender: Socket,
|
||||
) {
|
||||
//verify memebership, store chat and return event's data
|
||||
const data = await this.chatService.handleRoomMessage(body, sender);
|
||||
|
||||
// send everone in the room(including the sender)
|
||||
this.server.to(body.roomId).emit('roomMessage', {
|
||||
roomId: body.roomId,
|
||||
senderId: sender.id,
|
||||
username,
|
||||
text: body.text,
|
||||
});
|
||||
|
||||
this.server.to(body.roomId).emit('roomMessage', data);
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('startP2P')
|
||||
async handleStartP2P(
|
||||
@MessageBody() body: { targetUserId: string },
|
||||
@ConnectedSocket() client: Socket,
|
||||
) {
|
||||
|
||||
return this.chatService.handleStartP2P(body.targetUserId, client);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,9 +3,11 @@ import { ChatService } from './chat.service';
|
||||
import { ChatGateway } from './chat.gateway';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { MessagesModule } from 'src/messages/messages.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MessagesModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
|
||||
@ -1,6 +1,74 @@
|
||||
import { ConversationsService } from './../conversations/conversations.service';
|
||||
import { MessagesService } from './../messages/messages.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { RoomMessageDto } from './dto/room-message.dto';
|
||||
import { Socket } from 'socket.io';
|
||||
import { ConversationType } from 'src/conversations/schemas/conversation.schema';
|
||||
|
||||
@Injectable()
|
||||
export class ChatService {
|
||||
constructor(private readonly messagesService:MessagesService,
|
||||
private readonly conversationsService:ConversationsService
|
||||
){}
|
||||
|
||||
async handleRoomMessage(body:RoomMessageDto, sender:Socket){
|
||||
// extract user
|
||||
const { id: userId, username } = sender.data.user;
|
||||
// Check if user is in room
|
||||
const isMember = sender.rooms.has(body.roomId);
|
||||
if (!isMember) {
|
||||
sender.emit('roomError', {
|
||||
message: 'You are not allowed',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// store the message
|
||||
const message=await this.messagesService.create({
|
||||
conversationId:body.roomId,
|
||||
senderId:userId,
|
||||
text:body.text,
|
||||
})
|
||||
|
||||
return{
|
||||
roomId:body.roomId,
|
||||
senderId:userId,
|
||||
username,
|
||||
text:body.text,
|
||||
createdAt:message.createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
async handleStartP2P(targetUserId: string, client: Socket) {
|
||||
const senderId = client.data.user.id;
|
||||
|
||||
if (senderId === targetUserId) {
|
||||
client.emit('roomError', { message: 'Cannot start a conversation with yourself' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Find existing or create new P2P conversation
|
||||
let conversation:any = await this.conversationsService.findP2p(senderId, targetUserId)
|
||||
|
||||
if (!conversation) {
|
||||
conversation = await this.conversationsService.create({
|
||||
type: ConversationType.P2P,
|
||||
participants: [senderId, targetUserId],
|
||||
});
|
||||
}
|
||||
|
||||
const roomId = conversation._id.toString();
|
||||
|
||||
// Caller joins immediately
|
||||
client.join(roomId);
|
||||
|
||||
client.emit('p2pReady', {
|
||||
roomId, // client uses this for all future roomMessage events
|
||||
targetUserId,
|
||||
message: `P2P room ready`,
|
||||
});
|
||||
|
||||
return { roomId };
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateConversationDto } from './dto/create-conversation.dto';
|
||||
import { UpdateConversationDto } from './dto/update-conversation.dto';
|
||||
import { Conversation, ConversationDocument } from './schemas/conversation.schema';
|
||||
import { Conversation, ConversationDocument, ConversationType } from './schemas/conversation.schema';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
@ -9,17 +9,24 @@ import { Model } from 'mongoose';
|
||||
export class ConversationsService {
|
||||
|
||||
constructor(
|
||||
@InjectModel(Conversation.name) private userModel: Model<ConversationDocument>,
|
||||
@InjectModel(Conversation.name) private conversationModel: Model<ConversationDocument>,
|
||||
|
||||
) {}
|
||||
create(createConversationDto: CreateConversationDto) {
|
||||
return 'This action adds a new conversation';
|
||||
async create(createConversationDto: CreateConversationDto):Promise<ConversationDocument> {
|
||||
return await this.conversationModel.create(createConversationDto)
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all conversations`;
|
||||
}
|
||||
|
||||
async findP2p(senderId:string, targetUserId:string){
|
||||
return await this.conversationModel.findOne({
|
||||
type: ConversationType.P2P,
|
||||
participants: { $all: [senderId, targetUserId], $size: 2 },
|
||||
});
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} conversation`;
|
||||
}
|
||||
|
||||
@ -1 +1,47 @@
|
||||
export class CreateConversationDto {}
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { ConversationType } from '../schemas/conversation.schema';
|
||||
import {
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ArrayMinSize,
|
||||
ArrayUnique,
|
||||
IsArray,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateConversationDto {
|
||||
@ApiProperty({
|
||||
enum: ConversationType,
|
||||
enumName: 'ConversationType',
|
||||
example: ConversationType.P2P,
|
||||
description: 'Type of the conversation',
|
||||
})
|
||||
@IsEnum(ConversationType)
|
||||
@IsNotEmpty()
|
||||
type!: ConversationType;
|
||||
|
||||
@ApiProperty({
|
||||
type: [String],
|
||||
example: ['userId1', 'userId2'],
|
||||
description: 'List of participant user IDs',
|
||||
minItems: 2,
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayMinSize(2)
|
||||
@ArrayUnique()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
participants!: string[];
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
example: 'Team Chat',
|
||||
description: 'Name of the conversation (required for group, omit for p2p)',
|
||||
nullable: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name?: string;
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Document } from "mongoose";
|
||||
|
||||
export type ConversationDocument = Conversation & Document;
|
||||
export enum ConversationType{
|
||||
export enum ConversationType{
|
||||
P2P='p2p',
|
||||
GROUP='group'
|
||||
}
|
||||
@ -19,9 +20,9 @@ export class Conversation {
|
||||
participants!:string[]
|
||||
|
||||
@Prop({required:false,default:null})
|
||||
name!:string // for p2p no name
|
||||
name?:string // for p2p no name
|
||||
|
||||
createdAt!:Date
|
||||
createdAt!:Date // no prop
|
||||
}
|
||||
|
||||
export const ConversationSchema = SchemaFactory.createForClass(Conversation);
|
||||
|
||||
@ -1 +1,19 @@
|
||||
export class CreateMessageDto {}
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class CreateMessageDto {
|
||||
@ApiProperty({ example: 'qwertyj', description: 'mongodb comnversation id' })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
conversationId!: string;
|
||||
|
||||
@ApiProperty({ example: 'qwertyj', description: 'mongodb user id' })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
senderId!: string;
|
||||
|
||||
@ApiProperty({ example: 'Hello user', description: 'user message' })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
text!: string;
|
||||
}
|
||||
|
||||
@ -7,9 +7,11 @@ import { Model } from 'mongoose';
|
||||
|
||||
@Injectable()
|
||||
export class MessagesService {
|
||||
constructor( @InjectModel(Message.name) private MessageModel: Model<MessageDocument>,){}
|
||||
create(createMessageDto: CreateMessageDto) {
|
||||
return 'This action adds a new message';
|
||||
constructor(
|
||||
@InjectModel(Message.name) private MessageModel: Model<MessageDocument>,
|
||||
) {}
|
||||
async create(createMessageDto: CreateMessageDto): Promise<MessageDocument> {
|
||||
return await this.MessageModel.create(createMessageDto);
|
||||
}
|
||||
|
||||
findAll() {
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import { Document } from 'mongoose';
|
||||
|
||||
export type MessageDocument = Message & Document;
|
||||
|
||||
@Schema({timestamps:true})
|
||||
@Schema({ timestamps: true })
|
||||
export class Message {
|
||||
@Prop({ required:true})
|
||||
conversationId!:string
|
||||
@Prop({ required: true })
|
||||
conversationId!: string;
|
||||
|
||||
@Prop({ required:true})
|
||||
senderId!:string
|
||||
@Prop({ required: true })
|
||||
senderId!: string;
|
||||
|
||||
@Prop({ required:false})
|
||||
text!:string
|
||||
@Prop({ required: false })
|
||||
text!: string;
|
||||
|
||||
// no prop
|
||||
createdAt!:Date
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user