diff --git a/server/src/domain/entities/IChat.ts b/server/src/domain/entities/IChat.ts index 74385af3..e930fa3a 100644 --- a/server/src/domain/entities/IChat.ts +++ b/server/src/domain/entities/IChat.ts @@ -6,4 +6,8 @@ export default interface IChat { readonly updatedAt?: Date; readonly doctorName?: string; readonly patientName?: string; +} + +export interface IChatWithNotSeenCount extends IChat{ + notSeenMessages?:number; } \ No newline at end of file diff --git a/server/src/domain/interface/repositories/IMessageRepository.ts b/server/src/domain/interface/repositories/IMessageRepository.ts index 899af31e..4b58c14f 100644 --- a/server/src/domain/interface/repositories/IMessageRepository.ts +++ b/server/src/domain/interface/repositories/IMessageRepository.ts @@ -4,6 +4,7 @@ import { PaginatedResult } from "../../../types"; export default interface IMessageRepository { create(message: IMessage): Promise; findById(_id: string): Promise; - findByChatId(chatId: string, limit: number, offset:number): Promise>; + findByChatId(chatId: string, limit: number, offset: number): Promise>; markAsRead(messageId: string): Promise; + getUnreadMessageCountGroupedByChat(receiverId: string): Promise<{ _id: string, count: number }[]>; } diff --git a/server/src/infrastructure/repositories/MessageRepository.ts b/server/src/infrastructure/repositories/MessageRepository.ts index f064621f..aeafc13d 100644 --- a/server/src/infrastructure/repositories/MessageRepository.ts +++ b/server/src/infrastructure/repositories/MessageRepository.ts @@ -20,4 +20,11 @@ export default class MessageRepository implements IMessageRepository { async markAsRead(messageId: string): Promise { await this.model.findByIdAndUpdate(messageId, { isReceived: true }); } + async getUnreadMessageCountGroupedByChat(receiverId: string): Promise<{ _id: string, count: number }[]> { + return await this.model.aggregate([ + { $match: { receiverId, isReceived: false } }, + { $group: { _id: "$chatId", count: { $sum: 1 } } } + ]); + } + } \ No newline at end of file diff --git a/server/src/presentation/controllers/admin/DoctorController.ts b/server/src/presentation/controllers/admin/DoctorController.ts index 6003fed1..29d88d2d 100644 --- a/server/src/presentation/controllers/admin/DoctorController.ts +++ b/server/src/presentation/controllers/admin/DoctorController.ts @@ -8,8 +8,8 @@ export default class AdminDoctorController { async getDoctors(req: Request, res: Response, next: NextFunction) { try { - let offset = parseInt(req.query.offset as string); - let limit = parseInt(req.query.limit as string); + let offset = +(req.query.offset as string); + let limit = +(req.query.limit as string); const type = req.query.type as DoctorsFilter; offset = isNaN(offset) || offset < 0 ? 0 : offset; diff --git a/server/src/presentation/controllers/admin/PatientController.ts b/server/src/presentation/controllers/admin/PatientController.ts index 9b63a92e..28a51820 100644 --- a/server/src/presentation/controllers/admin/PatientController.ts +++ b/server/src/presentation/controllers/admin/PatientController.ts @@ -7,8 +7,8 @@ export default class AdminPatientController { async getPatients(req: Request, res: Response, next: NextFunction) { try { - const offset = parseInt(req.query.offset as string) || 0; - const limit = parseInt(req.query.limit as string) || 10; + const offset = +(req.query.offset as string) || 0; + const limit = +(req.query.limit as string) || 10; const patients = await this.adminPatientUseCase.getAll(offset, limit); res.status(StatusCode.Success).json(patients); diff --git a/server/src/presentation/controllers/appointment/AppointmentControllers.ts b/server/src/presentation/controllers/appointment/AppointmentControllers.ts index 46a1e2e1..a1472d62 100644 --- a/server/src/presentation/controllers/appointment/AppointmentControllers.ts +++ b/server/src/presentation/controllers/appointment/AppointmentControllers.ts @@ -39,8 +39,8 @@ export default class AppointmentController { try { const doctorId = req.doctor?.id; const status = req.query.status as AppointmentStatus | "undefined"; - let offset = parseInt(req.query.offset as string); - let limit = parseInt(req.query.limit as string); + let offset = +(req.query.offset as string); + let limit = +(req.query.limit as string); offset = isNaN(offset) || offset < 0 ? 0 : offset; limit = isNaN(limit) || limit < 0 ? 10 : Math.min(limit, 100); @@ -81,8 +81,8 @@ export default class AppointmentController { try { const patientId = req.patient?.id; const status = req.query.status as AppointmentStatus | "undefined"; - let offset = parseInt(req.query.offset as string); - let limit = parseInt(req.query.limit as string); + let offset = +(req.query.offset as string); + let limit = +(req.query.limit as string); offset = isNaN(offset) || offset < 0 ? 0 : offset; limit = isNaN(limit) || limit < 0 ? 10 : Math.min(limit, 100); diff --git a/server/src/presentation/controllers/chat/ChatControllers.ts b/server/src/presentation/controllers/chat/ChatControllers.ts index 9b125123..419e6a67 100644 --- a/server/src/presentation/controllers/chat/ChatControllers.ts +++ b/server/src/presentation/controllers/chat/ChatControllers.ts @@ -8,12 +8,41 @@ export default class ChatController { private createChatUseCase: CreateChatUseCase, private getChatUseCase: GetChatUseCase ) { } + async getChatsOfPatient(req: CustomRequest, res: Response, next: NextFunction) { + try { + const patientId = req.patient?.id; + const chats = await this.getChatUseCase.getAllChatsWithPatientId(patientId!); + res.status(StatusCode.Success).json(chats) + } catch (error) { + next(error) + } + } + async getChatsOfDoctor(req: CustomRequest, res: Response, next: NextFunction) { + try { + const doctorId = req.doctor?.id; + const chats = await this.getChatUseCase.getAllChatsWithDoctorId(doctorId!); + res.status(StatusCode.Success).json(chats) + } catch (error) { + next(error) + } + } + async getMessagesOfChat(req: CustomRequest, res: Response, next: NextFunction) { + try { + const chatId = req.params.chatId; + let limit = +(req.query.limit as string); + limit = isNaN(limit) || limit < 0 ? 10 : Math.min(limit, 100); + const messages = await this.getChatUseCase.getMessagesOfChat(chatId, limit); + res.status(StatusCode.Success).json(messages); + } catch (error) { + next(error) + } + } async createChatPatient(req: CustomRequest, res: Response, next: NextFunction) { try { const patientId = req.patient?.id; const { doctorId } = req.body; await this.createChatUseCase.createChat(doctorId, patientId!); - res.status(StatusCode.Success).json({ message: "Chat has created" }); + res.status(StatusCode.Created) } catch (error: any) { next(error); } @@ -23,7 +52,7 @@ export default class ChatController { const doctorId = req.doctor?.id; const { patientId } = req.body; await this.createChatUseCase.createChat(doctorId!, patientId); - res.status(StatusCode.Success).json({ message: "Chat has created" }); + res.status(StatusCode.Created) } catch (error: any) { next(error); } @@ -33,7 +62,7 @@ export default class ChatController { const doctorId = req.doctor?.id; const { chatId, patientId, message } = req.body; await this.createChatUseCase.createMessage(chatId, patientId, message, doctorId!); - res.status(StatusCode.Success).json({ message: "Chat has created" }); + res.status(StatusCode.Created) } catch (error: any) { next(error); } @@ -43,7 +72,7 @@ export default class ChatController { const doctorId = req.doctor?.id; const { chatId, patientId, message } = req.body; await this.createChatUseCase.createMessage(chatId, patientId, message, doctorId!); - res.status(StatusCode.Success).json({ message: "Chat has created" }); + res.status(StatusCode.Created) } catch (error: any) { next(error); } diff --git a/server/src/presentation/routers/chat/ChatRoutes.ts b/server/src/presentation/routers/chat/ChatRoutes.ts index bdf5934c..6b483404 100644 --- a/server/src/presentation/routers/chat/ChatRoutes.ts +++ b/server/src/presentation/routers/chat/ChatRoutes.ts @@ -11,21 +11,34 @@ import JWTService from '../../../infrastructure/services/JWTService'; import PatientAuthMiddleware from '../../middlewares/PatientAuthMiddleware'; import DoctorAuthMiddleware from '../../middlewares/DoctorAuthMiddleware'; -const router = express.Router() +const router = express.Router(); -const validatorService = new JoiService() +const validatorService = new JoiService(); const tokenService = new JWTService(); const chatRepository = new ChatRepository(); const messageRepository = new MessageRepository(); const patientRepository = new PatientRepository(); const doctorRepository = new DoctorRepository(); -const createChatUseCase = new CreateChatUseCase(messageRepository, chatRepository, validatorService, patientRepository, doctorRepository); -const getChatUseCase = new GetChatUseCase(messageRepository, chatRepository, validatorService); +const createChatUseCase = new CreateChatUseCase( + messageRepository, chatRepository, validatorService, patientRepository, doctorRepository +); +const getChatUseCase = new GetChatUseCase( + messageRepository, chatRepository, validatorService +); const chatController = new ChatController(createChatUseCase, getChatUseCase); const authorizePatient = new PatientAuthMiddleware(tokenService); const authorizeDoctor = new DoctorAuthMiddleware(tokenService); +router.get('/patient', authorizePatient.exec, chatController.getChatsOfPatient.bind(chatController)); +router.post('/patient', authorizePatient.exec, chatController.createChatPatient.bind(chatController)); +router.post('/patient/message', authorizePatient.exec, chatController.createMessagePatient.bind(chatController)); +router.get('/patient/message/:chatId', authorizePatient.exec, chatController.getMessagesOfChat.bind(chatController)); -export default router \ No newline at end of file +router.get('/doctor', authorizeDoctor.exec, chatController.getChatsOfDoctor.bind(chatController)); +router.get('/doctor/message/:chatId', authorizeDoctor.exec, chatController.getMessagesOfChat.bind(chatController)); +router.post('/doctor', authorizeDoctor.exec, chatController.createChatDoctor.bind(chatController)); +router.post('/doctor/message', authorizeDoctor.exec, chatController.createMessageDoctor.bind(chatController)); + +export default router; \ No newline at end of file diff --git a/server/src/use_case/chat/CreateChatUseCase.ts b/server/src/use_case/chat/CreateChatUseCase.ts index 94e153fb..2a6ffdfd 100644 --- a/server/src/use_case/chat/CreateChatUseCase.ts +++ b/server/src/use_case/chat/CreateChatUseCase.ts @@ -31,6 +31,6 @@ export default class CreateChatUseCase { this.validatorService.validateRequiredFields({ chatId, receiverId, message, senderId }); this.validatorService.validateMultipleIds([chatId, receiverId, senderId]); this.validatorService.validateLength(message, 1); - await this.messageRepository.create({ chatId, message, receiverId, senderId }); + await this.messageRepository.create({ chatId, message, receiverId, senderId, isReceived: false }); } } \ No newline at end of file diff --git a/server/src/use_case/chat/GetChatUseCase.ts b/server/src/use_case/chat/GetChatUseCase.ts index e31a40eb..07097941 100644 --- a/server/src/use_case/chat/GetChatUseCase.ts +++ b/server/src/use_case/chat/GetChatUseCase.ts @@ -1,4 +1,4 @@ -import IChat from "../../domain/entities/IChat"; +import IChat, { IChatWithNotSeenCount } from "../../domain/entities/IChat"; import IMessage from "../../domain/entities/IMessage"; import IChatRepository from "../../domain/interface/repositories/IChatRepository"; import IMessageRepository from "../../domain/interface/repositories/IMessageRepository"; @@ -12,17 +12,39 @@ export default class GetChatUseCase { private validatorService: IValidatorService ) { } - async getAllChatsWithPatientId(patientId: string): Promise { + async getAllChatsWithPatientId(patientId: string): Promise { this.validatorService.validateIdFormat(patientId); - return await this.chatRepository.findAllChatsForPatient(patientId); + const chats = await this.chatRepository.findAllChatsForPatient(patientId); + return await this.getChatsWithNotSeenMessages(patientId, chats); } - async getAllChatsWithDoctorId(doctorId: string): Promise { + + async getAllChatsWithDoctorId(doctorId: string): Promise { this.validatorService.validateIdFormat(doctorId); - return await this.chatRepository.findAllChatsForDoctor(doctorId); + const chats = await this.chatRepository.findAllChatsForDoctor(doctorId); + return await this.getChatsWithNotSeenMessages(doctorId, chats); } - async getMessagesOfChat(chatId:string,limit:number):Promise>{ + + async getMessagesOfChat(chatId: string, limit: number): Promise> { this.validatorService.validateIdFormat(chatId); const offset = 0; - return await this.messageRepository.findByChatId(chatId,limit,offset); + return await this.messageRepository.findByChatId(chatId, limit, offset); + } + + private async getChatsWithNotSeenMessages( + receiverId: string, + chats: IChatWithNotSeenCount[] + ): Promise { + const unreadMessages = await this.messageRepository.getUnreadMessageCountGroupedByChat(receiverId); + + const unreadMessagesMap = unreadMessages.reduce((acc, messageCount) => { + acc[messageCount._id] = messageCount.count; + return acc; + }, {} as { [chatId: string]: number }); + + const updatedChats = chats.map(chat => ({ + ...chat, + notSeenMessages: unreadMessagesMap[chat._id!] || 0, + })); + return updatedChats.length ? updatedChats : []; } } \ No newline at end of file