Skip to content

Commit

Permalink
Merge pull request #190 from CS3219-AY2425S1/integrate-judge0-fe
Browse files Browse the repository at this point in the history
Integrate Judge0 with FE
  • Loading branch information
shishirbychapur authored Nov 5, 2024
2 parents 59ec090 + 938217e commit 4322591
Show file tree
Hide file tree
Showing 19 changed files with 228 additions and 101 deletions.
26 changes: 15 additions & 11 deletions backend/collaboration-service/src/controllers/collab.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ValidationError } from 'class-validator'
import { Request, Response } from 'express'
import { ITypedBodyRequest } from '@repo/request-types'
import { SubmissionRequestDto, SubmissionResponseDto } from '@repo/submission-types'
import { ISubmission } from '@repo/submission-types'
import { SubmissionRequestDto } from '../types/SubmissionRequestDto'
import { SubmissionResponseDto } from '../types/SubmissionResponseDto'
import { CollabDto } from '../types/CollabDto'
import { createSession, getSessionById } from '../models/collab.repository'
import judgeZero from '../services/judgezero.service'
Expand Down Expand Up @@ -65,31 +67,33 @@ export async function getChatHistory(request: Request, response: Response): Prom
response.status(200).json(session.chatHistory).send()
}

export async function submitCode(request: ITypedBodyRequest<SubmissionRequestDto>, response: Response): Promise<void> {
const submissionRequestDto = SubmissionRequestDto.fromRequest(request)
export async function submitCode(dto: ISubmission): Promise<SubmissionResponseDto> {
const submissionRequestDto = new SubmissionRequestDto(
dto.language_id,
dto.source_code,
dto.expected_output,
dto.stdin
)
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
throw new Error(errorMessages.join('\n'))
}

const res = await judgeZero.post('/submissions?base64_encoded=false&wait=true', request.body)
const res = await judgeZero.post('/submissions?base64_encoded=false&wait=true', submissionRequestDto)

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

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

if (responseErrors.length) {
const errorMessages = responseErrors.flatMap((error: ValidationError) => Object.values(error.constraints))
response.status(400).json(errorMessages).send()
return
throw new Error(errorMessages.join('\n'))
}

response.status(200).json(submissionResponseDto).send()
return submissionResponseDto
}
3 changes: 1 addition & 2 deletions 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, getChatHistory, getSession, submitCode } from '../controllers/collab.controller'
import { createSessionRequest, getChatHistory, getSession } from '../controllers/collab.controller'

const router = Router()

Expand All @@ -10,6 +10,5 @@ router.use(passport.authenticate('jwt', { session: false }))
router.put('/', createSessionRequest)
router.get('/:id', getSession)
router.get('/chat/:id', getChatHistory)
router.post('/submit', submitCode)

export default router
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import config from '../common/config.util'
import { SubmissionRequestDto } from '@repo/submission-types'
import { SubmissionRequestDto } from '../types/SubmissionRequestDto'
import logger from '../common/logger.util'

