Skip to content

Commit 9922c4d

Browse files
committed
Merge branch 'main' of https://github.com/CS3219-AY2425S1/cs3219-ay2425s1-project-g15 into jehou/disconnect
# Conflicts: # frontend/src/app/collaboration/components/editor.tsx # frontend/src/app/collaboration/components/toolbar.tsx
2 parents afc0cd8 + 1bc3df5 commit 9922c4d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+84525
-287
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ kubernetes/matching/matching-configmap.yaml
77
kubernetes/question/question-configmap.yaml
88
kubernetes/user/user-configmap.yaml
99
kubernetes/collaboration/collaboration-configmap.yaml
10+
**/venv/

backend/chatlog/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM node
2+
3+
WORKDIR /usr/src/app
4+
5+
COPY package*.json ./
6+
7+
RUN npm install
8+
9+
COPY . .
10+
11+
EXPOSE 3008
12+
CMD ["npm", "start"]

backend/chatlog/package-lock.json

Lines changed: 2581 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/chatlog/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "express-kafka-mongo",
3+
"version": "1.0.0",
4+
"main": "dist/app.js",
5+
"scripts": {
6+
"build": "rimraf dist && npx tsc",
7+
"prestart": "npm run build",
8+
"start": "npx tsc -w & nodemon dist/app.js"
9+
},
10+
"dependencies": {
11+
"@types/cors": "^2.8.17",
12+
"@types/express": "^4.17.21",
13+
"@types/express-validator": "^3.0.2",
14+
"@types/mongoose": "^5.11.97",
15+
"@types/node": "^22.8.5",
16+
"cors": "^2.8.5",
17+
"dotenv": "^16.4.5",
18+
"express": "^4.21.0",
19+
"express-validator": "^7.2.0",
20+
"kafkajs": "^2.2.4",
21+
"mongoose": "^8.7.2",
22+
"nodemon": "^3.1.4",
23+
"rimraf": "^6.0.1",
24+
"typescript": "^5.6.2"
25+
}
26+
}

backend/chatlog/src/app.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import express, { Request, Response } from 'express';
2+
import mongoose from 'mongoose';
3+
import startConsumer from './kafkaConsumer';
4+
5+
const app = express();
6+
app.use(express.json());
7+
const PORT = process.env.PORT || "3008";
8+
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/chatlogs';
9+
10+
// Connect to MongoDB
11+
mongoose.connect(MONGODB_URI)
12+
.then(() => {
13+
console.log('Connected to MongoDB');
14+
})
15+
.catch((err) => {
16+
console.error('MongoDB connection error:', err);
17+
});
18+
19+
// Start Kafka Consumer
20+
startConsumer().catch(console.error);
21+
22+
// Basic route to verify server is running
23+
app.get('/', (req: Request, res: Response) => {
24+
res.send('ExpressJS Kafka-MongoDB Server is Running');
25+
});
26+
27+
app.listen(PORT, () => {
28+
console.log(`Server is listening on port ${PORT}`);
29+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// // src/kafkaConsumer.ts
2+
import { EachMessagePayload, Kafka } from 'kafkajs';
3+
import ChatLog, { IChatLog } from './models/ChatLog';
4+
5+
// // Kafka Configuration
6+
const kafka = new Kafka({
7+
clientId: 'my-id',
8+
brokers: ['kafka:9092']
9+
});
10+
11+
const consumer = kafka.consumer({ groupId: 'chat-logs-group' });
12+
13+
async function startConsumer(): Promise<void> {
14+
console.log("Starting Chatlog Kafka Consumer");
15+
16+
while (true) {
17+
try {
18+
await consumer.connect();
19+
console.log("Kafka consumer connected successfully");
20+
break; // Exit loop on successful connection
21+
} catch (error) {
22+
console.error("Failed to connect to Kafka, retrying in 5 seconds...", error);
23+
await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5 seconds before retrying
24+
}
25+
}
26+
27+
await consumer.subscribe({ topic: 'CHATLOGS'});
28+
29+
await consumer.run({
30+
eachMessage: async ({ message }: EachMessagePayload) => {
31+
const chatLog = JSON.parse(message.value?.toString() || '{}');
32+
console.log("Message consumed: " + chatLog)
33+
34+
// Save message to MongoDB
35+
const newChatLog: IChatLog = new ChatLog({
36+
senderId: chatLog.senderId,
37+
collabId: chatLog.collabId,
38+
recipientId: chatLog.recipientId,
39+
message: chatLog.message,
40+
timestamp: new Date(chatLog.timestamp * 1000)
41+
});
42+
43+
await newChatLog.save();
44+
console.log('Chat log saved to MongoDB:', newChatLog);
45+
},
46+
});
47+
}
48+
49+
export default startConsumer;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import mongoose, { Document, Schema } from 'mongoose';
2+
3+
interface IChatLog extends Document {
4+
senderId: string;
5+
message: string;
6+
collabId: string;
7+
recipientId: string;
8+
timestamp: Date;
9+
}
10+
11+
const chatLogSchema = new Schema<IChatLog>({
12+
senderId: { type: String, required: true },
13+
message: { type: String, required: true },
14+
timestamp: { type: Date, required: true },
15+
collabId: { type: String, required: true },
16+
recipientId: { type: String, required: true }
17+
});
18+
19+
const ChatLogDB = mongoose.connection.useDb("Chatlogs");
20+
export default ChatLogDB.model<IChatLog>('ChatLog', chatLogSchema, "chatlogs");
21+
export { IChatLog };

backend/chatlog/tsconfig.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES6",
4+
"module": "commonjs",
5+
"outDir": "./dist",
6+
"rootDir": "./src",
7+
"strict": true,
8+
"esModuleInterop": true,
9+
"skipLibCheck": true
10+
},
11+
"include": ["src"]
12+
}
13+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import mongoose, { Schema } from "mongoose";
2+
3+
export type TChatLog = {
4+
collabid: string;
5+
message: string;
6+
senderId: string;
7+
recipientId: string;
8+
timestampEpoch: number;
9+
}
10+
11+
export interface IChatLog extends TChatLog, Document {}
12+
13+
const chatLogSchema: Schema = new Schema(
14+
{
15+
collabId: {
16+
type: String,
17+
required: true,
18+
},
19+
message: {
20+
type: String,
21+
required: true,
22+
},
23+
senderId: {
24+
type: String,
25+
required: true,
26+
},
27+
recipientId: {
28+
type: String,
29+
required: true,
30+
},
31+
timestampEpoch: {
32+
type: Number,
33+
required: true,
34+
},
35+
},
36+
{ collection: "chatlogs" }
37+
)
38+
39+
const ChatLogDB = mongoose.connection.useDb("Chatlogs");
40+
const ChatLog = ChatLogDB.model<IChatLog>("ChatLog", chatLogSchema, "chatlogs");
41+
42+
export default ChatLog

