Skip to content

Commit

Permalink
Merge branch 'main' into collab-page-enhancement-1
Browse files Browse the repository at this point in the history
* main:
  Create GitHub Actions for auto deployment to AWS (#66)
  History Service (#69)
  Minor Fix To Handle isForfeit Status (#78)

# Conflicts:
#	services/collaboration/src/controllers/roomController.ts
  • Loading branch information
KhoonSun47 committed Nov 3, 2024
2 parents c3215a4 + ccd3d92 commit 5bb85a2
Show file tree
Hide file tree
Showing 43 changed files with 7,144 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ COLLAB_DB_LOCAL_URI=mongodb://collaboration-db:27017/collaboration-service
YJS_DB_CLOUD_URI=mongodb+srv://<username>:<password>@cluster0.h5ukw.mongodb.net/yjs-documents?retryWrites=true&w=majority&appName=Cluster0
YJS_DB_LOCAL_URI=mongodb://collaboration-db:27017/yjs-documents

# History Service
HISTORY_DB_CLOUD_URI=<FILL-THIS-IN>
HISTORY_DB_LOCAL_URI=mongodb://history-db:27017/history

# Will use cloud MongoDB Atlas database
ENV=PROD

Expand Down
64 changes: 64 additions & 0 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Deploy Backend Services

on:
push:
branches: [ 'production' ]

workflow_dispatch:

permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout

env:
AWS_REGION: ap-southeast-1
ECS_CLUSTER: backend-cluster

jobs:
deploy:
name: Deploy Backend Service
runs-on: ubuntu-latest
environment: production

strategy:
matrix:
service: [ 'question', 'user', 'match', 'collaboration' ]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure AWS credentials
id: aws-configure
uses: aws-actions/configure-aws-credentials@v4.0.2
with:
role-to-assume: ${{ secrets.AWS_BACKEND_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ env.AWS_REGION }}

- name: Login to AWS ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2.0.1

- name: Build and push ${{ matrix.service }} image to AWS ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ matrix.service }}
IMAGE_TAG: latest
run: |
echo "Building $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG ./services/${{ matrix.service }}
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Update AWS Service (${{ matrix.service }}) # Trigger re-deployment with latest image
id: update-service
env:
ECS_SERVICE: ${{ matrix.service }}-service
run: |
echo "Updating $ECS_SERVICE for $ECS_CLUSTER"
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service $ECS_SERVICE \
--force-new-deployment \
--region $AWS_REGION
44 changes: 44 additions & 0 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Deploy Frontend

on:
push:
branches: [ 'production' ]

workflow_dispatch:

permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout

env:
AWS_REGION: ap-southeast-1
S3_BUCKET_NAME: app.peerprep.org

jobs:
deploy:
name: Deploy Frontend
runs-on: ubuntu-latest
environment: production

steps:
- uses: actions/checkout@v4

- name: Configure AWS credentials
id: aws-configure
uses: aws-actions/configure-aws-credentials@v4.0.2
with:
role-to-assume: ${{ secrets.AWS_FRONTEND_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ env.AWS_REGION }}

- name: Build frontend distribution
working-directory: frontend
run: npm ci && npm run build

- name: Sync distribution to S3
run: |
aws s3 sync ./frontend/dist/frontend/browser/ s3://$S3_BUCKET_NAME --delete
- name: Invalidate Cloudfront Cache
run: |
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_ID }} --paths "/*"
19 changes: 19 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,22 @@ jobs:
run: cd ${{ matrix.service }} && npm run lint
- name: Build App
run: cd ${{ matrix.service }} && npm run build


build-history:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.node-version }}
- name: Install Node Modules
run: cd services/history && npm ci
- name: Linting
run: cd services/history && npm run lint
- name: Build App
run: cd services/history && npm run build
- name: Run tests
run: cd services/history && npm run test
16 changes: 16 additions & 0 deletions compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ services:
ports:
- 27020:27017

collaboration-db:
ports:
- 27020:27017

history:
command: npm run dev
ports:
- 8086:8086
volumes:
- /app/node_modules
- ./services/history:/app

history-db:
ports:
- 27021:27017

broker:
ports:
- 5672:5672
Expand Down
32 changes: 31 additions & 1 deletion compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ services:
- user
- match
- collaboration
- history
networks:
- gateway-network
restart: always
Expand Down Expand Up @@ -167,12 +168,39 @@ services:
networks:
- collaboration-db-network
restart: always

history:
container_name: history
image: history
build:
context: services/history
dockerfile: Dockerfile
environment:
DB_CLOUD_URI: ${HISTORY_DB_CLOUD_URI}
DB_LOCAL_URI: ${HISTORY_DB_LOCAL_URI}
BROKER_URL: ${BROKER_URL}
JWT_SECRET: ${JWT_SECRET}
depends_on:
broker:
condition: service_healthy
networks:
- gateway-network
- history-db-network

history-db:
container_name: history-db
image: mongo:7.0.14
volumes:
- history-db:/data/db
networks:
- history-db-network

volumes:
question-db:
user-db:
match-db:
collaboration-db:
history-db:

networks:
gateway-network:
Expand All @@ -184,4 +212,6 @@ networks:
match-db-network:
driver: bridge
collaboration-db-network:
driver: bridge
driver: bridge
history-db-network:
driver: bridge
9 changes: 9 additions & 0 deletions nginx/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ upstream collaboration-api {
server collaboration:8084;
}

upstream history-api {
server history:8086;
}

