From b9ed6e9c29c0b1d19cc414bef174610bdae2438a Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:27:56 +0900 Subject: [PATCH] =?UTF-8?q?feat(backend)!:=20=E5=95=8F=E9=A1=8C=E3=81=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=87=A6=E7=90=86=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration.sql | 12 ++ backend/prisma/schema.prisma | 4 + backend/src/api/components/schemas.ts | 45 +++++- backend/src/api/paths/problems.ts | 135 +++++++++++------- backend/src/services/program/run.ts | 4 +- backend/src/services/program/test.ts | 4 +- 6 files changed, 144 insertions(+), 60 deletions(-) create mode 100644 backend/prisma/migrations/20241104023951_set_unique_fields/migration.sql diff --git a/backend/prisma/migrations/20241104023951_set_unique_fields/migration.sql b/backend/prisma/migrations/20241104023951_set_unique_fields/migration.sql new file mode 100644 index 0000000..60571a0 --- /dev/null +++ b/backend/prisma/migrations/20241104023951_set_unique_fields/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - A unique constraint covering the columns `[languageName,languageVersion,problemId]` on the table `Language` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[input,output,problemId]` on the table `TestCase` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Language_languageName_languageVersion_problemId_key" ON "Language"("languageName", "languageVersion", "problemId"); + +-- CreateIndex +CREATE UNIQUE INDEX "TestCase_input_output_problemId_key" ON "TestCase"("input", "output", "problemId"); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index d9a7de0..d1e5d05 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -49,6 +49,8 @@ model TestCase { results TestResult[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + + @@unique([input, output, problemId]) } model TestResult { @@ -89,6 +91,8 @@ model Language { problemId Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + + @@unique([languageName, languageVersion, problemId]) } model Submission { diff --git a/backend/src/api/components/schemas.ts b/backend/src/api/components/schemas.ts index 1c78c58..6ccca2b 100644 --- a/backend/src/api/components/schemas.ts +++ b/backend/src/api/components/schemas.ts @@ -1,19 +1,43 @@ import { z } from "@hono/zod-openapi" +export const SupportedLanguage = z + .object({ + name: z.string(), + version: z.string(), + }) + .openapi("SupportedLanguage") + export const Language = z .object({ + id: z.number().int().nonnegative(), name: z.string(), version: z.string(), }) .openapi("Language") +export const LanguageCreate = Language.omit({ id: true }).openapi( + "LanguageCreate", +) + +export const LanguageUpdate = Language.openapi("LanguageUpdate") + export const TestCase = z .object({ + id: z.number().int().nonnegative(), input: z.string(), output: z.string(), }) .openapi("TestCase") +export const TestCaseCreate = TestCase.omit({ id: true }).openapi( + "TestCaseCreate", +) + +export const TestCaseUpdate = TestCase.partial({ + input: true, + output: true, +}).openapi("TestCaseUpdate") + export const Problem = z .object({ body: z.string(), @@ -24,10 +48,23 @@ export const Problem = z }) .openapi("Problem") -export const ProblemCreate = Problem.omit({ id: true }).openapi("ProblemCreate") +export const ProblemCreate = Problem.omit({ id: true }) + .merge( + z.object({ + supported_languages: z.array(LanguageCreate), + test_cases: z.array(TestCaseCreate), + } satisfies Partial, unknown>>), + ) + .openapi("ProblemCreate") -export const ProblemUpdate = Problem.partial() - .omit({ id: true }) +export const ProblemUpdate = Problem.omit({ id: true }) + .merge( + z.object({ + supported_languages: z.array(LanguageUpdate), + test_cases: z.array(TestCaseUpdate), + } satisfies Partial, unknown>>), + ) + .partial() .openapi("ProblemUpdate") export const SubmissionStatus = z @@ -55,7 +92,7 @@ export const Submission = z .object({ code: z.string(), id: z.number().int().nonnegative(), - language: Language, + language: SupportedLanguage, problem_id: z.number().int().nonnegative(), result: SubmissionResult, student_id: z.number().int().nonnegative(), diff --git a/backend/src/api/paths/problems.ts b/backend/src/api/paths/problems.ts index 9179512..635e991 100644 --- a/backend/src/api/paths/problems.ts +++ b/backend/src/api/paths/problems.ts @@ -239,19 +239,26 @@ app.openapi(getProblemsRoute, async (c) => { }, }) - const formattedProblems = problems.map((problem) => ({ - body: problem.body, - id: problem.id, - supported_languages: problem.supportedLanguages.map((supportedLang) => ({ - name: supportedLang.language.name, - version: supportedLang.language.version, - })), - test_cases: problem.testCases.map((testCase) => ({ - input: testCase.input, - output: testCase.output, - })), - title: problem.title, - })) + const formattedProblems = problems.map( + (problem) => + ({ + body: problem.body, + id: problem.id, + supported_languages: problem.supportedLanguages.map( + ({ id, language }) => ({ + id: id, + name: language.name, + version: language.version, + }), + ), + test_cases: problem.testCases.map(({ id, input, output }) => ({ + id, + input, + output, + })), + title: problem.title, + }) satisfies z.infer, + ) return c.json(formattedProblems, 200) }) @@ -265,11 +272,8 @@ app.openapi(createProblemRoute, async (c) => { create: data.supported_languages.map( (lang: { name: string; version: string }) => ({ language: { - connectOrCreate: { - create: { name: lang.name, version: lang.version }, - where: { - name_version: { name: lang.name, version: lang.version }, - }, + connect: { + name_version: { name: lang.name, version: lang.version }, }, }, }), @@ -299,15 +303,17 @@ app.openapi(createProblemRoute, async (c) => { body: createdProblem.body, id: createdProblem.id, supported_languages: createdProblem.supportedLanguages.map((lang) => ({ + id: lang.id, name: lang.language.name, version: lang.language.version, })), test_cases: createdProblem.testCases.map((testCase) => ({ + id: testCase.id, input: testCase.input, output: testCase.output, })), title: createdProblem.title, - } + } satisfies z.infer return c.json(formattedProblem, 201) }) @@ -330,17 +336,19 @@ app.openapi(getProblemRoute, async (c) => { body: problem.body, id: problem.id, supported_languages: problem.supportedLanguages.map( - ({ languageName, languageVersion }) => ({ - name: languageName, - version: languageVersion, + ({ id, language }) => ({ + id, + name: language.name, + version: language.version, }), ), - test_cases: problem.testCases.map(({ input, output }) => ({ + test_cases: problem.testCases.map(({ id, input, output }) => ({ + id, input, output, })), title: problem.title, - }, + } satisfies z.infer, 200, ) }) @@ -352,26 +360,44 @@ app.openapi(updateProblemRoute, async (c) => { data: { body: data.body, supportedLanguages: { - create: data.supported_languages?.map( - (lang: { name: string; version: string }) => ({ + upsert: data.supported_languages?.map((lang) => ({ + create: { language: { - connectOrCreate: { - create: { name: lang.name, version: lang.version }, - where: { - name_version: { name: lang.name, version: lang.version }, + connect: { + name_version: { + name: lang.name, + version: lang.version, }, }, }, - }), - ), + }, + update: { + language: { + connect: { + name_version: { + name: lang.name, + version: lang.version, + }, + }, + }, + }, + where: { + id: lang.id, + problemId, + }, + })), }, testCases: { - create: data.test_cases?.map( - (testCase: { input: string; output: string }) => ({ + update: data.test_cases?.map((testCase) => ({ + data: { input: testCase.input, output: testCase.output, - }), - ), + }, + where: { + id: testCase.id, + problemId, + }, + })), }, title: data.title, }, @@ -383,23 +409,28 @@ app.openapi(updateProblemRoute, async (c) => { }, testCases: true, }, - where: { - id: problemId, - }, + where: { id: problemId }, }) - return c.json({ - body: updatedProblem.body, - id: updatedProblem.id, - supported_languages: updatedProblem.supportedLanguages.map((lang) => ({ - name: lang.language.name, - version: lang.language.version, - })), - test_cases: updatedProblem.testCases.map((testCase) => ({ - input: testCase.input, - output: testCase.output, - })), - title: updatedProblem.title, - } satisfies z.infer) + return c.json( + { + body: updatedProblem.body, + id: updatedProblem.id, + supported_languages: updatedProblem.supportedLanguages.map( + ({ id, language }) => ({ + id, + name: language.name, + version: language.version, + }), + ), + test_cases: updatedProblem.testCases.map(({ id, input, output }) => ({ + id, + input, + output, + })), + title: updatedProblem.title, + } satisfies z.infer, + 200, + ) }) app.openapi(deleteProblemRoute, async (c) => { diff --git a/backend/src/services/program/run.ts b/backend/src/services/program/run.ts index 80d5862..8f01767 100644 --- a/backend/src/services/program/run.ts +++ b/backend/src/services/program/run.ts @@ -3,7 +3,7 @@ import { z } from "@hono/zod-openapi" import * as schemas from "../../api/components/schemas" import { runPythonCode } from "./languages" -type Language = z.infer +type SupportedLanguage = z.infer export const run = ({ code, @@ -12,7 +12,7 @@ export const run = ({ }: { code: string input: string - language: Language + language: SupportedLanguage }) => { switch (language.name) { case "Python": { diff --git a/backend/src/services/program/test.ts b/backend/src/services/program/test.ts index d2e8482..34189f2 100644 --- a/backend/src/services/program/test.ts +++ b/backend/src/services/program/test.ts @@ -3,7 +3,7 @@ import { z } from "@hono/zod-openapi" import * as schemas from "../../api/components/schemas" import { run } from "./run" -type Language = z.infer +type SupportedLanguage = z.infer type TestResult = Omit, "test_case_id"> @@ -15,7 +15,7 @@ export const test = async ({ }: { code: string input: string - language: Language + language: SupportedLanguage output: string }): Promise => { const result = await run({ code, input, language })