added more services and used table referencing

This commit is contained in:
Suman991 2026-04-17 01:04:58 +05:30
parent b885159784
commit 820fc9934c
8 changed files with 128 additions and 109 deletions

View File

@ -34,7 +34,7 @@ export class ChatGateway
private readonly logger = new Logger(ChatGateway.name); private readonly logger = new Logger(ChatGateway.name);
private users = new Map<string, { userId: string; username: string }>(); // {socketId:{userId:'12rt3',username:'suman'}} // private users = new Map<string, { userId: string; username: string }>(); // {socketId:{userId:'12rt3',username:'suman'}} // redundant
afterInit(server: Server) { afterInit(server: Server) {
this.logger.log('/chat namespace initialized'); this.logger.log('/chat namespace initialized');
@ -61,11 +61,6 @@ export class ChatGateway
id: payload.sub, // mongo id id: payload.sub, // mongo id
username: payload.username, username: payload.username,
}; };
// in memeory
this.users.set(client.id, {
userId: payload.sub,
username: payload.username,
});
this.logger.log(`[+] Authenticated: ${payload.username} (${client.id})`); this.logger.log(`[+] Authenticated: ${payload.username} (${client.id})`);
// tell the new client their own info // tell the new client their own info
@ -89,9 +84,8 @@ export class ChatGateway
} }
handleDisconnect(client: Socket) { handleDisconnect(client: Socket) {
const user = this.users.get(client.id); const user=client.data?.user
if (user) { if (user) {
this.users.delete(client.id);
this.logger.log(`[-] Disconnected: ${user.username} (${client.id})`); this.logger.log(`[-] Disconnected: ${user.username} (${client.id})`);
this.server.emit('serverNotice', `[-] ${user.username} left`); this.server.emit('serverNotice', `[-] ${user.username} left`);
} }
@ -100,13 +94,13 @@ export class ChatGateway
@UseGuards(WsJwtGuard) @UseGuards(WsJwtGuard)
@SubscribeMessage('joinRoom') @SubscribeMessage('joinRoom')
handleJoinRoom( handleJoinRoom(
@MessageBody() body: JoinLeaveRoomDto, //roomId @MessageBody() body: JoinLeaveRoomDto, //roomId(conversation id)
@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
client.join(body.roomId); client.join(body.roomId);
// tell the joiner he successfully joined // tell the joiner he successfully joined
@ -170,7 +164,6 @@ export class ChatGateway
@MessageBody() body: { targetUserId: string }, @MessageBody() body: { targetUserId: string },
@ConnectedSocket() client: Socket, @ConnectedSocket() client: Socket,
) { ) {
return this.chatService.handleStartP2P(body.targetUserId, client, this.server); return this.chatService.handleStartP2P(body.targetUserId, client, this.server);
} }
} }

View File

@ -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;
}

View File

@ -1,8 +1,17 @@
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 { ConversationsService } from './conversations.service';
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 { ApiOperation } from '@nestjs/swagger'; import { ApiOperation } from '@nestjs/swagger';
import path from 'path';
@Controller('conversations') @Controller('conversations')
export class ConversationsController { export class ConversationsController {
@ -14,24 +23,25 @@ export class ConversationsController {
return await this.conversationsService.create(createConversationDto); return await this.conversationsService.create(createConversationDto);
} }
@ApiOperation({ summary: 'Get all conversations' }) @ApiOperation({ summary: 'Get all conversations where the user is in' })
@Get() @Get(':userId')
async findAll() { async findAll(@Param('userId') userId: string) {
return await this.conversationsService.findAll(); return await this.conversationsService.findAll(userId);
} }
// @Get(':id') @Patch(':convId/add-participant')
// findOne(@Param('id') id: string) { async addParticipant(
// return this.conversationsService.findOne(+id); @Param('convId') convId: string,
// } @Body('userId') userId: string,
) {
@Patch(':id') return await this.conversationsService.addParticipant(convId, userId);
update(@Param('id') id: string, @Body() updateConversationDto: UpdateConversationDto) {
return this.conversationsService.update(id, updateConversationDto);
} }
@Delete(':id') @Patch(':convId/remove-participant')
remove(@Param('id') id: string) { async removeParticipant(
return this.conversationsService.remove(+id); @Param('convId') convId: string,
@Body('userId') userId: string,
) {
return this.conversationsService.removeParticipant(convId, userId);
} }
} }

View File

