Skip to content

Commit

Permalink
Merge pull request #183 from CS3219-AY2425S1/157-judge-zero-api-and-c…
Browse files Browse the repository at this point in the history
…ontainer

Implement Judge Zero into Collab Service
  • Loading branch information
Daviancold authored Nov 3, 2024
2 parents 7c7030f + f3f9ae8 commit 7200d8f
Show file tree
Hide file tree
Showing 15 changed files with 710 additions and 3 deletions.
2 changes: 2 additions & 0 deletions backend/collaboration-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/language-data": "^6.5.1",
"axios": "^1.7.7",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
Expand All @@ -37,6 +38,7 @@
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/request-types": "*",
"@repo/submission-types": "*",
"@repo/typescript-config": "*",
"@testcontainers/mongodb": "^10.13.1",
"@types/bcrypt": "^5.0.2",
Expand Down
32 changes: 32 additions & 0 deletions backend/collaboration-service/src/controllers/collab.controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ValidationError } from 'class-validator'
import { Request, Response } from 'express'
import { ITypedBodyRequest } from '@repo/request-types'
import { SubmissionRequestDto, SubmissionResponseDto } from '@repo/submission-types'
import { CollabDto } from '../types/CollabDto'
import { createSession, getSessionById } from '../models/collab.repository'
import judgeZero from '../services/judgezero.service'
import config from '../common/config.util'

export async function createSessionRequest(request: ITypedBodyRequest<CollabDto>, response: Response): Promise<void> {
const collabDto = CollabDto.fromRequest(request)
Expand Down Expand Up @@ -47,3 +50,32 @@ export async function getSession(request: Request, response: Response): Promise<
// Send retrieved data
response.status(200).json(session).send()
}

export async function submitCode(request: ITypedBodyRequest<SubmissionRequestDto>, response: Response): Promise<void> {
const submissionRequestDto = SubmissionRequestDto.fromRequest(request)
const requestErrors = await submissionRequestDto.validate()

if (requestErrors.length) {
const errorMessages = requestErrors.flatMap((error: ValidationError) => Object.values(error.constraints))
response.status(400).json(errorMessages).send()
return
}

const res = await judgeZero.post(config.JUDGE_ZERO_SUBMIT_CONFIG, submissionRequestDto)

if (!res) {
response.status(400).json('Failed to submit code. Please try again.').send()
return
}

const submissionResponseDto = SubmissionResponseDto.fromResponse(res)
const responseErrors = await submissionResponseDto.validate()

if (responseErrors.length) {
const errorMessages = requestErrors.flatMap((error: ValidationError) => Object.values(error.constraints))
response.status(400).json(errorMessages).send()
return
}

response.status(200).json(submissionResponseDto).send()
}
3 changes: 2 additions & 1 deletion backend/collaboration-service/src/routes/collab.routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Router } from 'express'
import passport from 'passport'
import { createSessionRequest, getSession } from '../controllers/collab.controller'
import { createSessionRequest, getSession, submitCode } from '../controllers/collab.controller'

const router = Router()

Expand All @@ -9,5 +9,6 @@ router.use(passport.authenticate('jwt', { session: false }))
// To change this route to enable retrival of sessions with pagination
router.put('/', createSessionRequest)
router.get('/:id', getSession)
router.post('/submit', submitCode)

export default router
41 changes: 41 additions & 0 deletions backend/collaboration-service/src/services/judgezero.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios'
import config from '../common/config.util'
import { SubmissionRequestDto } from '@repo/submission-types'
import logger from '../common/logger.util'

class JudgeZero {
private axiosInstance: AxiosInstance

constructor(baseURL: string) {
this.axiosInstance = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
},
})

// Request Interceptor
this.axiosInstance.interceptors.request.use((error) => {
logger.error(`[Judge-Zero] Failed to send Judge Zero API request: ${error}`)
return Promise.reject(error)
})

// Response Interceptor
this.axiosInstance.interceptors.response.use(
(response: AxiosResponse) => response,
(error) => {
logger.error(`[Judge-Zero] Error receving Judge Zero API response: ${error}`)
return Promise.reject(error)
}
)
}

public async post(url: string, data?: SubmissionRequestDto): Promise<AxiosResponse> {
const response = await this.axiosInstance.post(url, data)
return response
}
}

const judgeZero = new JudgeZero(config.JUDGE_ZERO_URL)

export default judgeZero
17 changes: 15 additions & 2 deletions backend/collaboration-service/src/types/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,23 @@ export class Config {
@IsUrl({ require_tld: false })
MATCHING_SERVICE_URL: string

@IsUrl({ require_tld: false })
JUDGE_ZERO_URL: string

@IsString()
@IsNotEmpty()
JUDGE_ZERO_SUBMIT_CONFIG: string

constructor(
NODE_ENV: string,
PORT: string,
DB_URL: string,
ACCESS_TOKEN_PUBLIC_KEY: string,
USER_SERVICE_URL: string,
QUESTION_SERVICE_URL: string,
MATCHING_SERVICE_URL: string
MATCHING_SERVICE_URL: string,
JUDGE_ZERO_URL: string,
JUDGE_ZERO_SUBMIT_CONFIG: string
) {
this.NODE_ENV = NODE_ENV ?? 'development'
this.PORT = PORT ?? '3006'
Expand All @@ -40,6 +49,8 @@ export class Config {
this.USER_SERVICE_URL = USER_SERVICE_URL
this.QUESTION_SERVICE_URL = QUESTION_SERVICE_URL
this.MATCHING_SERVICE_URL = MATCHING_SERVICE_URL
this.JUDGE_ZERO_URL = JUDGE_ZERO_URL
this.JUDGE_ZERO_SUBMIT_CONFIG = JUDGE_ZERO_SUBMIT_CONFIG
}

static fromEnv(env: { [key: string]: string | undefined }): Config {
Expand All @@ -50,7 +61,9 @@ export class Config {
env.ACCESS_TOKEN_PUBLIC_KEY!,
env.USER_SERVICE_URL!,
env.QUESTION_SERVICE_URL!,
env.MATCHING_SERVICE_URL!
env.MATCHING_SERVICE_URL!,
env.JUDGE_ZERO_URL!,
env.JUDGE_ZERO_SUBMIT_CONFIG!
)
}

Expand Down
95 changes: 95 additions & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,99 @@ services:
- question-service
- 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

# 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
privileged: true
restart: always
depends_on:
judgezero-db:
condition: service_healthy
judgezero-redis:
condition: service_healthy

judgezero-workers:
image: judge0/judge0:1.13.1
command: ["./scripts/workers"]
volumes:
- ./judge0.conf:/judge0.conf:ro
privileged: true
restart: always
depends_on:
judgezero-db:
condition: service_healthy
judgezero-redis:
condition: service_healthy

judgezero-db:
image: postgres:16.2
env_file: judge0.conf
volumes:
- judgezero_data:/var/lib/postgresql/data/
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
start_period: 5s

judgezero-redis:
image: redis:7.2.4
command: [
"bash", "-c",
'docker-entrypoint.sh --appendonly no --requirepass "$$REDIS_PASSWORD"'
]
env_file: judge0.conf
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
retries: 5
start_period: 5s

matching-service:
container_name: matching-service
Expand Down Expand Up @@ -142,6 +235,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
Expand Down
Loading

0 comments on commit 7200d8f

Please sign in to comment.