Skip to content

Commit 654b91d

Browse files
authored
Merge branch 'main' into feat/collab-service/ai-chat
2 parents 3773872 + 3ecd4f3 commit 654b91d

File tree

13 files changed

+285
-32
lines changed

13 files changed

+285
-32
lines changed

collab-service/app/controller/collab-controller.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
getAllRooms,
55
fetchRoomChatHistory,
66
getQuestionIdByRoomId,
7+
getAllRoomsByUserId,
78
} from "../model/repository.js";
89
import crypto from "crypto";
910

@@ -12,11 +13,13 @@ export async function createRoom(req, res) {
1213
const { user1, user2, question_id } = req.body;
1314

1415
if (!user1 || !user2 || !question_id) {
15-
return res.status(400).json({ error: "user1,user2 and question_id are required" });
16+
return res
17+
.status(400)
18+
.json({ error: "user1,user2 and question_id are required" });
1619
}
1720

1821
// Generate a unique room ID by hashing the two user IDs
19-
const timeSalt = new Date().toISOString().slice(0, 13);
22+
const timeSalt = new Date().toISOString().slice(0, 19);
2023
const roomId = crypto
2124
.createHash("sha256")
2225
.update(user1 + user2 + timeSalt)
@@ -58,6 +61,17 @@ export async function getAllRoomsController(req, res) {
5861
}
5962
}
6063

64+
// Get all rooms by user
65+
export async function getAllRoomsByUser(req, res) {
66+
const { user } = req.params;
67+
const rooms = await getAllRoomsByUserId(user);
68+
69+
if (rooms) {
70+
res.status(200).json(rooms);
71+
} else {
72+
res.status(500).json({ error: "Failed to retrieve rooms" });
73+
}
74+
}
6175
export async function getRoomChatHistory(req, res) {
6276
const { roomId } = req.params;
6377

@@ -86,6 +100,8 @@ export async function getQuestionId(req, res) {
86100
if (questionId) {
87101
res.status(200).json({ questionId });
88102
} else {
89-
res.status(404).json({ error: `Question ID not found for room ID: ${roomId}` });
103+
res
104+
.status(404)
105+
.json({ error: `Question ID not found for room ID: ${roomId}` });
90106
}
91-
}
107+
}

