added more services and used table referencing
This commit is contained in:
parent
b885159784
commit
820fc9934c
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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 { 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 {
|
||||||
constructor(private readonly conversationsService: ConversationsService) {}
|
constructor(private readonly conversationsService: ConversationsService) {}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Create a conversation'})
|
@ApiOperation({ summary: 'Create a conversation' })
|
||||||
@Post()
|
@Post()
|
||||||
async create(@Body() createConversationDto: CreateConversationDto) {
|
async create(@Body() createConversationDto: CreateConversationDto) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
@InjectModel(Conversation.name) private conversationModel: Model<ConversationDocument>,
|
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> {
|
||||||
|
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) {
|
return await this.conversationModel.findByIdAndUpdate(
|
||||||
await this.conversationModel.findByIdAndUpdate(
|
convId,
|
||||||
{_id:id},
|
{
|
||||||
updateConversationDto,
|
$addToSet: { participants: userId }, // prevent duplicate addition
|
||||||
{new:true}
|
},
|
||||||
)
|
{ new: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(id: number) {
|
async removeParticipant(
|
||||||
return `This action removes a #${id} conversation`;
|
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 },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user