Skip to content

Commit

Permalink
Merge pull request #12 from ad956/fix/pusher-chat
Browse files Browse the repository at this point in the history
💬 feat(chats): Add real-time chat functionality with UI and backend updates
  • Loading branch information
ad956 authored Jan 9, 2025
2 parents c14ce4c + 7a60068 commit ba40a38
Show file tree
Hide file tree
Showing 20 changed files with 841 additions and 350 deletions.
48 changes: 3 additions & 45 deletions app/(pages)/patient/components/PatientTabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,55 +72,13 @@ export default function PatientTabs({ patient }: PatientTabsProps) {
>
<ChatScreen
currentUser={{
id: patient._id,
_id: patient._id,
firstname: patient.firstname,
lastname: patient.firstname,
lastname: patient.lastname,
specialty: "",
profile: patient.profile,
role: "patient",
role: "Patient",
}}
chatList={[
{
id: "66b5e0aeea614d6d3301c858",
firstname: "Dr. Alice Johnson",
lastname: "Dr. Alice Johnson",
specialty: "Cardiology",
profile: "https://randomuser.me/api/portraits/women/1.jpg",
role: "doctor",
},
{
id: "102",
firstname: "Dr. Mark Smith",
lastname: "Dr. Mark Smith",
specialty: "Neurology",
profile: "https://randomuser.me/api/portraits/men/2.jpg",
role: "doctor",
},
{
id: "103",
firstname: "Dr. Emily Davis",
lastname: "Dr. Emily Davis",
specialty: "Pediatrics",
profile: "https://randomuser.me/api/portraits/women/3.jpg",
role: "doctor",
},
{
id: "104",
firstname: "Dr. James Brown",
lastname: "Dr. James Brown",
specialty: "Orthopedics",
profile: "https://randomuser.me/api/portraits/men/4.jpg",
role: "doctor",
},
{
id: "105",
firstname: "Dr. Olivia Taylor",
lastname: "Dr. Olivia Taylor",
specialty: "Dermatology",
profile: "https://randomuser.me/api/portraits/women/5.jpg",
role: "doctor",
},
]}
/>
</Tab>

Expand Down
49 changes: 49 additions & 0 deletions app/api/chat/messages/read/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import dbConfig from "@utils/db";
import { NextResponse } from "next/server";
import { Message } from "@models/index";
import { Types } from "mongoose";
import { errorHandler, STATUS_CODES } from "@utils/index";

export async function POST(req: Request) {
try {
const authHeader = req.headers.get("Authorization");

// Authorization check
if (!authHeader) {
return errorHandler(
"Authorization header is missing",
STATUS_CODES.UNAUTHORIZED
);
}

await dbConfig();
const { roomId, userId } = await req.json();

if (!roomId || !userId) {
return errorHandler(
"roomId and userId are required",
STATUS_CODES.BAD_REQUEST
);
}

// mark all unread messages in the room as read
await Message.updateMany(
{
roomId: new Types.ObjectId(roomId),
senderId: { $ne: new Types.ObjectId(userId) },
isRead: false,
},
{
isRead: true,
}
);

return NextResponse.json({ success: true });
} catch (error) {
console.error("Error marking messages as read:", error);
return errorHandler(
"Failed to mark messages as read",
STATUS_CODES.SERVER_ERROR
);
}
}
84 changes: 84 additions & 0 deletions app/api/chat/messages/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import dbConfig from "@utils/db";
import { NextResponse } from "next/server";
import { Patient, Doctor, Message, Room } from "@models/index";
import { pusherServer } from "@lib/pusher";
import { Types } from "mongoose";
import { errorHandler, STATUS_CODES } from "@utils/index";

export async function GET(req: Request) {
try {
const authHeader = req.headers.get("Authorization");

// Authorization check
if (!authHeader) {
return errorHandler(
"Authorization header is missing",
STATUS_CODES.UNAUTHORIZED
);
}

await dbConfig();
const { searchParams } = new URL(req.url);
const roomId = searchParams.get("roomId");
const page = parseInt(searchParams.get("page") || "1");
const limit = parseInt(searchParams.get("limit") || "50");

if (!roomId) {
return errorHandler("roomId is required", STATUS_CODES.BAD_REQUEST);
}

const messages = await Message.find({ roomId: new Types.ObjectId(roomId) })
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(limit)
.populate("senderId", "firstname lastname profile")
.sort({ createdAt: 1 });

return NextResponse.json(messages);
} catch (error) {
console.error("Error fetching messages:", error);
return errorHandler("Failed to fetch messages", STATUS_CODES.SERVER_ERROR);
}
}

export async function POST(req: Request) {
try {
const authHeader = req.headers.get("Authorization");

// Authorization check
if (!authHeader) {
return errorHandler(
"Authorization header is missing",
STATUS_CODES.UNAUTHORIZED
);
}

await dbConfig();
const { roomId, senderId, senderRole, message } = await req.json();

if (!roomId || !senderId || !senderRole || !message) {
return errorHandler("Missing required fields", STATUS_CODES.BAD_REQUEST);
}

const newMessage = await Message.create({
roomId: new Types.ObjectId(roomId),
senderId: new Types.ObjectId(senderId),
senderRole,
message,
});

// update room's lastMessage and timestamp
await Room.findByIdAndUpdate(new Types.ObjectId(roomId), {
lastMessage: newMessage._id,
updatedAt: new Date(),
});

// trigger Pusher event
await pusherServer.trigger(`chat-${roomId}`, "new-message", newMessage);

return NextResponse.json(newMessage);
} catch (error) {
console.error("Error sending message:", error);
return errorHandler("Failed to send message", STATUS_CODES.SERVER_ERROR);
}
}
119 changes: 119 additions & 0 deletions app/api/chat/room/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { NextResponse } from "next/server";
import dbConfig from "@utils/db";
import { Room } from "@models/chat";
import { Types } from "mongoose";
import { errorHandler, STATUS_CODES } from "@utils/index";

export async function GET(req: Request) {
try {
const authHeader = req.headers.get("Authorization");

// Authorization check
if (!authHeader) {
return errorHandler(
"Authorization header is missing",
STATUS_CODES.UNAUTHORIZED
);
}

await dbConfig();
const { searchParams } = new URL(req.url);
const userId = searchParams.get("userId");
const role = searchParams.get("role");

if (!userId || !role) {
return errorHandler("Missing userId or role", STATUS_CODES.BAD_REQUEST);
}

// find all rooms where the user is a participant
const rooms = await Room.find({
participants: {
$elemMatch: { userId: new Types.ObjectId(userId), role },
},
})
.populate([
{
path: "participants.userId",
match: { role: { $ne: role } }, // get the other participant's info
select: "firstname lastname profile specialty",
},
{
path: "lastMessage",
select: "message createdAt isRead",
},
])
.sort({ updatedAt: -1 });

return NextResponse.json(rooms);
} catch (error) {
console.error("Error fetching chat rooms:", error);
return errorHandler(
"Failed to fetch chat rooms",
STATUS_CODES.SERVER_ERROR
);
}
}

export async function POST(req: Request) {
try {
const authHeader = req.headers.get("Authorization");

// Authorization check
if (!authHeader) {
return errorHandler(
"Authorization header is missing",
STATUS_CODES.UNAUTHORIZED
);
}

await dbConfig();
const { patientId, doctorId } = await req.json();

if (!patientId || !doctorId) {
return errorHandler(
"Both patientId and doctorId are required",
STATUS_CODES.BAD_REQUEST
);
}

// check if room already exists
const existingRoom = await Room.findOne({
participants: {
$all: [
{
$elemMatch: {
userId: new Types.ObjectId(patientId),
role: "Patient",
},
},
{
$elemMatch: {
userId: new Types.ObjectId(doctorId),
role: "Doctor",
},
},
],
},
});

if (existingRoom) {
return NextResponse.json(existingRoom);
}

// create new room
const room = await Room.create({
participants: [
{ userId: patientId, role: "Patient" },
{ userId: doctorId, role: "Doctor" },
],
});

return NextResponse.json(room);
} catch (error) {
console.error("Error creating chat room:", error);
return errorHandler(
"Failed to create chat room",
STATUS_CODES.SERVER_ERROR
);
}
}
65 changes: 0 additions & 65 deletions app/api/chat/route.ts

This file was deleted.

3 changes: 3 additions & 0 deletions app/api/update-profile/personal/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { PersonalInfoBody } from "@pft-types/index";
import { Types } from "mongoose";
import { authenticateUser } from "@lib/auth";
import { revalidateTag } from "next/cache";

export async function PUT(req: Request) {
const authHeader = req.headers.get("Authorization");
Expand Down Expand Up @@ -80,6 +81,8 @@ export async function PUT(req: Request) {
return errorHandler(`${role} not found`, STATUS_CODES.NOT_FOUND);
}

revalidateTag("profile");

return NextResponse.json(
{ message: "Profile updated successfully" },
{ status: 200 }
Expand Down
Loading

0 comments on commit ba40a38

Please sign in to comment.