@ -1,45 +1,92 @@
import { Injectable } from '@nestjs/common'; import {
ConflictException,
Injectable,
NotFoundException,
} 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, ConversationType } 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';
import { UserDocument } from 'src/users/schemas/user.schema';
type PopulatedConversation = Omit<ConversationDocument, 'participants'> & {
participants: UserDocument[];
};
@Injectable() @Injectable()
export class ConversationsService { export class ConversationsService {
constructor( constructor(
@InjectModel(Conversation.name) private conversationModel: Model<ConversationDocument>, @InjectModel(Conversation.name)
private conversationModel: Model<ConversationDocument>,
) {} ) {}
async create(createConversationDto: CreateConversationDto):Promise<ConversationDocument> { async create(
return await this.conversationModel.create(createConversationDto) createConversationDto: CreateConversationDto,
): Promise<ConversationDocument> {
return await this.conversationModel.create(createConversationDto);
} }
async findAll():Promise<ConversationDocument[]> { async findAll(userId: string): Promise<PopulatedConversation[]> {
return await this.conversationModel.find().exec() 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<ConversationDocument | null> {
return await this.conversationModel.findOne({ return await this.conversationModel.findOne({
type: ConversationType.P2P, type: ConversationType.P2P,
participants: { $all: [senderId, targetUserId], $size: 2 }, participants: { $all: [senderId, targetUserId], $size: 2 },
}); });
} }
// findOne(id: number) { async addParticipant(
// return `This action returns a #${id} conversation`; convId: string,
// } userId: string,
): Promise<ConversationDocument | null> {
async update(id: string, updateConversationDto: UpdateConversationDto) { const conv = await this.conversationModel.findById(convId);
await this.conversationModel.findByIdAndUpdate( if (!conv) {
{_id:id}, throw new NotFoundException('Conversation not found');
updateConversationDto, }
{new:true} // prevent modifying p2p chat
) if (conv.type === 'p2p') {
throw new ConflictException('Can not add users to P2P chat');
} }
remove(id: number) { return await this.conversationModel.findByIdAndUpdate(
return `This action removes a #${id} conversation`; convId,
{
$addToSet: { participants: userId }, // prevent duplicate addition
},
{ new: true },
);
}
async removeParticipant(
conversationId: string,
userId: string,
): Promise<ConversationDocument | null> {
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 },
);
} }
} }

View File

@ -1,5 +1,5 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose"; import { Document, Types } from "mongoose";
export type ConversationDocument = Conversation & Document; export type ConversationDocument = Conversation & Document;
export enum ConversationType{ export enum ConversationType{
@ -16,13 +16,13 @@ export class Conversation {
}) })
type!:ConversationType type!:ConversationType
@Prop({type:[String], required:true}) @Prop({type:[Types.ObjectId], ref:'User',required:true})
participants!:string[] participants!:Types.ObjectId[]
@Prop({required:false,default:null}) @Prop({required:false,default:null})
name?:string // for p2p no name name?:string // for p2p no name
createdAt!:Date // no prop createdAt!:Date // no prop decorator
} }
export const ConversationSchema = SchemaFactory.createForClass(Conversation); export const ConversationSchema = SchemaFactory.createForClass(Conversation);

View File

@ -1,34 +1,16 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { MessagesService } from './messages.service'; import { MessagesService } from './messages.service';
import { CreateMessageDto } from './dto/create-message.dto';
import { UpdateMessageDto } from './dto/update-message.dto';
@Controller('messages') @Controller('messages')
export class MessagesController { export class MessagesController {
constructor(private readonly messagesService: MessagesService) {} constructor(private readonly messagesService: MessagesService) {}
@Post()
create(@Body() createMessageDto: CreateMessageDto) { @Get(':convId')
return this.messagesService.create(createMessageDto); 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);
}
} }

View File

@ -4,6 +4,11 @@ import { UpdateMessageDto } from './dto/update-message.dto';
import { InjectModel } from '@nestjs/mongoose'; import { InjectModel } from '@nestjs/mongoose';
import { Message, MessageDocument } from './schemas/message.schema'; import { Message, MessageDocument } from './schemas/message.schema';
import { Model } from 'mongoose'; import { Model } from 'mongoose';
import { UserDocument } from 'src/users/schemas/user.schema';
type PopulatedMessage = Omit<MessageDocument, 'senderId'> & {
senderId: UserDocument;
};
@Injectable() @Injectable()
export class MessagesService { export class MessagesService {
@ -14,19 +19,10 @@ export class MessagesService {
return await this.MessageModel.create(createMessageDto); return await this.MessageModel.create(createMessageDto);
} }
findAll() { async findAll(convId: string): Promise<PopulatedMessage[]> {
return `This action returns all messages`; return await this.MessageModel.find({ conversationId: convId })
} .populate<{ senderId: UserDocument }>('senderId', 'name')
.sort({ createdAt: 1 })
findOne(id: number) { .exec();
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`;
} }
} }

View File

@ -1,21 +1,21 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose'; import { Document, Types } 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({ type: Types.ObjectId, ref: 'Conversation', required: true })
conversationId!: string; conversationId!: Types.ObjectId;
@Prop({ required: true }) @Prop({ type: Types.ObjectId, ref: 'User', required: true })
senderId!: string; senderId!: Types.ObjectId; //we can pass string
@Prop({ required: false }) @Prop({ required: false })
text!: string; text!: string;
// no prop // no prop
createdAt!:Date createdAt!: Date;
} }
export const MessageSchema = SchemaFactory.createForClass(Message); export const MessageSchema = SchemaFactory.createForClass(Message);