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 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) {
this.logger.log('/chat namespace initialized');
@ -61,11 +61,6 @@ 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
@ -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);
}
}

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,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);
}
}

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 { 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<ConversationDocument, 'participants'> & {
participants: UserDocument[];
};
@Injectable()
export class ConversationsService {
constructor(
@InjectModel(Conversation.name) private conversationModel: Model<ConversationDocument>,
@InjectModel(Conversation.name)
private conversationModel: Model<ConversationDocument>,
) {}
async create(createConversationDto: CreateConversationDto):Promise<ConversationDocument> {
return await this.conversationModel.create(createConversationDto)
async create(
createConversationDto: CreateConversationDto,
): Promise<ConversationDocument> {
return await this.conversationModel.create(createConversationDto);
}
async findAll():Promise<ConversationDocument[]> {
return await this.conversationModel.find().exec()
async findAll(userId: string): Promise<PopulatedConversation[]> {
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({
type: ConversationType.P2P,
participants: { $all: [senderId, targetUserId], $size: 2 },
});
}
// findOne(id: number) {
// return `This action returns a #${id} conversation`;
// }
async update(id: string, updateConversationDto: UpdateConversationDto) {
await this.conversationModel.findByIdAndUpdate(
{_id:id},
updateConversationDto,
{new:true}
)
async addParticipant(
convId: string,
userId: string,
): Promise<ConversationDocument | null> {
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');
}
remove(id: number) {
return `This action removes a #${id} conversation`;
return await this.conversationModel.findByIdAndUpdate(
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 { 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);

View File

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

View File

@ -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<MessageDocument, 'senderId'> & {
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<PopulatedMessage[]> {
return await this.MessageModel.find({ conversationId: convId })
.populate<{ senderId: UserDocument }>('senderId', 'name')
.sort({ createdAt: 1 })
.exec();
}
}

View File

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