Skip to content

Commit

Permalink
Fix bug for collab (CS3219-AY2425S1#92)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
limcaaarl and KhoonSun47 authored Nov 13, 2024
1 parent 82f1389 commit 1a61971
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,18 @@ <h2 class="mt-0 mb-0">Submit</h2>
</div>
<p-progressBar mode="indeterminate" [style]="{ height: '6px' }" />
<ng-template pTemplate="footer">
<div class="flex w-full justify-content-end gap-4">
<p-button type="button" label="Cancel" severity="danger" [outlined]="true" (click)="cancel()" />
@if (!isInitiator || (numForfeit !== 2 && numUniqueUsers === 1)) {
<p-button type="button" label="Agree" severity="success" [outlined]="true" (click)="agreeSubmit()" />
}
</div>
@if (!isConsent) {
<div class="flex w-full justify-content-end gap-4">
<p-button type="button" label="Cancel" severity="danger" [outlined]="true" (click)="cancel()" />
@if (!isInitiator || (numForfeit !== 2 && numUniqueUsers === 1)) {
<p-button
type="button"
label="Agree"
severity="success"
[outlined]="true"
(click)="agreeSubmit()" />
}
</div>
}
</ng-template>
</p-dialog>
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class SubmitDialogComponent implements AfterViewInit {
ysubmit!: Y.Map<boolean>;
yforfeit!: Y.Map<boolean>;
userId!: string;
isConsent = false;

constructor(
@Inject(DOCUMENT) private document: Document,
Expand Down Expand Up @@ -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);
Expand Down
29 changes: 16 additions & 13 deletions services/collaboration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
{
Expand All @@ -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
{
Expand Down Expand Up @@ -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
{
Expand All @@ -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
{
Expand Down
16 changes: 1 addition & 15 deletions services/collaboration/src/controllers/roomController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 0 additions & 21 deletions services/collaboration/src/controllers/types.ts

This file was deleted.

7 changes: 7 additions & 0 deletions services/collaboration/src/events/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
25 changes: 25 additions & 0 deletions services/collaboration/src/events/producer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
1 change: 0 additions & 1 deletion services/collaboration/src/events/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions services/collaboration/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from 'express';
import { getHealth } from '../controllers';

const router = express.Router();

router.get('/ht', getHealth);
Expand Down
10 changes: 5 additions & 5 deletions services/collaboration/src/services/mongodbService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -29,7 +29,7 @@ const connectToRoomDB = async (): Promise<Db> => {
};

/**
* Connect to the YJS database
* Connect to the Yjs Document database
*/
const connectToYJSDB = async (): Promise<Db> => {
try {
Expand Down Expand Up @@ -66,7 +66,7 @@ export const startMongoDB = async (): Promise<void> => {
};

/**
* 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
Expand Down Expand Up @@ -108,7 +108,7 @@ export const findRoomById = async (roomId: string, userId: string): Promise<With
};

/**
* Create and bind a Yjs document using the room_id as the document name
* Create and bind a Yjs document using the room_id as the collection name
* @param roomId
* @returns
*/
Expand Down
3 changes: 1 addition & 2 deletions services/collaboration/src/services/webSocketService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const URL_REGEX = /^.*\/([0-9a-f]{24})\?accessToken=([a-zA-Z0-9\-._~%]{1,})$/;
/**
* Verifies the user's access to a specific room by validating the JWT token,
* checking the room's status, and ensuring the user has not forfeited.
* Returns `roomId` if the access is authorized, or `null` otherwise.
* @param ws
* @param request
* @returns
Expand Down Expand Up @@ -67,7 +66,7 @@ const authorize = async (ws: WebSocket, request: IncomingMessage): Promise<strin

/**
* Start and configure the WebSocket server
* @returns {WebSocketServer} The configured WebSocket server instance
* @returns {WebSocketServer}
*/
export const startWebSocketServer = (server: Server) => {
const wss = new WebSocketServer({ server });
Expand Down
46 changes: 46 additions & 0 deletions services/collaboration/src/types/collab.ts
Original file line number Diff line number Diff line change
@@ -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[];
}
10 changes: 10 additions & 0 deletions services/collaboration/src/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

0 comments on commit 1a61971

Please sign in to comment.