server {
listen 8080;
server_name localhost;
Expand All @@ -40,4 +44,9 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

location /api/history {
proxy_pass http://history-api;
proxy_set_header Host $host;
}
}
51 changes: 50 additions & 1 deletion services/collaboration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,14 +348,16 @@ 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 two
the `matching service` and `collaboration service`) in an asynchronous manner. The system consists of a consumer and four
producers:

### Queues Used

- `QUESTION_FOUND`: Handles messages related to matching users and creating collaboration rooms.
- `COLLAB_CREATED`: Sends messages indicating that a collaboration room has been successfully created.
- `MATCH_FAILED`: Sends messages indicating that a collaboration room could not be created.
- `CREATE_HISTORY`: Sends messages requesting that user history be created for the new collaboration room.
- `UPDATE_HISTORY`: Sends messages requesting that user history be updated for the new collaboration room.

---

Expand Down Expand Up @@ -393,6 +395,53 @@ The producer will send a message to the `MATCH_FAILED` queue when a collaboratio
}
```

The producer will send a message to the `CREATE_HISTORY` queue when a collaboration room was created successfully.

- **Queue**: `CREATE_HISTORY`
- **Data Produced**
- `roomId` - The ID of the collaboration room.
- `user1` - The first user associated with the collaboration room.
- `user2` - The second user associated with the collaboration room.
- `question` - The question associated with the collaboration room.

```json
{
"roomId": "67234d29aa52f2376973f96a",
"user1": {
"username": "user123",
"_id": "671a064a6f536e9af46b0017"
},
"user2": {
"username": "userabc",
"_id": "671a06526f536e9af46b001f"
},
"question": {
"id": 1,
"title": "Roman to Integer",
"description": "Given a roman numeral, convert it to an integer.",
"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.

- **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"`.

```json
{
"roomId": "67234d29aa52f2376973f96a",
"userId": "671a064a6f536e9af46b0017",
"status": "FORFEITED"
},
```

---

## Consumer
Expand Down
9 changes: 9 additions & 0 deletions services/collaboration/src/controllers/roomController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from '../services/mongodbService';
import { handleHttpNotFound, handleHttpSuccess, handleHttpServerError, handleHttpBadRequest } from '../utils/helper';
import { Room } from './types';
import { produceUpdateHistory } from '../events/producer';
import { HistoryStatus } from '../types/message';

export enum Difficulty {
Easy = 'Easy',
Expand Down Expand Up @@ -96,6 +98,12 @@ export const closeRoomController = async (req: Request, res: Response) => {
}

await deleteYjsDocument(roomId);
await Promise.all(
room.users
.filter(user => !user.isForfeit)
.map(user => produceUpdateHistory(roomId, user.id, HistoryStatus.COMPLETED)),
);

console.log(`Room ${roomId} closed and Yjs document removed`);

return handleHttpSuccess(res, `Room ${roomId} successfully closed`);
Expand Down Expand Up @@ -130,6 +138,7 @@ export const updateUserStatusInRoomController = async (req: Request, res: Respon
return handleHttpNotFound(res, 'User not found in room');
}

await produceUpdateHistory(roomId, userId, HistoryStatus.FORFEITED);
const allUsersForfeited = updatedRoom.users.every(user => user.isForfeit === true);
if (allUsersForfeited) {
const result = await closeRoomById(roomId);
Expand Down
8 changes: 7 additions & 1 deletion services/collaboration/src/events/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Queues } from './queues';
import messageBroker from './broker';
import { createRoomWithQuestion } from '../controllers/roomController';
import { QuestionFoundEvent } from '../types/event';
import { produceCollabCreated, produceCollabCreateFailedEvent } from './producer';
import { produceCollabCreated, produceCollabCreateFailedEvent, produceCreateHistory } from './producer';

async function consumeQuestionFound(message: QuestionFoundEvent) {
console.log('Attempting to create room:', message);
Expand All @@ -15,6 +15,12 @@ async function consumeQuestionFound(message: QuestionFoundEvent) {
if (roomId) {
console.log('Room created with ID:', message, roomId);
await produceCollabCreated(requestId1, requestId2, roomId, question);
await produceCreateHistory(
roomId,
{ _id: user1.id, username: user1.username },
{ _id: user2.id, username: user2.username },
question,
);
} else {
console.log('Failed to create room:', message);
await produceCollabCreateFailedEvent(requestId1, requestId2);
Expand Down
11 changes: 11 additions & 0 deletions services/collaboration/src/events/producer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CollabCreatedEvent, IdType, MatchFailedEvent, Question } from '../types/event';
import { CreateHistoryMessage, HistoryStatus, UpdateHistoryMessage, User } from '../types/message';
import messageBroker from './broker';
import { Queues } from './queues';

Expand All @@ -18,3 +19,13 @@ export async function produceCollabCreateFailedEvent(requestId1: IdType, request
const message: MatchFailedEvent = { requestId1, requestId2, reason: COLLAB_CREATED_ERROR };
await messageBroker.produce(Queues.MATCH_FAILED, message);
}

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);
}

export async function produceUpdateHistory(roomId: IdType, userId: IdType, status: HistoryStatus) {
const message: UpdateHistoryMessage = { roomId, userId, status };
await messageBroker.produce(Queues.UPDATE_HISTORY, message);
}
2 changes: 2 additions & 0 deletions services/collaboration/src/events/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ export enum Queues {
QUESTION_FOUND = 'QUESTION_FOUND',
COLLAB_CREATED = 'COLLAB_CREATED',
MATCH_FAILED = 'MATCH_FAILED',
CREATE_HISTORY = 'CREATE_HISTORY',
UPDATE_HISTORY = 'UPDATE_HISTORY',
}
Loading

0 comments on commit 5bb85a2

Please sign in to comment.