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
|
// tell everyone else someone joined
|
||||||
client.broadcast.emit('serverNotice', `[+] ${payload.username} joined`);
|
client.broadcast.emit('serverNotice', `[+] ${payload.username} joined`);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// st-4: Disconnect unauthorized clients
|
// st-4: Disconnect unauthorized clients
|
||||||
this.logger.warn(`[!] Rejected connection: ${err}`);
|
this.logger.warn(`[!] Rejected connection: ${err}`);
|
||||||
@ -104,41 +103,39 @@ export class ChatGateway
|
|||||||
@MessageBody() body: JoinLeaveRoomDto, //roomId
|
@MessageBody() body: JoinLeaveRoomDto, //roomId
|
||||||
@ConnectedSocket() client: Socket,
|
@ConnectedSocket() client: Socket,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// extract user
|
// 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))
|
// 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
|
// tell the joiner he successfully joined
|
||||||
client.emit("roomJoined",{
|
client.emit('roomJoined', {
|
||||||
roomId:body.roomId,
|
roomId: body.roomId,
|
||||||
message:`You joined room: ${body.roomId}`,
|
message: `You joined room: ${body.roomId}`,
|
||||||
})
|
});
|
||||||
|
|
||||||
// also tell others in the room except the new joiner
|
// 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,
|
roomId: body.roomId,
|
||||||
message: `[+] ${username} joined the room`,
|
message: `[+] ${username} joined the room`,
|
||||||
})
|
});
|
||||||
|
|
||||||
// return acknowledgement
|
// return acknowledgement
|
||||||
return { success: true, roomId: body.roomId };
|
return { success: true, roomId: body.roomId };
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(WsJwtGuard)
|
@UseGuards(WsJwtGuard)
|
||||||
@SubscribeMessage('leaveRoom')
|
@SubscribeMessage('leaveRoom')
|
||||||
handleLeaveRoom(
|
handleLeaveRoom(
|
||||||
@MessageBody() body: JoinLeaveRoomDto, //roomId
|
@MessageBody() body: JoinLeaveRoomDto, //roomId
|
||||||
@ConnectedSocket() client: Socket,
|
@ConnectedSocket() client: Socket,
|
||||||
) {
|
) {
|
||||||
// extract user
|
// extract user
|
||||||
const username=client.data.user.username;
|
const username = client.data.user.username;
|
||||||
|
|
||||||
// remove the user from the room
|
// remove the user from the room
|
||||||
client.leave(body.roomId)
|
client.leave(body.roomId);
|
||||||
|
|
||||||
// confirm to the leaver
|
// confirm to the leaver
|
||||||
client.emit('roomLeft', { roomId: body.roomId });
|
client.emit('roomLeft', { roomId: body.roomId });
|
||||||
@ -154,31 +151,24 @@ export class ChatGateway
|
|||||||
|
|
||||||
@UseGuards(WsJwtGuard)
|
@UseGuards(WsJwtGuard)
|
||||||
@SubscribeMessage('roomMessage')
|
@SubscribeMessage('roomMessage')
|
||||||
handleRoomMessage(
|
async handleRoomMessage(
|
||||||
@MessageBody() body:RoomMessageDto, //text:string
|
@MessageBody() body: RoomMessageDto, //text:string
|
||||||
@ConnectedSocket() sender:Socket
|
@ConnectedSocket() sender: Socket,
|
||||||
){
|
) {
|
||||||
// extract user
|
//verify memebership, store chat and return event's data
|
||||||
const username=sender.data.user.username
|
const data = await this.chatService.handleRoomMessage(body, sender);
|
||||||
// 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}`);
|
|
||||||
|
|
||||||
// send everone in the room(including the sender)
|
// send everone in the room(including the sender)
|
||||||
this.server.to(body.roomId).emit('roomMessage', {
|
this.server.to(body.roomId).emit('roomMessage', data);
|
||||||
roomId: body.roomId,
|
|
||||||
senderId: sender.id,
|
|
||||||
username,
|
|
||||||
text: body.text,
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 { ChatGateway } from './chat.gateway';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { MessagesModule } from 'src/messages/messages.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
MessagesModule,
|
||||||
JwtModule.registerAsync({
|
JwtModule.registerAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
|
|||||||
@ -1,6 +1,74 @@
|
|||||||
|
import { ConversationsService } from './../conversations/conversations.service';
|
||||||
|
import { MessagesService } from './../messages/messages.service';
|
||||||
import { Injectable } from '@nestjs/common';
|
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()
|
@Injectable()
|
||||||
export class ChatService {
|
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 { Injectable } from '@nestjs/common';
|
||||||
import { CreateConversationDto } from './dto/create-conversation.dto';
|
import { CreateConversationDto } from './dto/create-conversation.dto';
|
||||||
import { UpdateConversationDto } from './dto/update-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 { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Model } from 'mongoose';
|
import { Model } from 'mongoose';
|
||||||
|
|
||||||
@ -9,17 +9,24 @@ import { Model } from 'mongoose';
|
|||||||
export class ConversationsService {
|
export class ConversationsService {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectModel(Conversation.name) private userModel: Model<ConversationDocument>,
|
@InjectModel(Conversation.name) private conversationModel: Model<ConversationDocument>,
|
||||||
|
|
||||||
) {}
|
) {}
|
||||||
create(createConversationDto: CreateConversationDto) {
|
async create(createConversationDto: CreateConversationDto):Promise<ConversationDocument> {
|
||||||
return 'This action adds a new conversation';
|
return await this.conversationModel.create(createConversationDto)
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll() {
|
findAll() {
|
||||||
return `This action returns all conversations`;
|
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) {
|
findOne(id: number) {
|
||||||
return `This action returns a #${id} conversation`;
|
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 { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||||
|
import { Document } from "mongoose";
|
||||||
|
|
||||||
export type ConversationDocument = Conversation & Document;
|
export type ConversationDocument = Conversation & Document;
|
||||||
export enum ConversationType{
|
export enum ConversationType{
|
||||||
P2P='p2p',
|
P2P='p2p',
|
||||||
GROUP='group'
|
GROUP='group'
|
||||||
}
|
}
|
||||||
@ -19,9 +20,9 @@ export class Conversation {
|
|||||||
participants!:string[]
|
participants!:string[]
|
||||||
|
|
||||||
@Prop({required:false,default:null})
|
@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);
|
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()
|
@Injectable()
|
||||||
export class MessagesService {
|
export class MessagesService {
|
||||||
constructor( @InjectModel(Message.name) private MessageModel: Model<MessageDocument>,){}
|
constructor(
|
||||||
create(createMessageDto: CreateMessageDto) {
|
@InjectModel(Message.name) private MessageModel: Model<MessageDocument>,
|
||||||
return 'This action adds a new message';
|
) {}
|
||||||
|
async create(createMessageDto: CreateMessageDto): Promise<MessageDocument> {
|
||||||
|
return await this.MessageModel.create(createMessageDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll() {
|
findAll() {
|
||||||
|
|||||||
@ -1,19 +1,21 @@
|
|||||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||||
|
import { Document } from 'mongoose';
|
||||||
|
|
||||||
export type MessageDocument = Message & Document;
|
export type MessageDocument = Message & Document;
|
||||||
|
|
||||||
@Schema({timestamps:true})
|
@Schema({ timestamps: true })
|
||||||
export class Message {
|
export class Message {
|
||||||
@Prop({ required:true})
|
@Prop({ required: true })
|
||||||
conversationId!:string
|
conversationId!: string;
|
||||||
|
|
||||||
@Prop({ required:true})
|
@Prop({ required: true })
|
||||||
senderId!:string
|
senderId!: string;
|
||||||
|
|
||||||
@Prop({ required:false})
|
@Prop({ required: false })
|
||||||
text!:string
|
text!: string;
|
||||||
|
|
||||||
createdAt!:Date
|
// no prop
|
||||||
|
createdAt!:Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageSchema = SchemaFactory.createForClass(Message);
|
export const MessageSchema = SchemaFactory.createForClass(Message);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user