backend/collaboration/src/routes/collaborationRoutes.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import express, { Request, Response } from "express";
2+
import { validationResult } from "express-validator";
3+
import ChatLog from "../models/ChatLog";
24
import Session, { TSession } from "../models/Session";
35
import {
6+
createChatLogValidators,
47
createSessionValidators,
8+
getChatLogValidators,
59
idValidators,
610
updateSessionValidators,
711
} from "./validators";
8-
import { validationResult } from "express-validator";
912

1013
/**
1114
* Router for the collaboration service.
@@ -205,4 +208,69 @@ router.delete(
205208
}
206209
);
207210

211+
// Create a single chat log
212+
router.post("/chat/:collabid/create_chatlog", [...createChatLogValidators], async (req: Request, res: Response) => {
213+
// TODO: Add validation check to check if a collab ID exists and if the sender and recipient IDs are valid
214+
215+
const errors = validationResult(req);
216+
if (!errors.isEmpty()) {
217+
return res.status(400).json({ errors: errors.array() });
218+
}
219+
try {
220+
const { message, senderId, recipientId, timestampEpoch } = req.body;
221+
const { collabid } = req.params;
222+
const chatLog = {
223+
collabid,
224+
message,
225+
senderId,
226+
recipientId,
227+
timestampEpoch
228+
};
229+
230+
const newChatLog = new ChatLog(chatLog);
231+
await newChatLog.save();
232+
res.status(200).json({ message: "Chat log created successfully", chatLog: newChatLog });
233+
} catch (error) {
234+
console.log(error)
235+
return res.status(500).send("Internal server error");
236+
}
237+
})
238+
239+
// Fetch chat logs for a specific collabID with pagination
240+
router.get("/chat/:collabid/get_chatlogs", [...getChatLogValidators], async (req: Request, res: Response) => {
241+
try {
242+
const { collabid } = req.params;
243+
const page = parseInt(req.query.page as string, 10) || 1;
244+
const limit = parseInt(req.query.limit as string, 10) || 10;
245+
246+
const skip = (page - 1) * limit;
247+
248+
// Fetch chat logs for the specified collabID, with pagination and sorted by timestamp
249+
const chatLogs = await ChatLog.find({ collabId: collabid })
250+
.sort({ timestamp: -1 }) // Sort by timestamp in descending order (latest first)
251+
.skip(skip)
252+
.limit(limit);
253+
254+
chatLogs.reverse();
255+
256+
// Get total count of chat logs for the given collabID
257+
const totalLogs = await ChatLog.countDocuments({ collabid });
258+
const totalPages = Math.ceil(totalLogs / limit);
259+
260+
res.status(200).json({
261+
message: "Chat logs fetched successfully",
262+
chatLogs: chatLogs,
263+
pagination: {
264+
page,
265+
limit,
266+
totalPages,
267+
totalLogs,
268+
},
269+
});
270+
} catch (error) {
271+
console.log(error);
272+
res.status(500).send("Internal server error");
273+
}
274+
});
275+
208276
export default router;

0 commit comments

Comments
 (0)