class JudgeZero {
Expand Down
15 changes: 15 additions & 0 deletions backend/collaboration-service/src/services/socketio.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { Server as IOServer, Socket } from 'socket.io'
import { completeCollaborationSession } from './collab.service'
import { updateChatHistory, updateLanguage } from '../models/collab.repository'
import { LanguageMode } from '../types/LanguageMode'
import { IResponse, ISubmission } from '@repo/submission-types'
import { SubmissionResponseDto } from '../types/SubmissionResponseDto'
import { ChatModel } from '../types'
import { submitCode } from '../controllers/collab.controller'

export class WebSocketConnection {
private io: IOServer
Expand Down Expand Up @@ -38,6 +41,18 @@ export class WebSocketConnection {
await updateLanguage(roomId, language as LanguageMode)
})

socket.on('run-code', async (data: ISubmission) => {
this.io.to(roomId).emit('executing-code')
try {
const dto: SubmissionResponseDto = await submitCode(data)
const { stdout, status, time, stderr, compile_output } = dto
const response: IResponse = { stdout, status, time, stderr, compile_output }
this.io.to(roomId).emit('code-executed', response, data.expected_output)
} catch (err) {
this.io.to(roomId).emit('code-executed', { error: err })
}
})

socket.on('disconnect', async () => {
const room = this.io.sockets.adapter.rooms.get(roomId)
socket.leave(roomId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { ITypedBodyRequest } from '@repo/request-types'
import { IsNotEmpty, IsNumber, IsString, validate, ValidationError } from 'class-validator'
import 'reflect-metadata'

export class SubmissionRequestDto {
@IsNumber()
@IsNotEmpty()
language_id: number

@IsString()
source_code: string

Expand All @@ -15,19 +11,17 @@ export class SubmissionRequestDto {
@IsString()
expected_output: string

@IsNumber()
@IsNotEmpty()
language_id: number

constructor(language_id: number, source_code: string, expected_output: string, stdin?: string) {
this.language_id = language_id
this.source_code = source_code
this.stdin = stdin || ''
this.stdin = stdin ?? ''
this.expected_output = expected_output
}

static fromRequest({
body: { language_id, source_code, stdin, expected_output },
}: ITypedBodyRequest<SubmissionRequestDto>): SubmissionRequestDto {
return new SubmissionRequestDto(language_id, source_code, stdin, expected_output)
}

async validate(): Promise<ValidationError[]> {
return validate(this)
}
Expand Down
13 changes: 5 additions & 8 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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
build:
Expand Down Expand Up @@ -76,7 +76,7 @@ services:

judgezero-workers:
image: judge0/judge0:1.13.1
command: ["./scripts/workers"]
command: ['./scripts/workers']
volumes:
- ./judge0.conf:/judge0.conf:ro
networks:
Expand All @@ -98,23 +98,20 @@ services:
- backend-network
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U judge0"]
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"'
]
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"]
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
retries: 5
start_period: 10s
Expand Down
9 changes: 2 additions & 7 deletions frontend/components/account/AccountSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,15 @@ const AccountSetting = () => {
const accountTabs = ['Profile', 'Setting']
const [activeTab, setActiveTab] = useState(0) // 0: Profile, 1: Setting

const handleActiveTabChange = (tab: number) => {
setActiveTab(tab)
console.log(activeTab)
}

return (
<>
<div className="h-[calc(100vh-101px)]">
<div className="flex flex-col flex-grow w-full h-full pl-6 pt-3">
<div className="flex flex-row mb-3">
<CustomTabs
tabs={accountTabs}
handleActiveTabChange={handleActiveTabChange}
isBottomBorder={true}
activeTab={activeTab}
setActiveTab={setActiveTab}
className="flex flex-[4]"
btnclassName="text-lg"
/>
Expand Down
22 changes: 7 additions & 15 deletions frontend/components/customs/custom-tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,43 @@
import { useState } from 'react'
import { Button, ButtonProps } from '../ui/button'

interface CustomTabsProps {
tabs: string[]
isBottomBorder?: boolean
activeTab: number
setActiveTab: (idx: number) => void
type?: 'default' | 'label'
size?: ButtonProps['size']
handleActiveTabChange: (idx: number) => void
className?: string
btnclassName?: string
}

export default function CustomTabs({
tabs,
handleActiveTabChange,
isBottomBorder,
activeTab,
setActiveTab,
type,
size,
className,
btnclassName,
}: CustomTabsProps) {
const [activeTab, setActiveTab] = useState(0)

const tabVariant = (idx: number) => {
if (activeTab === idx) {
return type === 'label' ? 'activeTabLabel' : 'activeTab'
}
return type === 'label' ? 'ghostTabLabel' : 'ghostTab'
}

const handleSetActiveTab = (idx: number) => {
setActiveTab(idx)
handleActiveTabChange(idx)
}

return (
<div
id="test-tabs"
className={`${isBottomBorder ? 'border-b-2 border-slate-100' : ''} flex items-center ${type === 'label' ? 'gap-2' : ''} ${className}`}
className={`${activeTab === 0 ? 'border-slate-100' : ''} flex items-center ${type === 'label' ? 'gap-2' : ''} ${className}`}
>
{tabs.map((tab, index) => (
<Button
key={index}
variant={tabVariant(index)}
size={size || 'default'}
onClick={() => handleSetActiveTab(index)}
className={`${isBottomBorder ? '-mb-[0.1rem]' : ''} ${btnclassName}`}
onClick={() => setActiveTab(index)}
className={`${activeTab === 0 ? '-mb-[0.1rem]' : ''} ${btnclassName}`}
>
{tab}
</Button>
Expand Down
Loading

0 comments on commit 4322591

Please sign in to comment.