Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Judge0 with FE #190

Merged
merged 14 commits into from
Nov 5, 2024
Merged
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
Loading