From 820fc9934c959595691933f09d8d60b441f53ae5 Mon Sep 17 00:00:00 2001 From: Suman991 Date: Fri, 17 Apr 2026 01:04:58 +0530 Subject: [PATCH] added more services and used table referencing --- src/chat/chat.gateway.ts | 17 +--- src/chat/dto/chat-message.dto.ts | 9 -- src/conversations/conversations.controller.ts | 44 +++++---- src/conversations/conversations.service.ts | 93 ++++++++++++++----- .../schemas/conversation.schema.ts | 8 +- src/messages/messages.controller.ts | 30 ++---- src/messages/messages.service.ts | 24 ++--- src/messages/schemas/message.schema.ts | 12 +-- 8 files changed, 128 insertions(+), 109 deletions(-) delete mode 100644 src/chat/dto/chat-message.dto.ts diff --git a/src/chat/chat.gateway.ts b/src/chat/chat.gateway.ts index 9445558..ec6ff93 100644 --- a/src/chat/chat.gateway.ts +++ b/src/chat/chat.gateway.ts @@ -34,7 +34,7 @@ export class ChatGateway private readonly logger = new Logger(ChatGateway.name); - private users = new Map(); // {socketId:{userId:'12rt3',username:'suman'}} + // private users = new Map(); // {socketId:{userId:'12rt3',username:'suman'}} // redundant afterInit(server: Server) { this.logger.log('/chat namespace initialized'); @@ -61,12 +61,7 @@ export class ChatGateway id: payload.sub, // mongo id username: payload.username, }; - // in memeory - this.users.set(client.id, { - userId: payload.sub, - username: payload.username, - }); - + this.logger.log(`[+] Authenticated: ${payload.username} (${client.id})`); // tell the new client their own info client.emit('connected', { @@ -89,9 +84,8 @@ export class ChatGateway } handleDisconnect(client: Socket) { - const user = this.users.get(client.id); + const user=client.data?.user if (user) { - this.users.delete(client.id); this.logger.log(`[-] Disconnected: ${user.username} (${client.id})`); this.server.emit('serverNotice', `[-] ${user.username} left`); } @@ -100,13 +94,13 @@ export class ChatGateway @UseGuards(WsJwtGuard) @SubscribeMessage('joinRoom') handleJoinRoom( - @MessageBody() body: JoinLeaveRoomDto, //roomId + @MessageBody() body: JoinLeaveRoomDto, //roomId(conversation id) @ConnectedSocket() client: Socket, ) { // extract user const username = client.data.user.username; - // join user in the room(front-end will provide roomId(standard appraoch)) + // join user in the room client.join(body.roomId); // tell the joiner he successfully joined @@ -170,7 +164,6 @@ export class ChatGateway @MessageBody() body: { targetUserId: string }, @ConnectedSocket() client: Socket, ) { - return this.chatService.handleStartP2P(body.targetUserId, client, this.server); } } diff --git a/src/chat/dto/chat-message.dto.ts b/src/chat/dto/chat-message.dto.ts deleted file mode 100644 index 94d009f..0000000 --- a/src/chat/dto/chat-message.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; - -export class ChatMessageDto { - @IsString({ message: 'text must be a string' }) - @IsNotEmpty({ message: 'text cannot be empty' }) - @MinLength(1, { message: 'Message is too short' }) - @MaxLength(500, { message: 'Message cannot exceed 500 characters' }) - text!: string; -} diff --git a/src/conversations/conversations.controller.ts b/src/conversations/conversations.controller.ts index 89ad9e7..f0cd808 100644 --- a/src/conversations/conversations.controller.ts +++ b/src/conversations/conversations.controller.ts @@ -1,37 +1,47 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { ConversationsService } from './conversations.service'; import { CreateConversationDto } from './dto/create-conversation.dto'; import { UpdateConversationDto } from './dto/update-conversation.dto'; import { ApiOperation } from '@nestjs/swagger'; +import path from 'path'; @Controller('conversations') export class ConversationsController { constructor(private readonly conversationsService: ConversationsService) {} - @ApiOperation({ summary: 'Create a conversation'}) + @ApiOperation({ summary: 'Create a conversation' }) @Post() async create(@Body() createConversationDto: CreateConversationDto) { return await this.conversationsService.create(createConversationDto); } - @ApiOperation({ summary: 'Get all conversations' }) - @Get() - async findAll() { - return await this.conversationsService.findAll(); + @ApiOperation({ summary: 'Get all conversations where the user is in' }) + @Get(':userId') + async findAll(@Param('userId') userId: string) { + return await this.conversationsService.findAll(userId); } - // @Get(':id') - // findOne(@Param('id') id: string) { - // return this.conversationsService.findOne(+id); - // } - - @Patch(':id') - update(@Param('id') id: string, @Body() updateConversationDto: UpdateConversationDto) { - return this.conversationsService.update(id, updateConversationDto); + @Patch(':convId/add-participant') + async addParticipant( + @Param('convId') convId: string, + @Body('userId') userId: string, + ) { + return await this.conversationsService.addParticipant(convId, userId); } - @Delete(':id') - remove(@Param('id') id: string) { - return this.conversationsService.remove(+id); + @Patch(':convId/remove-participant') + async removeParticipant( + @Param('convId') convId: string, + @Body('userId') userId: string, + ) { + return this.conversationsService.removeParticipant(convId, userId); } } diff --git a/src/conversations/conversations.service.ts b/src/conversations/conversations.service.ts index ebf1637..5600679 100644 --- a/src/conversations/conversations.service.ts +++ b/src/conversations/conversations.service.ts @@ -1,45 +1,92 @@ -import { Injectable } from '@nestjs/common'; +import { + ConflictException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { CreateConversationDto } from './dto/create-conversation.dto'; import { UpdateConversationDto } from './dto/update-conversation.dto'; -import { Conversation, ConversationDocument, ConversationType } from './schemas/conversation.schema'; +import { + Conversation, + ConversationDocument, + ConversationType, +} from './schemas/conversation.schema'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; +import { UserDocument } from 'src/users/schemas/user.schema'; + +type PopulatedConversation = Omit & { + participants: UserDocument[]; +}; @Injectable() export class ConversationsService { - - constructor( - @InjectModel(Conversation.name) private conversationModel: Model, - + constructor( + @InjectModel(Conversation.name) + private conversationModel: Model, ) {} - async create(createConversationDto: CreateConversationDto):Promise { - return await this.conversationModel.create(createConversationDto) + async create( + createConversationDto: CreateConversationDto, + ): Promise { + return await this.conversationModel.create(createConversationDto); } - async findAll():Promise { - return await this.conversationModel.find().exec() + async findAll(userId: string): Promise { + return await this.conversationModel + .find({ participants: userId }) + .populate<{ participants: UserDocument[] }>('participants', 'name') + .exec(); } - - async findP2p(senderId:string, targetUserId:string){ + + async findP2p( + senderId: string, + targetUserId: string, + ): Promise { return await this.conversationModel.findOne({ type: ConversationType.P2P, participants: { $all: [senderId, targetUserId], $size: 2 }, }); } - // findOne(id: number) { - // return `This action returns a #${id} conversation`; - // } + async addParticipant( + convId: string, + userId: string, + ): Promise { + const conv = await this.conversationModel.findById(convId); + if (!conv) { + throw new NotFoundException('Conversation not found'); + } + // prevent modifying p2p chat + if (conv.type === 'p2p') { + throw new ConflictException('Can not add users to P2P chat'); + } - async update(id: string, updateConversationDto: UpdateConversationDto) { - await this.conversationModel.findByIdAndUpdate( - {_id:id}, - updateConversationDto, - {new:true} - ) + return await this.conversationModel.findByIdAndUpdate( + convId, + { + $addToSet: { participants: userId }, // prevent duplicate addition + }, + { new: true }, + ); } - remove(id: number) { - return `This action removes a #${id} conversation`; + async removeParticipant( + conversationId: string, + userId: string, + ): Promise { + const convo = await this.conversationModel.findById(conversationId); + + if (!convo) throw new NotFoundException('Conversation not found'); + + if (convo.type === 'p2p') { + throw new ConflictException('Can not remove users from P2P chat'); + } + + return await this.conversationModel.findByIdAndUpdate( + conversationId, + { + $pull: { participants: userId }, + }, + { new: true }, + ); } } diff --git a/src/conversations/schemas/conversation.schema.ts b/src/conversations/schemas/conversation.schema.ts index c5f0b6d..d62daf6 100644 --- a/src/conversations/schemas/conversation.schema.ts +++ b/src/conversations/schemas/conversation.schema.ts @@ -1,5 +1,5 @@ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; -import { Document } from "mongoose"; +import { Document, Types } from "mongoose"; export type ConversationDocument = Conversation & Document; export enum ConversationType{ @@ -16,13 +16,13 @@ export class Conversation { }) type!:ConversationType - @Prop({type:[String], required:true}) - participants!:string[] + @Prop({type:[Types.ObjectId], ref:'User',required:true}) + participants!:Types.ObjectId[] @Prop({required:false,default:null}) name?:string // for p2p no name - createdAt!:Date // no prop + createdAt!:Date // no prop decorator } export const ConversationSchema = SchemaFactory.createForClass(Conversation); diff --git a/src/messages/messages.controller.ts b/src/messages/messages.controller.ts index 0f46d06..3c9a8e5 100644 --- a/src/messages/messages.controller.ts +++ b/src/messages/messages.controller.ts @@ -1,34 +1,16 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; import { MessagesService } from './messages.service'; -import { CreateMessageDto } from './dto/create-message.dto'; -import { UpdateMessageDto } from './dto/update-message.dto'; @Controller('messages') export class MessagesController { constructor(private readonly messagesService: MessagesService) {} - @Post() - create(@Body() createMessageDto: CreateMessageDto) { - return this.messagesService.create(createMessageDto); + + @Get(':convId') + async findAll(@Param('convId') conId:string) { + return await this.messagesService.findAll(conId); } - @Get() - findAll() { - return this.messagesService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.messagesService.findOne(+id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() updateMessageDto: UpdateMessageDto) { - return this.messagesService.update(+id, updateMessageDto); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.messagesService.remove(+id); - } + + } diff --git a/src/messages/messages.service.ts b/src/messages/messages.service.ts index c519281..fe6f2c0 100644 --- a/src/messages/messages.service.ts +++ b/src/messages/messages.service.ts @@ -4,6 +4,11 @@ import { UpdateMessageDto } from './dto/update-message.dto'; import { InjectModel } from '@nestjs/mongoose'; import { Message, MessageDocument } from './schemas/message.schema'; import { Model } from 'mongoose'; +import { UserDocument } from 'src/users/schemas/user.schema'; + +type PopulatedMessage = Omit & { + senderId: UserDocument; +}; @Injectable() export class MessagesService { @@ -14,19 +19,10 @@ export class MessagesService { return await this.MessageModel.create(createMessageDto); } - findAll() { - return `This action returns all messages`; - } - - findOne(id: number) { - return `This action returns a #${id} message`; - } - - update(id: number, updateMessageDto: UpdateMessageDto) { - return `This action updates a #${id} message`; - } - - remove(id: number) { - return `This action removes a #${id} message`; + async findAll(convId: string): Promise { + return await this.MessageModel.find({ conversationId: convId }) + .populate<{ senderId: UserDocument }>('senderId', 'name') + .sort({ createdAt: 1 }) + .exec(); } } diff --git a/src/messages/schemas/message.schema.ts b/src/messages/schemas/message.schema.ts index 5a3add6..69a5418 100644 --- a/src/messages/schemas/message.schema.ts +++ b/src/messages/schemas/message.schema.ts @@ -1,21 +1,21 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { Document } from 'mongoose'; +import { Document, Types } from 'mongoose'; export type MessageDocument = Message & Document; @Schema({ timestamps: true }) export class Message { - @Prop({ required: true }) - conversationId!: string; + @Prop({ type: Types.ObjectId, ref: 'Conversation', required: true }) + conversationId!: Types.ObjectId; - @Prop({ required: true }) - senderId!: string; + @Prop({ type: Types.ObjectId, ref: 'User', required: true }) + senderId!: Types.ObjectId; //we can pass string @Prop({ required: false }) text!: string; // no prop - createdAt!:Date + createdAt!: Date; } export const MessageSchema = SchemaFactory.createForClass(Message);