Skip to content

Commit

Permalink
Merge pull request #100 from CS3219-AY2324S1/feat/add-notification
Browse files Browse the repository at this point in the history
Feat/add notification
  • Loading branch information
Guo-KeCheng authored Nov 13, 2023
2 parents 8d37f14 + efdc6f1 commit a038ab0
Show file tree
Hide file tree
Showing 45 changed files with 811 additions and 260 deletions.
5 changes: 5 additions & 0 deletions apps/backend/chat/src/controllers/messageController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ class MessageController {

handleConnection(socket: Socket) {
socket.on("connectToChatroom", async ({ chatroomId }) => {
this.io.to(chatroomId).emit("other user connecting");
socket.join(chatroomId);
const messages = await this.chatroomService.getMessages(chatroomId);
console.log(messages);
socket.emit("receiveMessage", { messages });
socket.on("sendMessage", (message) =>
this.handleSendMessage(message, chatroomId)
);
socket.on("disconnect", () => {
console.log("starting to disconnect");
this.io.to(chatroomId).emit("other user disconnecting");
});
});
socket.emit("connected");
}
Expand Down
19 changes: 16 additions & 3 deletions apps/backend/question/src/controllers/api/questionRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,20 @@ questionRouter.put(
}
);

questionRouter.post(
"/:id/run",

questionRouter.get(
"/random-question/:difficulty/:language",
async (req, res, next) => {
const difficulty = req.params.difficulty;
console.log(difficulty);
const languageId = req.params.language

const questions = await Question.aggregate([{
$match: {
complexity: difficulty,
'starterCode.languageId': Number(languageId),
}
},
{ $sample: { size: 1 } }]);
res.status(200).json({questions});
}
)
19 changes: 17 additions & 2 deletions apps/backend/queue/src/queue/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,32 @@ import axios from "axios";
import { SESSION_URL } from "../utils/config";
import Producer from "../message-queue/Producer";
import Consumer from "../message-queue/Consumer";
import { ProgrammingLanguages } from "../enum/ProgrammingLanguages";
import { Difficulty } from "../enum/Difficulty";

export class Queue {
waitList: number[];
socketMap: Map<number, Socket>;
nameSpace: string;
producer: Producer;
consumer: Consumer;
diff: Difficulty;
language: ProgrammingLanguages;
next?: Queue;

constructor(nameSpace: string, nextQueue?: Queue) {
constructor(
nameSpace: string,
diff: Difficulty,
language: ProgrammingLanguages,
nextQueue?: Queue
) {
this.nameSpace = nameSpace;
this.waitList = [];
this.socketMap = new Map();
this.producer = new Producer(nameSpace);
this.consumer = new Consumer(nameSpace, (uid) => this.matchUsers(uid));
this.diff = diff;
this.language = language;
this.next = nextQueue;
}

Expand Down Expand Up @@ -118,7 +129,11 @@ export class Queue {

private async generateSession(user1: number, user2: number) {
const sessionID = await axios
.post(SESSION_URL, { users: [user1, user2] })
.post(SESSION_URL, {
users: [user1, user2],
difficulty: this.diff,
language: this.language,
})
.then((response) => {
return response.data.sessionId;
})
Expand Down
8 changes: 4 additions & 4 deletions apps/backend/queue/src/services/queueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ class QueueService {

constructor() {
this.queues = new Map();
this.queues.set(Difficulty.EASY, new Queue(Difficulty.EASY));
this.queues.set(Difficulty.MEDIUM, new Queue(Difficulty.MEDIUM));
this.queues.set(Difficulty.HARD, new Queue(Difficulty.HARD));
// this.queues.set(Difficulty.EASY, new Queue(Difficulty.EASY));
// this.queues.set(Difficulty.MEDIUM, new Queue(Difficulty.MEDIUM));
// this.queues.set(Difficulty.HARD, new Queue(Difficulty.HARD));

for (const diff of Object.values(Difficulty)) {
for (const language of Object.values(ProgrammingLanguages)) {
const nameSpace = diff + "/" + language;
this.queues.set(nameSpace, new Queue(nameSpace, this.queues.get(diff)));
this.queues.set(nameSpace, new Queue(nameSpace, diff, language));
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/session/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ SESSION_PORT= # Port that session microservice is hosted on
WS_PORT= # Port that websocket microservice is hosted on
HOST= # Hostname of the server
MONGODB_URI= # MongoDB URI
QUESTION_URL= # Question microservice URL
USER_SERVICE_URL= # User microservice URL
28 changes: 27 additions & 1 deletion apps/backend/session/src/controllers/sessionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ export class SessionController {
) {
logger.info(req.body);
const [user1, user2] = req.body.users;
const sessionId = await this.sessionManager.createNewSession(user1, user2);
const difficulty = req.body.difficulty;
const language = req.body.language;
const sessionId = await this.sessionManager.createNewSession(
user1,
user2,
difficulty,
language
);
console.log(`Created a sessionId: ${sessionId}`);
res.status(200).json({ sessionId: sessionId.toString() });
}
Expand Down Expand Up @@ -65,4 +72,23 @@ export class SessionController {
console.log("Saving to database");
await this.sessionManager.saveToDatabase();
}

public async handleGetSessionsForUser(
req: Request,
res: Response,
next: NextFunction
) {
console.log(req.params.user);
const uid = Number(req.params.user);
if (!uid) {
return res.status(500).json({ err: "UID passed in was not valid." });
}
const sessions = await this.sessionManager.getAllSessionsForUser(uid);
if (!sessions) {
return res.status(500).json({
err: "Something went wrong when searching for the sessions for this user.",
});
}
return res.status(200).json({ sessions });
}
}
5 changes: 5 additions & 0 deletions apps/backend/session/src/enums/Difficulty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum Difficulty {
EASY = "Easy",
MEDIUM = "Medium",
HARD = "Hard",
}
4 changes: 4 additions & 0 deletions apps/backend/session/src/enums/OAuthType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum OAuthType {
Google = "google",
Github = "github",
}
7 changes: 7 additions & 0 deletions apps/backend/session/src/enums/ProgrammingLanguage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum ProgrammingLanguage {
PYTHON = "Python (3.8.1)",
JAVASCRIPT = "JavaScript (Node.js 12.14.0)",
TYPESCRIPT = "TypeScript (3.7.4)",
CPP = "C++ (GCC 9.2.0)",
JAVA = "Java (OpenJDK 13.0.1)",
}
4 changes: 4 additions & 0 deletions apps/backend/session/src/enums/Role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum Role {
Admin = "admin",
Normal = "normal",
}
66 changes: 66 additions & 0 deletions apps/backend/session/src/guards/jwtGuard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import axios, { AxiosError } from "axios";
import { Request, Response, NextFunction } from "express";
import { OAuthType } from "../enums/OAuthType.ts";
import { Role } from "../enums/Role.ts";

interface User {
id: number;
email: string;
username: string;
password: string;
oauth: OAuthType[];
role: Role;
iat: string;
exp: string;
}

export interface AuthenticatedRequest extends Request {
user?: User;
}

function getAxiosErrorMessage(error: unknown) {
// console.log(error);
if (error instanceof AxiosError) {
if (error.code === "ECONNREFUSED") {
return "Unable to connect with user service.";
}
return error.response?.data?.error;
}
return String(error);
}

const verifyAccessToken = async (accessToken?: string) => {
const response = await axios.get(
process.env.USER_SERVICE_URL + "/api/users/verifyJwt",
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
console.log("Verified User: ", response.data);
return response.data;
};

export const jwtGuard = async (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) => {
console.log("Checking JWT");
const accessToken = req.headers.authorization?.split(" ")[1];
if (!accessToken) {
res.status(401).json({ error: "No access token was provided." });
return;
}

try {
req.user = await verifyAccessToken(accessToken);
console.log(req.user);
next();
} catch (error) {
const errorMessage = getAxiosErrorMessage(error);
console.log(errorMessage);
res.status(401).json({ error: errorMessage });
}
};
70 changes: 70 additions & 0 deletions apps/backend/session/src/guards/sessionGuard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import axios, { AxiosError } from "axios";
import { Request, Response, NextFunction } from "express";
import { OAuthType } from "../enums/OAuthType.ts";
import { Role } from "../enums/Role.ts";
import SessionModel, { SessionEntity } from "../model/Session.ts";
import { AuthenticatedRequest } from "./jwtGuard.ts";

export interface AuthenticatedRequestWithSession extends AuthenticatedRequest {
session?: SessionEntity;
}

function getAxiosErrorMessage(error: unknown) {
if (error instanceof AxiosError) {
if (error.code === "ECONNREFUSED") {
return "Unable to connect with user service.";
}
return error.response?.data?.error;
}
return String(error);
}

const verifyUserInSession = async (sessionId: string, userId: number) => {
const session = await SessionModel.findById(sessionId);
if (!session) {
console.log("No session of this sessionId has been found");
return { isUserInSession: undefined, session: undefined };
}
const isUserInSession: boolean = session.users.includes(userId);
return { isUserInSession, session };
};

export const sessionGuard = async (
req: AuthenticatedRequestWithSession,
res: Response,
next: NextFunction
) => {
const sessionId = req.params.sessionId;
console.log("HELLO");
if (!sessionId) {
res.status(401).json({ error: "No sesionId was provided." });
return;
}

try {
if (!req.user || !req.user.id) {
return res.status(401).json({ error: "User not found" });
}
const { isUserInSession, session } = await verifyUserInSession(
sessionId,
req.user.id
);
if (!session) {
console.log("No session of that sessionId was found.");
return res
.status(404)
.json({ error: "No session of that sessionId was found." });
}
if (!isUserInSession) {
console.log("User is not a part of this session.");
return res
.status(401)
.json({ error: "User is not a part of this session." });
}
req.session = session;
next();
} catch (error) {
const errorMessage = getAxiosErrorMessage(error);
res.status(401).json({ error: errorMessage });
}
};
21 changes: 21 additions & 0 deletions apps/backend/session/src/guards/userGuard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextFunction, Response } from "express";
import { AuthenticatedRequest } from "./jwtGuard";

export const userGuard = async (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) => {
const user = req.user;
const uid = Number(req.params.user);

if (user?.id != uid) {
return res
.status(401)
.json({
err: "User is attempting to access the session history of another user",
});
} else {
next();
}
};
16 changes: 13 additions & 3 deletions apps/backend/session/src/model/Session.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import mongoose, { Document, Schema } from "mongoose";

interface Session extends Document {
export interface SessionEntity extends Document {
users: number[];
chatroomId: string;
code: string;
question: string;
language: string;
}

const sessionSchema = new Schema({
Expand All @@ -21,12 +23,20 @@ const sessionSchema = new Schema({
code: {
type: String,
},
question: {
type: String,
required: true,
},
language: {
type: String,
required: true,
},
});

const SessionModel = mongoose.model<Session>(
const SessionModel = mongoose.model<SessionEntity>(
"Session",
sessionSchema,
"sessions"
);

export default SessionModel;
export default SessionModel;
Loading

0 comments on commit a038ab0

Please sign in to comment.