diff --git a/packages/api-server/src/routes/v1/contest.routes.ts b/packages/api-server/src/routes/v1/contest.routes.ts index 65a42b3e..e91fbda1 100644 --- a/packages/api-server/src/routes/v1/contest.routes.ts +++ b/packages/api-server/src/routes/v1/contest.routes.ts @@ -65,6 +65,7 @@ import { userAuthHook } from '../../hooks/authentication.hooks.js' import { contestInfoHook } from '../../hooks/contest.hooks.js' import { requestUserProfile } from '../../utils/auth.utils.js' import {fetchUser} from '../../services/user.services.js' +/*=*/ async function contestProblemRoutes (problemRoutes: FastifyTypeBox): Promise { problemRoutes.addHook('onRequest', contestInfoHook) diff --git a/packages/api-server/src/routes/v1/domain.routes.ts b/packages/api-server/src/routes/v1/domain.routes.ts index 80417f43..35a8d3a3 100644 --- a/packages/api-server/src/routes/v1/domain.routes.ts +++ b/packages/api-server/src/routes/v1/domain.routes.ts @@ -21,14 +21,15 @@ import { } from '../../services/domain.services.js' import { isSuperAdmin } from '../../auth/role.auth.js' import { hasDomainPrivilege } from '../../auth/scope.auth.js' -import { type FastifyTypeBox } from '../../types.js' -import { createDomainProblem, deleteDomainProblem, fetchDomainProblems, updateDomainProblem } from '../../services/problem.services.js' +import { type FastifyTypeBox } from '../../types.js' /*=*/ +import { deleteDomainProblem, fetchDomainProblems } from '../../services/problem.services.js' import { fetchDomainProblem } from '@argoncs/common' import { createTestingSubmission } from '../../services/submission.services.js' -import { createPolygonUploadSession, createUploadSession } from '../../services/testcase.services.js' +import { createPolygonUploadSession } from '../../services/testcase.services.js' import { UnauthorizedError, badRequestSchema, forbiddenSchema, methodNotAllowedSchema, notFoundSchema, unauthorizedSchema } from 'http-errors-enhanced' import { createContest, createContestSeries, fetchDomainContestSeries, fetchDomainContests } from '../../services/contest.services.js' import { userAuthHook } from '../../hooks/authentication.hooks.js' +/*=*/ async function domainMemberRoutes (memberRoutes: FastifyTypeBox): Promise { memberRoutes.post( @@ -134,6 +135,7 @@ async function domainMemberRoutes (memberRoutes: FastifyTypeBox): Promise } async function domainProblemRoutes (problemRoutes: FastifyTypeBox): Promise { +/* problemRoutes.post( '/', { @@ -158,6 +160,7 @@ async function domainProblemRoutes (problemRoutes: FastifyTypeBox): Promise { const problemId = nanoid() const problem: Problem = { ...newProblem, id: problemId, domainId } await domainProblemCollection.insertOne(problem) return { problemId } } +*/ +/* export async function updateDomainProblem ({ problemId, domainId, problem }: { problemId: string, domainId: string, problem: Partial }): Promise<{ modified: boolean }> { if (problem.testcases != null) { const testcasesVerifyQueue: Array> = [] problem.testcases.forEach((testcase) => { - testcasesVerifyQueue.push(testcaseExists({ problemId, domainId, filename: testcase.input.name, versionId: testcase.input.versionId })) - testcasesVerifyQueue.push(testcaseExists({ problemId, domainId, filename: testcase.output.name, versionId: testcase.output.versionId })) + testcasesVerifyQueue.push(testcaseExists({ problemId, filename: testcase.input.name, versionId: testcase.input.versionId })) + testcasesVerifyQueue.push(testcaseExists({ problemId, filename: testcase.output.name, versionId: testcase.output.versionId })) }) await Promise.all(testcasesVerifyQueue) } @@ -32,6 +35,7 @@ export async function updateDomainProblem ({ problemId, domainId, problem }: { p return { modified: modifiedCount > 0 } } +*/ export async function deleteDomainProblem ({ problemId, domainId }: { problemId: string, domainId: string }): Promise { const session = mongoClient.startSession() diff --git a/packages/api-server/src/services/testcase.services.ts b/packages/api-server/src/services/testcase.services.ts index b5a80598..f826f193 100644 --- a/packages/api-server/src/services/testcase.services.ts +++ b/packages/api-server/src/services/testcase.services.ts @@ -4,10 +4,9 @@ import { fetchDomainProblem, minio, uploadSessionCollection } from '@argoncs/com import path = require('node:path') import { nanoid } from 'nanoid' -export async function testcaseExists ({ problemId, domainId, filename, versionId }: { problemId: string, domainId: string, filename: string, versionId: string }): Promise { - const objectName = path.join(domainId, problemId, filename) +export async function testcaseExists ({ problemId, filename, versionId }: { problemId: string, filename: string, versionId: string }): Promise { try { - const stat = await minio.statObject('testcases', objectName, { versionId }) + const stat = await minio.statObject('testcases', path.join(problemId, filename), { versionId }) if (stat == null || stat.versionId !== versionId) { throw new NotFoundError('One of the testcases not found') @@ -17,12 +16,14 @@ export async function testcaseExists ({ problemId, domainId, filename, versionId } } +/* export async function createUploadSession ({ problemId, domainId }: { problemId: string, domainId: string }): Promise<{ uploadId: string }> { const id = nanoid(32) await fetchDomainProblem({ problemId, domainId }) // Could throw not found await uploadSessionCollection.insertOne({ id, problemId, domainId, createdAt: (new Date()).getTime() }) return { uploadId: id } } +*/ export async function createPolygonUploadSession ({ domainId }: { domainId: string }): Promise<{ uploadId: string }> { const id = nanoid(32) diff --git a/packages/judge-daemon/bits/stdc++.h b/packages/judge-daemon/bits/stdc++.h deleted file mode 100644 index b3a186db..00000000 --- a/packages/judge-daemon/bits/stdc++.h +++ /dev/null @@ -1,117 +0,0 @@ -// C++ includes used for precompiling -*- C++ -*- - -// Copyright (C) 2003-2015 Free Software Foundation, Inc. -// -// This file is part of the GNU ISO C++ Library. This library is free -// software; you can redistribute it and/or modify it under the -// terms of the GNU General Public License as published by the -// Free Software Foundation; either version 3, or (at your option) -// any later version. - -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// Under Section 7 of GPL version 3, you are granted additional -// permissions described in the GCC Runtime Library Exception, version -// 3.1, as published by the Free Software Foundation. - -// You should have received a copy of the GNU General Public License and -// a copy of the GCC Runtime Library Exception along with this program; -// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -// . - -/** @file stdc++.h - * This is an implementation file for a precompiled header. - */ - -// 17.4.1.2 Headers - -// C -#ifndef _GLIBCXX_NO_ASSERT -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if __cplusplus >= 201103L -#include -#include -#include -#include -#include -#include -#include -#include -#include -#endif - -// C++ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if __cplusplus >= 201103L -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#endif \ No newline at end of file diff --git a/packages/judge-daemon/src/services/compile.services.ts b/packages/judge-daemon/src/services/compile.services.ts index 6424dd8f..5a717398 100644 --- a/packages/judge-daemon/src/services/compile.services.ts +++ b/packages/judge-daemon/src/services/compile.services.ts @@ -3,7 +3,7 @@ import { promises as fs } from 'node:fs' import { runInSandbox } from './sandbox.services.js' -import { type CompilingTask, SandboxStatus, type CompileSucceeded, type CompileFailed, CompilingStatus, type CompilingCheckerTask } from '@argoncs/types' +import { type CompilingTask, SandboxStatus, type CompileSucceeded, type CompileFailed, CompilingStatus, type CompilingCheckerTask, CompilingCheckerResult } from '@argoncs/types' import { minio } from '@argoncs/common' import { languageConfigs } from '../../configs/language.configs.js' @@ -50,7 +50,7 @@ export async function compileSubmission ({ task, boxId }: { task: CompilingTask, } } -export async function compileChecker ({ task, boxId }: { task: CompilingCheckerTask, boxId: number }) +export async function compileChecker ({ task, boxId }: { task: CompilingCheckerTask, boxId: number }): Promise { const workDir = `/var/local/lib/isolate/${boxId}/box` const srcPath = path.join(workDir, 'checker.cpp') @@ -74,9 +74,29 @@ export async function compileChecker ({ task, boxId }: { task: CompilingCheckerT }) console.log('compiled checker') - if (result.status !== SandboxStatus.Succeeded) - throw `Checker compilation for problem ${task.problemId} failed` - await minio.fPutObject('checkers', task.problemId, binaryPath) + if (result.status !== SandboxStatus.Succeeded) + return { + status: CompilingStatus.Failed, + log: (await fs.readFile(logPath)).toString() + } + + const { versionId } = await minio.putObject('checkers', task.problemId, binaryPath) + + if (versionId === null) + return { + status: CompilingStatus.Failed, + log: 'failed to put into bucket' + } + console.log("checker put'ed") + + const log = (await fs.readFile(logPath)).toString() + return { + status: CompilingStatus.Succeeded, + checker: { + name: task.problemId, + versionId + } + } } diff --git a/packages/judge-daemon/src/services/grading.services.ts b/packages/judge-daemon/src/services/grading.services.ts index 9d557a5d..bfb485b3 100644 --- a/packages/judge-daemon/src/services/grading.services.ts +++ b/packages/judge-daemon/src/services/grading.services.ts @@ -25,10 +25,7 @@ export async function gradeSubmission ({ task, boxId }: { task: GradingTask, box await fetchTestcase({ objectName: task.testcase.input.objectName, versionId: task.testcase.input.versionId, destPath: inputPath }) await fetchTestcase({ objectName: task.testcase.output.objectName, versionId: task.testcase.output.versionId, destPath: answerPath }) await makeExecutable(path.join(workDir, config.binaryFile)) - await fetchChecker({ - objectName: task.checker.objectName, - versionId: task.checker.versionId, - destPath: checkerPath }) + await fetchChecker({ objectName: task.checker.objectName, versionId: task.checker.versionId, destPath: checkerPath }) let command = config.executeCommand command = command.replaceAll('{binary_path}', config.binaryFile) diff --git a/packages/judge-daemon/src/services/storage.services.ts b/packages/judge-daemon/src/services/storage.services.ts index c276f6c4..354ba103 100644 --- a/packages/judge-daemon/src/services/storage.services.ts +++ b/packages/judge-daemon/src/services/storage.services.ts @@ -77,22 +77,21 @@ export async function fetchBinary ({ objectName, destPath }: { objectName: strin async function cacheChecker ({ objectName, versionId }: { objectName: string, versionId: string }): Promise { // TODO: try catch. checker may not exist, throw something. try { - const key = path.join(cacheDir, 'checkers', objectName, versionId) + const key = path.join(cacheDir, 'checkers', objectName) const size = (await minio.statObject('checkers', objectName, { versionId })).size - // @ts-expect-error typing bug minio.fGetObject('checkers', objectName, key, { versionId }) cache.set(key, key, { size }) } catch (err) { console.log('cache checker error:', err) - throw "not found cache" + throw "none such checker" } } export async function fetchChecker ({ objectName, versionId, destPath }: { objectName: string, versionId: string, destPath: string }): Promise { - const key = path.join(cacheDir, 'checkers', objectName, versionId) + const key = path.join(cacheDir, 'checkers', objectName) if (cache.get(key) != null) { await fs.copyFile(key, destPath); return } @@ -102,7 +101,7 @@ export async function fetchChecker ({ objectName, versionId, destPath }: { objec await fs.copyFile(key, destPath) } } else { - downloading.set(key, cacheTestcase({ objectName, versionId })) + downloading.set(key, cacheChecker({ objectName, versionId })) await downloading.get(key) downloading.delete(key) await fs.copyFile(key, destPath) diff --git a/packages/judge-daemon/src/start.ts b/packages/judge-daemon/src/start.ts index 5470ccf8..79ae532e 100644 --- a/packages/judge-daemon/src/start.ts +++ b/packages/judge-daemon/src/start.ts @@ -2,7 +2,7 @@ import { destroySandbox, initSandbox } from './services/sandbox.services.js' import { gradeSubmission } from './services/grading.services.js' import { compileChecker, compileSubmission } from './services/compile.services.js' -import { type GradingTask, type CompilingTask, JudgerTaskType, type GradingResultMessage, JudgerResultType, type CompilingResultMessage, CompilingCheckerTask } from '@argoncs/types' +import { type GradingTask, type CompilingTask, JudgerTaskType, type GradingResultMessage, JudgerResultType, type CompilingResultMessage, CompilingCheckerTask, CompilingCheckerResultMessage } from '@argoncs/types' import { rabbitMQ, judgerTasksQueue, judgerExchange, judgerResultsKey, sentry, connectRabbitMQ, connectMinIO } from '@argoncs/common' import os = require('node:os') @@ -68,21 +68,26 @@ export async function startJudger (): Promise //await initSandbox({ boxId }) if (task.type === JudgerTaskType.CompilingChecker) { - compileChecker({ task, boxId }); + const result: CompilingCheckerResultMessage = { + type: JudgerResultType.CompilingChecker, + result: await compileChecker({ task, boxId }), + problemId: task.problemId, + } + rabbitMQ.publish(judgerExchange, judgerResultsKey, Buffer.from(JSON.stringify(result))) } else if (task.type === JudgerTaskType.Grading) { - const result = { + const result: GradingResultMessage = { type: JudgerResultType.Grading, - result: (await gradeSubmission({ task, boxId })), + result: await gradeSubmission({ task, boxId }), submissionId: task.submissionId, testcaseIndex: task.testcaseIndex } rabbitMQ.publish(judgerExchange, judgerResultsKey, Buffer.from(JSON.stringify(result))) } else if (task.type === JudgerTaskType.Compiling) { - const result = { + const result: CompilingResultMessage = { type: JudgerResultType.Compiling, - result: (await compileSubmission({ task, boxId })), + result: await compileSubmission({ task, boxId }), submissionId: task.submissionId } rabbitMQ.publish(judgerExchange, judgerResultsKey, Buffer.from(JSON.stringify(result))) diff --git a/packages/result-handler/src/services/result.services.ts b/packages/result-handler/src/services/result.services.ts index ae1b849a..9f195308 100644 --- a/packages/result-handler/src/services/result.services.ts +++ b/packages/result-handler/src/services/result.services.ts @@ -1,5 +1,5 @@ -import { fetchContestProblem, fetchDomainProblem, fetchSubmission, judgerExchange, judgerTasksKey, rabbitMQ, ranklistRedis, recalculateTeamTotalScore, submissionCollection, teamScoreCollection } from '@argoncs/common' -import { type CompilingResult, CompilingStatus, type GradingResult, GradingStatus, type GradingTask, JudgerTaskType, type Problem, SubmissionStatus } from '@argoncs/types' /*=*/ +import { fetchContestProblem, fetchDomainProblem, fetchSubmission, judgerExchange, judgerTasksKey, rabbitMQ, ranklistRedis, recalculateTeamTotalScore, submissionCollection, teamScoreCollection, domainProblemCollection } from '@argoncs/common' +import { type CompilingResult, CompilingStatus, type GradingResult, GradingStatus, type GradingTask, JudgerTaskType, type Problem, SubmissionStatus, type CompilingCheckerResult } from '@argoncs/types' /*=*/ import { NotFoundError } from 'http-errors-enhanced' import path from 'path' @@ -8,33 +8,43 @@ export async function handleCompileResult (compileResult: CompilingResult, submi if (submission.status === SubmissionStatus.Compiling) { if (compileResult.status === CompilingStatus.Succeeded) { - let problem: Problem - if (submission.contestId == null) { - const { problemId, domainId } = submission - problem = await fetchDomainProblem({ problemId, domainId }) - } else { - const { problemId, contestId } = submission - problem = await fetchContestProblem({ problemId, contestId }) - } - const submissionTestcases: Array<{ points: number, input: { name: string, versionId: string }, output: { name: string, versionId: string } }> = [] + const { problemId, domainId, contestId } = submission + let problem: Problem = contestId == null ? + await fetchDomainProblem({ problemId, domainId }) : + await fetchContestProblem({ problemId, contestId }) + if (problem.testcases == null) { - await completeGrading(submissionId, 'Problem does not have testcases'); return + await completeGrading(submissionId, 'Problem does not have testcases'); + return } + + if (problem.checker === null || problem.checker === undefined) { + await completeGrading(submissionId, 'Problem Checker does not exist or is not compiled'); + return + } + + const submissionTestcases: Array<{ points: number, input: { name: string, versionId: string }, output: { name: string, versionId: string } }> = [] + problem.testcases.forEach((testcase, index) => { const task: GradingTask = { constraints: problem.constraints, type: JudgerTaskType.Grading, submissionId, + problemId: problem.id, testcase: { input: { - objectName: path.join(problem.domainId, problem.id, testcase.input.name), + objectName: path.join(problem.id, testcase.input.name), versionId: testcase.input.versionId }, output: { - objectName: path.join(problem.domainId, problem.id, testcase.output.name), + objectName: path.join(problem.id, testcase.output.name), versionId: testcase.output.versionId } }, + checker: { + objectName: problem.checker!.name, + versionId: problem.checker!.versionId + }, testcaseIndex: index, language: submission.language } @@ -126,3 +136,15 @@ export async function handleGradingResult (gradingResult: GradingResult, submiss } } } + +export async function handleCompileCheckerResult (result: CompilingCheckerResult, problemId: string): Promise { + + if (result.status == CompilingStatus.Failed) { + // TODO: warn of broken checker + console.log('checker compilation failed') + return + } + + const checker = result.checker; + domainProblemCollection.updateOne({ problemId }, { $set: { checker }}); +} diff --git a/packages/result-handler/src/start.ts b/packages/result-handler/src/start.ts index de110f0e..c9624beb 100644 --- a/packages/result-handler/src/start.ts +++ b/packages/result-handler/src/start.ts @@ -1,7 +1,8 @@ import { connectMongoDB, connectRabbitMQ, connectRanklistRedis, deadResultsQueue, deadTasksQueue, judgerResultsQueue, rabbitMQ, sentry } from '@argoncs/common' -import { type CompilingResultMessage, type CompilingTask, type GradingResultMessage, type GradingTask, JudgerResultType } from '@argoncs/types' +import { type CompilingResultMessage, type CompilingTask, type GradingResultMessage, type GradingTask, JudgerResultType, CompilingCheckerResultMessage } from '@argoncs/types' import assert from 'assert' -import { completeGrading, handleCompileResult, handleGradingResult } from './services/result.services.js' +import { completeGrading, handleCompileCheckerResult, handleCompileResult, handleGradingResult } from './services/result.services.js' +/*=*/ sentry.init({ dsn: 'https://f56d872b49cc4981baf851fd569080cd@o1044666.ingest.sentry.io/450531102457856', @@ -11,10 +12,11 @@ sentry.init({ export async function startHandler (): Promise { assert(process.env.RABBITMQ_URL != null) - await connectRabbitMQ(process.env.RABBITMQ_URL) assert(process.env.MONGO_URL != null) - await connectMongoDB(process.env.MONGO_URL) assert(process.env.RANKLISTREDIS_URL != null) + + await connectRabbitMQ(process.env.RABBITMQ_URL) + await connectMongoDB(process.env.MONGO_URL) await connectRanklistRedis(process.env.RANKLISTREDIS_URL) // eslint-disable-next-line @typescript-eslint/no-misused-promises @@ -22,17 +24,23 @@ export async function startHandler (): Promise { console.log('judger result queue consumed'); if (message != null) { try { - const resultMessage: CompilingResultMessage | GradingResultMessage = JSON.parse(message.content.toString()) + const result: CompilingCheckerResultMessage | CompilingResultMessage | GradingResultMessage = + JSON.parse(message.content.toString()) - if (resultMessage.type === JudgerResultType.Compiling) { - await handleCompileResult(resultMessage.result, resultMessage.submissionId) - } else if (resultMessage.type === JudgerResultType.Grading) { - await handleGradingResult(resultMessage.result, resultMessage.submissionId, resultMessage.testcaseIndex) - } else { + if (result.type === JudgerResultType.Compiling) + await handleCompileResult(result.result, result.submissionId) + + else if (result.type === JudgerResultType.Grading) + await handleGradingResult(result.result, result.submissionId, result.testcaseIndex) + + else if (result.type === JudgerResultType.CompilingChecker) + await handleCompileCheckerResult(result.result, result.problemId) + + else throw Error('Invalid result type') - } rabbitMQ.ack(message) + } catch (err) { sentry.captureException(err) rabbitMQ.reject(message, false) @@ -45,9 +53,14 @@ export async function startHandler (): Promise { console.log('dead results queue consumed') if (message != null) { try { - const letter: CompilingResultMessage | GradingResultMessage = JSON.parse(message.content.toString()) + const result: CompilingCheckerResultMessage | CompilingResultMessage | GradingResultMessage = + JSON.parse(message.content.toString()) - await completeGrading(letter.submissionId, 'One or more of the grading results failed to be processed') + if (result.type === JudgerResultType.CompilingChecker) + // TODO: find a better way to notify this + console.log('checker compilation message failed to be acknowledged. weird') + else + await completeGrading(result.submissionId, 'One or more of the grading results failed to be processed') rabbitMQ.ack(message) } catch (err) { sentry.captureException(err) diff --git a/packages/types/src/types/compilation.types.ts b/packages/types/src/types/compilation.types.ts index 1f7d915d..cd63438e 100644 --- a/packages/types/src/types/compilation.types.ts +++ b/packages/types/src/types/compilation.types.ts @@ -56,3 +56,19 @@ export interface CompilingCheckerTask { source: string, problemId: string, } + +export const CompilingCheckerSucceededSchema = Type.Object({ + status: Type.Literal(CompilingStatus.Succeeded), + checker: Type.Object({ name: Type.String(), versionId: Type.String() }) +}) +export type CompilingChekcerSucceeded = Static + +// When using Type.Union, all children should not have addtionalProperties: false set to avoid an ajv issue +export const CompilingCheckerResultSchema = Type.Union([CompilingCheckerSucceededSchema, CompileFailedSchema]) +export type CompilingCheckerResult = Static + +export interface CompilingCheckerResultMessage { + type: JudgerResultType.CompilingChecker + problemId: string + result: CompilingCheckerResult +} diff --git a/packages/types/src/types/grading.types.ts b/packages/types/src/types/grading.types.ts index e394a7f0..ffafbfea 100644 --- a/packages/types/src/types/grading.types.ts +++ b/packages/types/src/types/grading.types.ts @@ -1,4 +1,4 @@ -import { type SubmissionLang } from './compilation.types.js' +import { type SubmissionLang } from './compilation.types.js' /*=*/ import { type Constraints, @@ -8,9 +8,9 @@ import { SandboxRuntimeErrorSchema, SandboxSystemErrorSchema, SandboxTimeExceededSchema -} from './judger.types.js' +} from './judger.types.js' /*=*/ -import { type Static, Type } from '@sinclair/typebox' +import { type Static, Type } from '@sinclair/typebox' /*=*/ export enum GradingStatus { Accepted = 'AC', @@ -20,6 +20,8 @@ export enum GradingStatus { export interface GradingTask { type: JudgerTaskType.Grading submissionId: string + problemId: string + checker: { objectName: string, versionId: string } testcase: { input: { objectName: string, versionId: string } output: { objectName: string, versionId: string } diff --git a/packages/types/src/types/problem.types.ts b/packages/types/src/types/problem.types.ts index 0e25ba0d..c00bc107 100644 --- a/packages/types/src/types/problem.types.ts +++ b/packages/types/src/types/problem.types.ts @@ -38,7 +38,7 @@ export const ProblemSchema = Type.Object({ points: Type.Number() })) ), - + checker: Type.Optional(Type.Object({ name: Type.String(), versionId: Type.String() })), id: Type.String(), domainId: Type.String() }) diff --git a/packages/upload-server/src/routes/testcase.routes.ts b/packages/upload-server/src/routes/testcase.routes.ts index 9c727efc..76ad73d5 100644 --- a/packages/upload-server/src/routes/testcase.routes.ts +++ b/packages/upload-server/src/routes/testcase.routes.ts @@ -1,10 +1,9 @@ import { consumeUploadSession, uploadTestcase } from '../services/testcase.services.js' -import { uploadPolygon } from '../services/polygon.services.js' import { Type } from '@sinclair/typebox' import multipart from '@fastify/multipart' import { type FastifyTypeBox } from '../types.js' -import { BadRequestError, badRequestSchema, PayloadTooLargeError, unauthorizedSchema } from 'http-errors-enhanced' +import { BadRequestError, badRequestSchema, PayloadTooLargeError, unauthorizedSchema } from 'http-errors-enhanced' /*=*/ export async function testcaseRoutes (routes: FastifyTypeBox): Promise { await routes.register(multipart.default, { @@ -38,7 +37,11 @@ export async function testcaseRoutes (routes: FastifyTypeBox): Promise { const testcases = request.files() const queue: Array> = [] for await (const testcase of testcases) { - queue.push(uploadTestcase(domainId, problemId, testcase)) + queue.push(uploadTestcase({ + problemId, + filename: testcase.filename.replaceAll('/', '.'), + stream: testcase.file + })) } return await reply.status(201).send(await Promise.all(queue)) } catch (err) { diff --git a/packages/upload-server/src/services/polygon.services.ts b/packages/upload-server/src/services/polygon.services.ts index 9ca92ec8..f6a91c63 100644 --- a/packages/upload-server/src/services/polygon.services.ts +++ b/packages/upload-server/src/services/polygon.services.ts @@ -1,6 +1,6 @@ -import { uploadFile } from './testcase.services.js' +import { uploadTestcase } from './testcase.services.js' import { nanoid } from 'nanoid' -import { CheckerCompileTask, JudgerTaskType, Problem, type Constraints, type NewProblem } from '@argoncs/types' /*=*/ +import { CompilingCheckerTask, JudgerTaskType, Problem, type Constraints, type NewProblem } from '@argoncs/types' /*=*/ import { type MultipartFile } from '@fastify/multipart' /*=*/ import { exec as exec_sync } from 'node:child_process' import { promisify } from 'node:util' @@ -70,8 +70,8 @@ export async function uploadPolygon ({ domainId, archive }: { domainId: string, const input_file = await fs.open(path.join(work_path, 'tests', name)) const output_file = await fs.open(path.join(work_path, 'tests', name + '.a')) - const input = await uploadFile(domainId, problem.id, name, input_file.createReadStream()) - const output = await uploadFile(domainId, problem.id, name + '-ans', output_file.createReadStream()) + const input = await uploadTestcase({problemId:problem.id, filename: name, stream: input_file.createReadStream()}) + const output = await uploadTestcase({problemId: problem.id, filename: name + '-ans', stream: output_file.createReadStream()}) problem.testcases.push({ input, @@ -90,7 +90,7 @@ export async function uploadPolygon ({ domainId, archive }: { domainId: string, // Compile checker const checkerSource = (await fs.readFile(path.join(work_path, 'checker.cpp'))).toString() - const compileTask: CheckerCompileTask = { + const compileTask: CompilingCheckerTask = { type: JudgerTaskType.CompilingChecker, source: checkerSource, problemId: problem.id diff --git a/packages/upload-server/src/services/testcase.services.ts b/packages/upload-server/src/services/testcase.services.ts index ced5a95a..d0dd0afc 100644 --- a/packages/upload-server/src/services/testcase.services.ts +++ b/packages/upload-server/src/services/testcase.services.ts @@ -1,27 +1,17 @@ import { minio, uploadSessionCollection } from '@argoncs/common' -import { type MultipartFile } from '@fastify/multipart' +import { type MultipartFile } from '@fastify/multipart' /*=*/ import { BadRequestError, UnauthorizedError } from 'http-errors-enhanced' import path from 'node:path' import type internal from 'node:stream' -/* Uploads a MultipartFile to testcases. Warps 'uploadFile(..)' */ -export async function uploadTestcase ( - domainId: string, - problemId: string, - testcase: MultipartFile -): Promise<{ versionId: string, name: string }> { - const filename = testcase.filename.replaceAll('/', '.') - return await uploadFile(domainId, problemId, filename, testcase.file) -} /* Uploads a single file into 'testcases' */ -export async function uploadFile ( - domainId: string, +export async function uploadTestcase ({ problemId, filename, stream }:{ problemId: string, filename: string, stream: internal.Readable -): Promise<{ versionId: string, name: string }> { - const objectName = path.join(domainId, problemId, filename) +}): Promise<{ versionId: string, name: string }> { + const objectName = path.join(problemId, filename) const { versionId } = await minio.putObject('testcases', objectName, stream) if (versionId == null) { throw Error('Versioning not enabled on testcases bucket') diff --git a/packages/upload-server/src/start.ts b/packages/upload-server/src/start.ts index 2ad50edf..914a9c70 100644 --- a/packages/upload-server/src/start.ts +++ b/packages/upload-server/src/start.ts @@ -4,7 +4,7 @@ import fastifyHttpErrorsEnhanced from '@chenhongqiao/fastify-http-errors-enhance import fastifyAuth from '@fastify/auth' import fastifyCors from '@fastify/cors' -import { testcaseRoutes } from './routes/testcase.routes.js' +//import { testcaseRoutes } from './routes/testcase.routes.js' import { heartbeatRoutes } from './routes/heartbeat.routes.js' import { connectMinIO, connectMongoDB, sentry } from '@argoncs/common' @@ -27,8 +27,9 @@ sentry.init({ export async function startUploadServer (): Promise { assert(process.env.MINIO_URL != null) - await connectMinIO(process.env.MINIO_URL) assert(process.env.MONGO_URL != null) + + await connectMinIO(process.env.MINIO_URL) await connectMongoDB(process.env.MONGO_URL) await app.register(fastifyHttpErrorsEnhanced, { @@ -49,9 +50,9 @@ export async function startUploadServer (): Promise { credentials: true }) - await app.register(polygonRoutes, { prefix: '/polygon' }) - await app.register(testcaseRoutes, { prefix: '/testcases' }) await app.register(heartbeatRoutes, { prefix: '/heartbeat' }) + await app.register(polygonRoutes, { prefix: '/polygon' }) + //await app.register(testcaseRoutes, { prefix: '/testcases' }) try { const port: number = parseInt(process.env.UPLOAD_SERVER_PORT ?? '8001')