From 1a61971ab816b42d74555ce0eb4837a23d2670db Mon Sep 17 00:00:00 2001 From: limcaaarl <42115432+limcaaarl@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:11:47 +0800 Subject: [PATCH] Fix bug for collab (#92) * Fix bug - Previously, on successful forfeit call, we still set a new entry in the yjs doc. This causes the yjs doc to be reintialise when both users has already forfeited * Refactoring in Collaboration Service: - Refactor comments in collab service - Move interface from controller folder to types folder - Refactor README.md - Remove unused variable in queue: MATCH_FOUND * Update message for when user agrees to submit --------- Co-authored-by: KhoonSun47 --- .../forfeit-dialog.component.ts | 4 +- .../submit-dialog.component.html | 19 +++++--- .../submit-dialog/submit-dialog.component.ts | 8 ++-- services/collaboration/README.md | 29 ++++++------ .../src/controllers/roomController.ts | 16 +------ .../collaboration/src/controllers/types.ts | 21 --------- services/collaboration/src/events/consumer.ts | 7 +++ services/collaboration/src/events/producer.ts | 25 ++++++++++ services/collaboration/src/events/queues.ts | 1 - services/collaboration/src/routes/index.ts | 1 + .../src/services/mongodbService.ts | 10 ++-- .../src/services/webSocketService.ts | 3 +- services/collaboration/src/types/collab.ts | 46 +++++++++++++++++++ services/collaboration/src/utils/helper.ts | 10 ++++ 14 files changed, 133 insertions(+), 67 deletions(-) delete mode 100644 services/collaboration/src/controllers/types.ts create mode 100644 services/collaboration/src/types/collab.ts diff --git a/frontend/src/app/collaboration/forfeit-dialog/forfeit-dialog.component.ts b/frontend/src/app/collaboration/forfeit-dialog/forfeit-dialog.component.ts index ab7d8bf645..af5faf1382 100644 --- a/frontend/src/app/collaboration/forfeit-dialog/forfeit-dialog.component.ts +++ b/frontend/src/app/collaboration/forfeit-dialog/forfeit-dialog.component.ts @@ -64,7 +64,9 @@ export class ForfeitDialogComponent implements OnInit { if (userId) { this.collabService.forfeit(this.roomId).subscribe({ next: () => { - this.yforfeit.set(userId, true); + if (this.yforfeit.size == 0) { + this.yforfeit.set(userId, true); + } this.message = 'You have forfeited. \n\n Redirecting you to homepage...'; this.isForfeit = true; this.hideButtons = true; diff --git a/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.html b/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.html index e42cbb1216..cd2d9ce893 100644 --- a/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.html +++ b/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.html @@ -16,11 +16,18 @@

Submit

-
- - @if (!isInitiator || (numForfeit !== 2 && numUniqueUsers === 1)) { - - } -
+ @if (!isConsent) { +
+ + @if (!isInitiator || (numForfeit !== 2 && numUniqueUsers === 1)) { + + } +
+ }
diff --git a/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.ts b/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.ts index 984f9c5d32..9c208a38da 100644 --- a/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.ts +++ b/frontend/src/app/collaboration/submit-dialog/submit-dialog.component.ts @@ -31,6 +31,7 @@ export class SubmitDialogComponent implements AfterViewInit { ysubmit!: Y.Map; yforfeit!: Y.Map; userId!: string; + isConsent = false; constructor( @Inject(DOCUMENT) private document: Document, @@ -112,18 +113,19 @@ export class SubmitDialogComponent implements AfterViewInit { } checkVoteOutcome(counter: number) { - const isConsent = counter == this.numUniqueUsers; + this.isConsent = counter == this.numUniqueUsers; - if (!isConsent) { + if (!this.isConsent) { return; } + this.message = 'Successfully submitted. \n\n Redirecting you to homepage...'; + this.successfulSubmit.emit(); if (this.isInitiator) { this.collabService.closeRoom(this.roomId).subscribe({ next: () => { - this.message = 'Successfully submitted. \n\n Redirecting you to homepage...'; setTimeout(() => { this.router.navigate(['/home']); }, 1500); diff --git a/services/collaboration/README.md b/services/collaboration/README.md index 4c5dd7fc1e..d0d8ca2058 100644 --- a/services/collaboration/README.md +++ b/services/collaboration/README.md @@ -346,7 +346,8 @@ curl -X PATCH http://localhost:8080/api/collaboration/room/6724e9d892fb3e9f04c2e ## Documentation on Queue (RabbitMQ) The collaboration service uses RabbitMQ as a message broker to facilitate communication between microservices (such as -the `matching service` and `collaboration service`) in an asynchronous manner. The system consists of a consumer and four +the `Matching Service`, `Collaboration Service` and `Question Service`) in an asynchronous manner. The system consists +of a consumer and four producers: ### Queues Used @@ -365,9 +366,9 @@ The producer will send a message to the `COLLAB_CREATED` queue when a collaborat - **Queue**: `COLLAB_CREATED` - **Data in the Message**: - - `requestId1` (Required) - The request ID of the first user. - - `requestId2` (Required) - The request ID of the second user. - - `collabId` (Required) - The ID of the collaboration room. + - `requestId1` (Required) - The request ID of the first user. + - `requestId2` (Required) - The request ID of the second user. + - `collabId` (Required) - The ID of the collaboration room. ```json { @@ -381,9 +382,9 @@ The producer will send a message to the `MATCH_FAILED` queue when a collaboratio - **Queue**: `MATCH_FAILED` - **Data Produced** - - `requestId1` (Required) - The first request ID associated with the match failure. - - `requestId2` (Required) - The second request ID associated with the match failure. - - `reason` (Required) - The error encountered. + - `requestId1` (Required) - The first request ID associated with the match failure. + - `requestId2` (Required) - The second request ID associated with the match failure. + - `reason` (Required) - The error encountered. ```json { @@ -420,17 +421,19 @@ The producer will send a message to the `CREATE_HISTORY` queue when a collaborat "topics": [ "Algorithms" ], "difficulty": "Easy", "_id": "671a0615dc63fe2d5f3bbae5" - }, + } }, ``` -The producer will send a message to the `UPDATE_HISTORY` queue when a user forfeits or completes a collaborative session. +The producer will send a message to the `UPDATE_HISTORY` queue when a user forfeits or completes a collaborative +session. - **Queue**: `UPDATE_HISTORY` - **Data Produced** - `roomId` - The ID of the collaboration room. - `userId` - The user associated with the update. - - `status` - The new status associated with the collaboration room. It may be `"IN_PROGRESS"`, `"FORFEITED"`, or `"COMPLETED"`. + - `status` - The new status associated with the collaboration room. It may be `"IN_PROGRESS"`, `"FORFEITED"`, + or `"COMPLETED"`. ```json { @@ -449,9 +452,9 @@ matched. - **Queue**: `QUESTION_FOUND` - **Data in the Message**: - - `user1` (Required) - The details of the first user. - - `user2` (Required) - The details of the second user. - - `question` (Required) - The question assigned to the users. + - `user1` (Required) - The details of the first user. + - `user2` (Required) - The details of the second user. + - `question` (Required) - The question assigned to the users. ```json { diff --git a/services/collaboration/src/controllers/roomController.ts b/services/collaboration/src/controllers/roomController.ts index cb877a146a..1c339cb5a1 100644 --- a/services/collaboration/src/controllers/roomController.ts +++ b/services/collaboration/src/controllers/roomController.ts @@ -9,24 +9,10 @@ import { updateRoomUserStatus, } from '../services/mongodbService'; import { handleHttpNotFound, handleHttpSuccess, handleHttpServerError, handleHttpBadRequest } from '../utils/helper'; -import { Room } from './types'; +import { Room, Question } from '../types/collab'; import { produceUpdateHistory } from '../events/producer'; import { HistoryStatus } from '../types/message'; -export enum Difficulty { - Easy = 'Easy', - Medium = 'Medium', - Hard = 'Hard', -} - -export interface Question { - id: number; - description: string; - difficulty: Difficulty; - title: string; - topics?: string[]; -} - /** * Create a room with users, question details, and Yjs document * @param user1 diff --git a/services/collaboration/src/controllers/types.ts b/services/collaboration/src/controllers/types.ts deleted file mode 100644 index 89dcab9caa..0000000000 --- a/services/collaboration/src/controllers/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ObjectId } from 'mongodb'; -import { Question } from './roomController'; - -/** - * @fileoverview Types for the collaboration service. - */ - -export interface User { - id: string; - username: string; - requestId: string; - isForfeit?: boolean; -} - -export interface Room { - _id: ObjectId; - users: User[]; - question: Question; - createdAt: Date; - room_status: boolean; -} diff --git a/services/collaboration/src/events/consumer.ts b/services/collaboration/src/events/consumer.ts index 97aca6c62e..aa2c4c5d7b 100644 --- a/services/collaboration/src/events/consumer.ts +++ b/services/collaboration/src/events/consumer.ts @@ -4,6 +4,10 @@ import { createRoomWithQuestion } from '../controllers/roomController'; import { QuestionFoundEvent } from '../types/event'; import { produceCollabCreated, produceCollabCreateFailedEvent, produceCreateHistory } from './producer'; +/** + * Consume the question found event and create a room + * @param message + */ async function consumeQuestionFound(message: QuestionFoundEvent) { console.log('Attempting to create room:', message); const { user1, user2, question } = message; @@ -27,6 +31,9 @@ async function consumeQuestionFound(message: QuestionFoundEvent) { } } +/** + * Initialize the consumers for the collaboration service + */ export async function initializeConsumers() { messageBroker.consume(Queues.QUESTION_FOUND, consumeQuestionFound); } diff --git a/services/collaboration/src/events/producer.ts b/services/collaboration/src/events/producer.ts index 70e924b188..938a2bffb1 100644 --- a/services/collaboration/src/events/producer.ts +++ b/services/collaboration/src/events/producer.ts @@ -5,6 +5,13 @@ import { Queues } from './queues'; const COLLAB_CREATED_ERROR = 'Failed to create room'; +/** + * Produce a collab created event + * @param requestId1 + * @param requestId2 + * @param collabId + * @param question + */ export async function produceCollabCreated( requestId1: IdType, requestId2: IdType, @@ -15,16 +22,34 @@ export async function produceCollabCreated( await messageBroker.produce(Queues.COLLAB_CREATED, message); } +/** + * Produce a collab create failed event + * @param requestId1 + * @param requestId2 + */ export async function produceCollabCreateFailedEvent(requestId1: IdType, requestId2: IdType) { const message: MatchFailedEvent = { requestId1, requestId2, reason: COLLAB_CREATED_ERROR }; await messageBroker.produce(Queues.MATCH_FAILED, message); } +/** + * Produce a create history event + * @param roomId + * @param user1 + * @param user2 + * @param question + */ export async function produceCreateHistory(roomId: IdType, user1: User, user2: User, question: Question) { const message: CreateHistoryMessage = { roomId, user1, user2, question }; await messageBroker.produce(Queues.CREATE_HISTORY, message); } +/** + * Produce an update history event + * @param roomId + * @param userId + * @param status + */ export async function produceUpdateHistory(roomId: IdType, userId: IdType, status: HistoryStatus) { const message: UpdateHistoryMessage = { roomId, userId, status }; await messageBroker.produce(Queues.UPDATE_HISTORY, message); diff --git a/services/collaboration/src/events/queues.ts b/services/collaboration/src/events/queues.ts index 250a63e051..c158b11e09 100644 --- a/services/collaboration/src/events/queues.ts +++ b/services/collaboration/src/events/queues.ts @@ -2,7 +2,6 @@ * Enum for queues */ export enum Queues { - MATCH_FOUND = 'MATCH_FOUND', QUESTION_FOUND = 'QUESTION_FOUND', COLLAB_CREATED = 'COLLAB_CREATED', MATCH_FAILED = 'MATCH_FAILED', diff --git a/services/collaboration/src/routes/index.ts b/services/collaboration/src/routes/index.ts index 7e7faddcfd..943f3297c5 100644 --- a/services/collaboration/src/routes/index.ts +++ b/services/collaboration/src/routes/index.ts @@ -1,5 +1,6 @@ import express from 'express'; import { getHealth } from '../controllers'; + const router = express.Router(); router.get('/ht', getHealth); diff --git a/services/collaboration/src/services/mongodbService.ts b/services/collaboration/src/services/mongodbService.ts index d5bdb967cc..f3f4539648 100644 --- a/services/collaboration/src/services/mongodbService.ts +++ b/services/collaboration/src/services/mongodbService.ts @@ -2,11 +2,11 @@ import { MongoClient, Db, ObjectId, WithId } from 'mongodb'; import { MongodbPersistence } from 'y-mongodb-provider'; import * as Y from 'yjs'; import config from '../config'; -import { Question } from '../controllers/roomController'; -import { Room } from '../controllers/types'; +import { Question, Room } from '../types/collab'; let roomDb: Db | null = null; let yjsDb: Db | null = null; + /** Yjs MongoDB persistence provider for Yjs documents */ export let mdb!: MongodbPersistence; @@ -29,7 +29,7 @@ const connectToRoomDB = async (): Promise => { }; /** - * Connect to the YJS database + * Connect to the Yjs Document database */ const connectToYJSDB = async (): Promise => { try { @@ -66,7 +66,7 @@ export const startMongoDB = async (): Promise => { }; /** - * Save room data in the MongoDB rooms database and create a Yjs document + * Create a room in the database * @returns roomId * @param user1 * @param user2 @@ -108,7 +108,7 @@ export const findRoomById = async (roomId: string, userId: string): Promise { const wss = new WebSocketServer({ server }); diff --git a/services/collaboration/src/types/collab.ts b/services/collaboration/src/types/collab.ts new file mode 100644 index 0000000000..4a44c3bf86 --- /dev/null +++ b/services/collaboration/src/types/collab.ts @@ -0,0 +1,46 @@ +import { ObjectId } from 'mongodb'; + +/** + * @fileoverview Types for the collaboration service. + */ + +/** + * User interface. + */ +export interface User { + id: string; + username: string; + requestId: string; + isForfeit?: boolean; +} + +/** + * Room interface. + */ +export interface Room { + _id: ObjectId; + users: User[]; + question: Question; + createdAt: Date; + room_status: boolean; +} + +/** + * Question difficulty enum. + */ +export enum Difficulty { + Easy = 'Easy', + Medium = 'Medium', + Hard = 'Hard', +} + +/** + * Question interface. + */ +export interface Question { + id: number; + description: string; + difficulty: Difficulty; + title: string; + topics?: string[]; +} diff --git a/services/collaboration/src/utils/helper.ts b/services/collaboration/src/utils/helper.ts index 887b9be6fc..cca66f6b8b 100644 --- a/services/collaboration/src/utils/helper.ts +++ b/services/collaboration/src/utils/helper.ts @@ -48,10 +48,20 @@ export const handleHttpServerError = (client: Response, message = 'Internal Serv * WS-specific handlers */ +/** + * Handle auth failed for WS + * @param ws + * @param message + */ export const handleAuthFailed = (ws: WebSocket, message: string) => { ws.close(WEBSOCKET_AUTH_FAILED, message); }; +/** + * Handle room closed for WS + * @param ws + * @param message + */ export const handleRoomClosed = (ws: WebSocket, message = 'Room closed') => { ws.close(WEBSOCKET_ROOM_CLOSED, message); };