From 02029815341130584755adea3620129844299701 Mon Sep 17 00:00:00 2001 From: Davian Kho Date: Mon, 4 Nov 2024 01:56:43 +0800 Subject: [PATCH 1/4] add: docker compose local --- backend/collaboration-service/package.json | 3 +- .../src/controllers/collab.controller.ts | 4 +- .../src/services/judgezero.service.ts | 16 ++- .../matching-service/__tests__/index.test.ts | 14 +++ docker-compose.local.yml | 103 +++++++++--------- nginx/templates/local-nginx.conf.template | 4 + package-lock.json | 1 + 7 files changed, 88 insertions(+), 57 deletions(-) create mode 100644 backend/matching-service/__tests__/index.test.ts diff --git a/backend/collaboration-service/package.json b/backend/collaboration-service/package.json index 91e1fdfeae..75f208814a 100644 --- a/backend/collaboration-service/package.json +++ b/backend/collaboration-service/package.json @@ -33,7 +33,8 @@ "reflect-metadata": "^0.2.2", "socket.io": "^4.8.1", "winston": "^3.14.2", - "y-websocket": "^2.0.4" + "y-websocket": "^2.0.4", + "ws": "^8.18.0" }, "devDependencies": { "@repo/eslint-config": "*", diff --git a/backend/collaboration-service/src/controllers/collab.controller.ts b/backend/collaboration-service/src/controllers/collab.controller.ts index e8dcc0745f..6cc71a283a 100644 --- a/backend/collaboration-service/src/controllers/collab.controller.ts +++ b/backend/collaboration-service/src/controllers/collab.controller.ts @@ -61,7 +61,7 @@ export async function submitCode(request: ITypedBodyRequest Object.values(error.constraints)) + const errorMessages = responseErrors.flatMap((error: ValidationError) => Object.values(error.constraints)) response.status(400).json(errorMessages).send() return } diff --git a/backend/collaboration-service/src/services/judgezero.service.ts b/backend/collaboration-service/src/services/judgezero.service.ts index ec58e58535..2a37072ef4 100644 --- a/backend/collaboration-service/src/services/judgezero.service.ts +++ b/backend/collaboration-service/src/services/judgezero.service.ts @@ -1,4 +1,4 @@ -import axios, { AxiosInstance, AxiosResponse } from 'axios' +import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios' import config from '../common/config.util' import { SubmissionRequestDto } from '@repo/submission-types' import logger from '../common/logger.util' @@ -15,10 +15,16 @@ class JudgeZero { }) // Request Interceptor - this.axiosInstance.interceptors.request.use((error) => { - logger.error(`[Judge-Zero] Failed to send Judge Zero API request: ${error}`) - return Promise.reject(error) - }) + this.axiosInstance.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + console.log(`Requesting [${config.method?.toUpperCase()}] ${config.baseURL}${config.url}`) + return config + }, + (error) => { + logger.error(`[Judge-Zero] Failed to send Judge Zero API request: ${error}`) + return Promise.reject(error) + } + ) // Response Interceptor this.axiosInstance.interceptors.response.use( diff --git a/backend/matching-service/__tests__/index.test.ts b/backend/matching-service/__tests__/index.test.ts new file mode 100644 index 0000000000..9eccce5ce2 --- /dev/null +++ b/backend/matching-service/__tests__/index.test.ts @@ -0,0 +1,14 @@ +import request from 'supertest' +import configMock from '../__mocks__/config.mock' +import app from '../src/index' + +jest.mock('../src/common/config.util', () => configMock) + +describe('Index', () => { + describe('GET /', () => { + it('should return 200 OK', async () => { + const response = await request(app).get('/') + expect(response.status).toBe(200) + }) + }) +}) diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 4c916bca54..d7f0cdb65a 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -6,6 +6,7 @@ services: - USER_SERVICE_URL=http://user-service:3002 - QUESTION_SERVICE_URL=http://question-service:3004 - MATCHING_SERVICE_URL=http://matching-service:3006 + - COLLABORATION_SERVICE_URL=http://collaboration-service:3008 - RMQ_URL=http://rabbitmq:15672 - DOMAIN_NAME=${DOMAIN_NAME} image: nginx:1.27.2-alpine @@ -22,58 +23,56 @@ services: - matching-service command: sh -c "envsubst < /etc/nginx/templates/nginx.conf.template > /etc/nginx/nginx.conf && nginx -g 'daemon off;'" - # collaboration-service: - # container_name: collaboration-service - # port: - # - 3008:3008 - # build: - # context: . - # dockerfile: ./backend/collaboration-service/Dockerfile - # environment: - # - NODE_ENV=development - # - PORT=3006 - # - DB_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@collab-db:27017/collaboration-service?authSource=admin - # - ACCESS_TOKEN_PUBLIC_KEY=${ACCESS_TOKEN_PUBLIC_KEY} - # - ACCESS_TOKEN_PRIVATE_KEY=${ACCESS_TOKEN_PRIVATE_KEY} - # - USER_SERVICE_URL=http://user-service:3002 - # - QUESTION_SERVICE_URL=http://question-service:3004 - # - MATCHING_SERVICE_URL=http://matching-service:3006 - # - JUDGE_ZERO_URL=http://judgezero-server:2358 - # - JUDGE_ZERO_SUBMIT_CONFIG=${JUDGE_ZERO_SUBMIT_CONFIG} - # restart: always - # networks: - # - backend-network - # depends_on: - # - judgezero-server - # - judgezero-workers - # - collab-db + collaboration-service: + container_name: collaboration-service + build: + context: . + dockerfile: ./backend/collaboration-service/Dockerfile + environment: + - NODE_ENV=development + - PORT=3008 + - DB_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@collab-db:27017/collaboration-service?authSource=admin + - ACCESS_TOKEN_PUBLIC_KEY=${ACCESS_TOKEN_PUBLIC_KEY} + - ACCESS_TOKEN_PRIVATE_KEY=${ACCESS_TOKEN_PRIVATE_KEY} + - USER_SERVICE_URL=http://user-service:3002 + - QUESTION_SERVICE_URL=http://question-service:3004 + - MATCHING_SERVICE_URL=http://matching-service:3006 + - JUDGE_ZERO_URL=http://judgezero-server:2358 + - JUDGE_ZERO_SUBMIT_CONFIG=${JUDGE_ZERO_SUBMIT_CONFIG} + restart: always + networks: + - backend-network + depends_on: + - judgezero-server + - judgezero-workers + - collab-db - # collab-db: - # container_name: collab-db - # image: mongo:8.0.0 - # environment: - # - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER} - # - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} - # ports: - # - '27087:27017' - # volumes: - # - 'collab_data:/data/db' - # networks: - # - backend-network - # command: mongod --quiet --logpath /dev/null --auth + collab-db: + container_name: collab-db + image: mongo:8.0.0 + environment: + - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} + ports: + - '27087:27017' + volumes: + - 'collab_data:/data/db' + networks: + - backend-network + command: mongod --quiet --logpath /dev/null --auth judgezero-server: image: judge0/judge0:1.13.1 volumes: - ./judge0.conf:/judge0.conf:ro - ports: - - 2358:2358 + networks: + - backend-network privileged: true restart: always depends_on: - judgezero-db: + db: condition: service_healthy - judgezero-redis: + redis: condition: service_healthy judgezero-workers: @@ -81,39 +80,45 @@ services: command: ["./scripts/workers"] volumes: - ./judge0.conf:/judge0.conf:ro + networks: + - backend-network privileged: true restart: always depends_on: - judgezero-db: + db: condition: service_healthy - judgezero-redis: + redis: condition: service_healthy - judgezero-db: + db: image: postgres:16.2 env_file: judge0.conf volumes: - judgezero_data:/var/lib/postgresql/data/ + networks: + - backend-network restart: always healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: ["CMD-SHELL", "pg_isready -U judge0"] interval: 10s retries: 5 - start_period: 5s + start_period: 10s - judgezero-redis: + redis: image: redis:7.2.4 command: [ "bash", "-c", 'docker-entrypoint.sh --appendonly no --requirepass "$$REDIS_PASSWORD"' ] env_file: judge0.conf + networks: + - backend-network restart: always healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s retries: 5 - start_period: 5s + start_period: 10s matching-service: container_name: matching-service diff --git a/nginx/templates/local-nginx.conf.template b/nginx/templates/local-nginx.conf.template index 80f4ac2a0d..ecda46f422 100644 --- a/nginx/templates/local-nginx.conf.template +++ b/nginx/templates/local-nginx.conf.template @@ -42,6 +42,10 @@ http { proxy_pass ${RMQ_URL}/; } + location /collab { + proxy_pass ${COLLABORATION_SERVICE_URL}; + } + error_page 404 /404.json; location /404.json { return 404 '{"error":{"code":404,"message":"Not Found"}}'; diff --git a/package-lock.json b/package-lock.json index 8945bcd300..a4a5514e02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "reflect-metadata": "^0.2.2", "socket.io": "^4.8.1", "winston": "^3.14.2", + "ws": "^8.18.0", "y-websocket": "^2.0.4" }, "devDependencies": { From 054dd3556161f2bfa6c44acc0004aeac056df1b6 Mon Sep 17 00:00:00 2001 From: Davian Kho Date: Mon, 4 Nov 2024 02:01:32 +0800 Subject: [PATCH 2/4] feat: add collab svc to docker compose for VPS --- .github/workflows/cd.yml | 2 + .github/workflows/ci.yml | 2 + docker-compose.yml | 98 +++++++++++++++++++++++++++++ nginx/templates/nginx.conf.template | 4 ++ 4 files changed, 106 insertions(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 26c44df055..593c90174c 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -18,6 +18,8 @@ jobs: dockerfile: ./backend/question-service/Dockerfile - image: glemenneo/cs3219-ay2425s1-project-g31-matching-service dockerfile: ./backend/matching-service/Dockerfile + - image: glemenneo/cs3219-ay2425s1-project-g31-collaboration-service + dockerfile: ./backend/collaboration-service/Dockerfile steps: - name: Check out code uses: actions/checkout@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6cfeff54f7..d7aff78467 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: dockerfile: ./backend/question-service/Dockerfile - image: glemenneo/cs3219-ay2425s1-project-g31-matching-service dockerfile: ./backend/matching-service/Dockerfile + - image: glemenneo/cs3219-ay2425s1-project-g31-collaboration-service + dockerfile: ./backend/collaboration-service/Dockerfile steps: - name: Check out code uses: actions/checkout@v4 diff --git a/docker-compose.yml b/docker-compose.yml index 88e029e1ba..4b4c6ae7be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: - USER_SERVICE_URL=http://user-service:3002 - QUESTION_SERVICE_URL=http://question-service:3004 - MATCHING_SERVICE_URL=http://matching-service:3006 + - COLLABORATION_SERVICE_URL=http://collaboration-service:3008 - RMQ_URL=http://rabbitmq:15672 - DOMAIN_NAME=${DOMAIN_NAME} - VPS_SSL_CERT_FILE=${VPS_SSL_CERT_FILE} @@ -31,6 +32,101 @@ services: - matching-service command: sh -c "envsubst < /etc/nginx/templates/nginx.conf.template > /etc/nginx/nginx.conf && nginx -g 'daemon off;'" + collaboration-service: + container_name: collaboration-service + image: glemenneo/cs3219-ay2425s1-project-g31-collaboration-service:latest + environment: + - NODE_ENV=development + - PORT=3008 + - DB_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@collab-db:27017/collaboration-service?authSource=admin + - ACCESS_TOKEN_PUBLIC_KEY=${ACCESS_TOKEN_PUBLIC_KEY} + - ACCESS_TOKEN_PRIVATE_KEY=${ACCESS_TOKEN_PRIVATE_KEY} + - USER_SERVICE_URL=http://user-service:3002 + - QUESTION_SERVICE_URL=http://question-service:3004 + - MATCHING_SERVICE_URL=http://matching-service:3006 + - JUDGE_ZERO_URL=http://judgezero-server:2358 + - JUDGE_ZERO_SUBMIT_CONFIG=${JUDGE_ZERO_SUBMIT_CONFIG} + restart: always + networks: + - backend-network + depends_on: + - judgezero-server + - judgezero-workers + - collab-db + + collab-db: + container_name: collab-db + image: mongo:8.0.0 + environment: + - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} + ports: + - '27087:27017' + volumes: + - 'collab_data:/data/db' + networks: + - backend-network + command: mongod --quiet --logpath /dev/null --auth + + judgezero-server: + image: judge0/judge0:1.13.1 + volumes: + - ./judge0.conf:/judge0.conf:ro + networks: + - backend-network + privileged: true + restart: always + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + + judgezero-workers: + image: judge0/judge0:1.13.1 + command: ["./scripts/workers"] + volumes: + - ./judge0.conf:/judge0.conf:ro + networks: + - backend-network + privileged: true + restart: always + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + + db: + image: postgres:16.2 + env_file: judge0.conf + volumes: + - judgezero_data:/var/lib/postgresql/data/ + networks: + - backend-network + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U judge0"] + interval: 10s + retries: 5 + start_period: 10s + + redis: + image: redis:7.2.4 + command: [ + "bash", "-c", + 'docker-entrypoint.sh --appendonly no --requirepass "$$REDIS_PASSWORD"' + ] + env_file: judge0.conf + networks: + - backend-network + restart: always + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + retries: 5 + start_period: 10s + matching-service: container_name: matching-service image: glemenneo/cs3219-ay2425s1-project-g31-matching-service:latest @@ -145,6 +241,8 @@ volumes: matching_data: rabbitmq_data: rabbitmq_log: + collab_data: + judgezero_data: # Define a network, which allows containers to communicate # with each other, by using their container name as a hostname diff --git a/nginx/templates/nginx.conf.template b/nginx/templates/nginx.conf.template index 67b0743da5..988ec68096 100644 --- a/nginx/templates/nginx.conf.template +++ b/nginx/templates/nginx.conf.template @@ -65,6 +65,10 @@ http { proxy_pass ${RMQ_URL}/; } + location /collab { + proxy_pass ${COLLABORATION_SERVICE_URL}; + } + error_page 404 /404.json; location /404.json { return 404 '{"error":{"code":404,"message":"Not Found"}}'; From b97a54d63004567a4048813d6270cb5840bc1ee3 Mon Sep 17 00:00:00 2001 From: Davian Kho Date: Mon, 4 Nov 2024 02:10:22 +0800 Subject: [PATCH 3/4] feat: add base jest test for matching service --- backend/matching-service/__tests__/index.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/matching-service/__tests__/index.test.ts b/backend/matching-service/__tests__/index.test.ts index 9eccce5ce2..99d44cfa05 100644 --- a/backend/matching-service/__tests__/index.test.ts +++ b/backend/matching-service/__tests__/index.test.ts @@ -3,6 +3,8 @@ import configMock from '../__mocks__/config.mock' import app from '../src/index' jest.mock('../src/common/config.util', () => configMock) +jest.mock('../src/common/rabbitmq.util', () => () => {}) +jest.mock('../src/services/rabbitmq.service', () => {}) describe('Index', () => { describe('GET /', () => { From b38a8689de7936cd98c93e4a09c8d4758572486b Mon Sep 17 00:00:00 2001 From: Davian Kho Date: Mon, 4 Nov 2024 02:51:53 +0800 Subject: [PATCH 4/4] fix: matching service test --- backend/matching-service/__mocks__/config.mock.ts | 8 +++++--- backend/matching-service/__tests__/index.test.ts | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/backend/matching-service/__mocks__/config.mock.ts b/backend/matching-service/__mocks__/config.mock.ts index fce6bf8fa4..e19a8d4bdf 100644 --- a/backend/matching-service/__mocks__/config.mock.ts +++ b/backend/matching-service/__mocks__/config.mock.ts @@ -9,11 +9,13 @@ const { publicKey, privateKey } = generateKeyPairSync('rsa', { export default { NODE_ENV: 'test', - PORT: '8000', + PORT: '3006', ACCESS_TOKEN_PUBLIC_KEY: Buffer.from(publicKey).toString('base64'), ACCESS_TOKEN_PRIVATE_KEY: Buffer.from(privateKey).toString('base64'), + DB_URL: 'mongodb://localhost:27017/matching-service', + USER_SERVICE_URL: 'http://localhost:3002', + QUESTION_SERVICE_URL: 'http://localhost:3004', RMQ_USER: 'test', RMQ_PASSWORD: 'test', - RMQ_HOST: 'test', - DB_URL: 'test', + RMQ_HOST: 'localhost', } diff --git a/backend/matching-service/__tests__/index.test.ts b/backend/matching-service/__tests__/index.test.ts index 99d44cfa05..5fd1ed2b51 100644 --- a/backend/matching-service/__tests__/index.test.ts +++ b/backend/matching-service/__tests__/index.test.ts @@ -3,8 +3,10 @@ import configMock from '../__mocks__/config.mock' import app from '../src/index' jest.mock('../src/common/config.util', () => configMock) -jest.mock('../src/common/rabbitmq.util', () => () => {}) -jest.mock('../src/services/rabbitmq.service', () => {}) +jest.mock('../src/server.ts', () => { + wsConnection: { + } +}) describe('Index', () => { describe('GET /', () => {