collab-service/app/model/repository.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ export async function connectToMongo() {
77

88
export async function newRoom(user1, user2, roomId, questionId) {
99
try {
10-
// Remove any existing rooms where either user1 or user2 is a participant
11-
await UsersSession.deleteMany({ users: { $in: [user1, user2] } });
12-
1310
const newRoom = new UsersSession({
1411
users: [user1, user2],
1512
roomId: roomId,
@@ -45,6 +42,16 @@ export async function getAllRooms() {
4542
}
4643
}
4744

45+
export async function getAllRoomsByUserId(user) {
46+
try {
47+
const rooms = await UsersSession.find({ users: user });
48+
return rooms;
49+
} catch (error) {
50+
console.error("Error getting all rooms of user:", error);
51+
return null;
52+
}
53+
}
54+
4855
// Function to add a new message to chatHistory with transaction support
4956
export async function addMessageToChat(roomId, userId, text) {
5057
// Start a session for the transaction
@@ -116,7 +123,6 @@ export async function getQuestionIdByRoomId(roomId) {
116123

117124
export async function sendAiMessage(message) {
118125
try {
119-
console.log("sending to openai");
120126
const response = await fetch("https://api.openai.com/v1/chat/completions", {
121127
method: "POST",
122128
headers: {

collab-service/app/model/usersSession-model.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const messageSchema = new Schema({
1515
type: String,
1616
required: true,
1717
},
18+
timestamp: {
19+
type: Date,
20+
required: true,
21+
default: Date.now
22+
},
1823
});
1924

2025
const usersSessionSchema = new Schema({

collab-service/app/routes/collab-routes.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import {
33
createRoom,
44
getRoomByUser,
55
getAllRoomsController,
6+
getAllRoomsByUser,
67
getRoomChatHistory,
7-
getQuestionId
8+
getQuestionId,
89
} from "../controller/collab-controller.js";
910
import { sendAiMessageController } from "../controller/ai-controller.js";
1011

@@ -16,6 +17,8 @@ router.get("/user/:user", getRoomByUser);
1617

1718
router.get("/rooms", getAllRoomsController);
1819

20+
router.get("/rooms/:user", getAllRoomsByUser);
21+
1922
router.get("/chat-history/:roomId", getRoomChatHistory);
2023

2124
router.get("/rooms/:roomId/questionId", getQuestionId);

frontend/app/app/history/page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import AuthPageWrapper from "@/components/auth/auth-page-wrapper";
2+
import UserRooms from "@/components/collab/user-rooms";
3+
import { Suspense } from "react";
4+
5+
export default function CollabPage() {
6+
return (
7+
<AuthPageWrapper requireLoggedIn>
8+
<Suspense>
9+
<UserRooms />
10+
</Suspense>
11+
</AuthPageWrapper>
12+
);
13+
}

frontend/components/collab/chat.tsx

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,21 @@ import { io, Socket } from "socket.io-client";
1111
import { useAuth } from "@/app/auth/auth-context";
1212
import LoadingScreen from "@/components/common/loading-screen";
1313
import { sendAiMessage } from "@/lib/api/openai/send-ai-message";
14+
import { getChatHistory } from "@/lib/api/collab-service/get-chat-history";
1415

1516
interface Message {
1617
id: string;
1718
userId: string;
1819
text: string;
1920
timestamp: Date;
21+
messageIndex?: number;
22+
}
23+
24+
interface ChatHistoryMessage {
25+
messageIndex: number;
26+
userId: string;
27+
text: string;
28+
timestamp: string;
2029
}
2130

2231
export default function Chat({ roomId }: { roomId: string }) {
@@ -28,10 +37,45 @@ export default function Chat({ roomId }: { roomId: string }) {
2837
const [partnerMessages, setPartnerMessages] = useState<Message[]>([]);
2938
const [aiMessages, setAiMessages] = useState<Message[]>([]);
3039
const [isConnected, setIsConnected] = useState(false);
40+
const [isLoading, setIsLoading] = useState(true);
3141
const lastMessageRef = useRef<HTMLDivElement | null>(null);
3242

3343
useEffect(() => {
34-
if (!auth?.user?.id) return; // Avoid connecting if user is not authenticated
44+
const fetchChatHistory = async () => {
45+
try {
46+
const response = await getChatHistory(roomId);
47+
48+
if (response.ok) {
49+
const history: ChatHistoryMessage[] = await response.json();
50+
const formattedHistory = history.map((msg) => ({
51+
id: msg.messageIndex.toString(),
52+
userId: msg.userId,
53+
text: msg.text,
54+
timestamp: new Date(msg.timestamp),
55+
messageIndex: msg.messageIndex,
56+
}));
57+
58+
formattedHistory.sort(
59+
(a: Message, b: Message) =>
60+
(a.messageIndex ?? 0) - (b.messageIndex ?? 0)
61+
);
62+
63+
setPartnerMessages(formattedHistory);
64+
}
65+
} catch (error) {
66+
console.error("Error fetching chat history:", error);
67+
} finally {
68+
setIsLoading(false);
69+
}
70+
};
71+
72+
if (roomId) {
73+
fetchChatHistory();
74+
}
75+
}, [roomId]);
76+
77+
useEffect(() => {
78+
if (!auth?.user?.id) return;
3579

3680
const socketInstance = io(
3781
process.env.NEXT_PUBLIC_COLLAB_SERVICE_URL || "http://localhost:3002",
@@ -52,7 +96,24 @@ export default function Chat({ roomId }: { roomId: string }) {
5296
});
5397

5498
socketInstance.on("chatMessage", (message: Message) => {
55-
setPartnerMessages((prev) => [...prev, message]);
99+
setPartnerMessages((prev) => {
100+
const exists = prev.some(
101+
(msg) => msg.messageIndex === message.messageIndex
102+
);
103+
if (exists) return prev;
104+
105+
const newMessage = {
106+
...message,
107+
id: message.messageIndex?.toString() || crypto.randomUUID(),
108+
timestamp: new Date(message.timestamp),
109+
};
110+
111+
const newMessages = [...prev, newMessage].sort(
112+
(a, b) => (a.messageIndex ?? 0) - (b.messageIndex ?? 0)
113+
);
114+
115+
return newMessages;
116+
});
56117
});
57118

58119
setSocket(socketInstance);
@@ -77,20 +138,19 @@ export default function Chat({ roomId }: { roomId: string }) {
77138
const sendMessage = async () => {
78139
if (!newMessage.trim() || !socket || !isConnected || !own_user_id) return;
79140

80-
const message = {
81-
id: crypto.randomUUID(),
82-
userId: own_user_id,
83-
text: newMessage,
84-
timestamp: new Date(),
85-
};
86-
87141
if (chatTarget === "partner") {
88142
socket.emit("sendMessage", {
89143
roomId,
90144
userId: own_user_id,
91145
text: newMessage,
92146
});
93147
} else {
148+
const message: Message = {
149+
id: crypto.randomUUID(),
150+
userId: own_user_id,
151+
text: newMessage,
152+
timestamp: new Date(),
153+
};
94154
setAiMessages((prev) => [...prev, message]);
95155
const response = await sendAiMessage(newMessage);
96156
const data = await response.json();
@@ -110,7 +170,10 @@ export default function Chat({ roomId }: { roomId: string }) {
110170
};
111171

112172
const formatTimestamp = (date: Date) => {
113-
return new Date(date).toLocaleTimeString([], {
173+
if (!(date instanceof Date) || isNaN(date.getTime())) {
174+
return "Invalid Date";
175+
}
176+
return date.toLocaleTimeString([], {
114177
hour: "2-digit",
115178
minute: "2-digit",
116179
});
@@ -134,7 +197,7 @@ export default function Chat({ roomId }: { roomId: string }) {
134197
</div>
135198
);
136199

137-
if (!own_user_id) {
200+
if (!own_user_id || isLoading) {
138201
return <LoadingScreen />;
139202
}
140203

frontend/components/collab/collab-room.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ import { X } from "lucide-react";
44
import Chat from "./chat";
55
import QuestionDisplay from "./question-display";
66
import CodeEditor from "./code-editor";
7+
import Link from "next/link";
78

89
export default function CollabRoom({ roomId }: { roomId: string }) {
910
return (
1011
<div className="h-full flex flex-col mx-4 p-4">
1112
<header className="flex justify-between border-b">
1213
<h1 className="text-2xl font-bold mb-4">Collab Room {roomId}</h1>
13-
<Button variant="destructive">
14-
Leave Room
15-
<X className="ml-2" />
16-
</Button>
14+
<Link href="/app/history">
15+
<Button variant="destructive">
16+
Leave Room
17+
<X className="ml-2" />
18+
</Button>
19+
</Link>
1720
</header>
1821
<div className="flex flex-1">
1922
<div className="w-2/5 p-4 flex flex-col space-y-4">

frontend/components/collab/question-display.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22
import React, { useEffect, useState } from "react";
3+
import clsx from "clsx";
34
import {
45
Card,
56
CardContent,
@@ -26,7 +27,15 @@ interface Question {
2627
description: string;
2728
}
2829

29-
export default function QuestionDisplay({ roomId }: { roomId: string }) {
30+
export default function QuestionDisplay({
31+
roomId,
32+
className,
33+
date,
34+
}: {
35+
roomId: string;
36+
className?: string;
37+
date?: Date;
38+
}) {
3039
const auth = useAuth();
3140
const token = auth?.token;
3241
const [question, setQuestion] = useState<Question | null>(null);
@@ -66,16 +75,21 @@ export default function QuestionDisplay({ roomId }: { roomId: string }) {
6675
if (!question) {
6776
return <div>Question not found</div>;
6877
}
78+
date && console.log(date.toLocaleString());
79+
date && console.log(date.toLocaleString("en-GB"));
6980

7081
return (
71-
<Card className="flex-shrink-0">
82+
<Card className={clsx("flex-shrink-0", className)}>
7283
<CardHeader>
7384
<CardTitle>{question.title}</CardTitle>
74-
<CardDescription className="flex items-center space-x-2">
75-
<span>{question.categories}</span>
76-
<Badge className={`${difficultyColors[question.complexity]}`}>
77-
{question.complexity}
78-
</Badge>
85+
<CardDescription className="flex items-center justify-between">
86+
<div className="flex space-x-2">
87+
<span>{question.categories}</span>
88+
<Badge className={`${difficultyColors[question.complexity]}`}>
89+
{question.complexity}
90+
</Badge>
91+
</div>
92+
{date && <span>{new Date(date).toLocaleString()}</span>}
7993
</CardDescription>
8094
</CardHeader>
8195
<CardContent>

0 commit comments

Comments
 (0)