From 965e96690ce936bd38f09b5b1cb2ccee7d3b9c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Tainon?= Date: Fri, 29 May 2026 11:08:00 +0200 Subject: [PATCH 1/2] Add GET /submissions/user-answer/{userAnswerId} --- features/get_submission.feature | 42 +++++++++++++++++++++++++++++++++ src/platform_interface.ts | 9 ++++++- src/server.ts | 22 ++++++++++++++++- src/submissions.ts | 38 +++++++++++++++++++---------- 4 files changed, 97 insertions(+), 14 deletions(-) diff --git a/features/get_submission.feature b/features/get_submission.feature index 7fccfd7..a43b685 100644 --- a/features/get_submission.feature +++ b/features/get_submission.feature @@ -543,4 +543,46 @@ Feature: Get submission "error": "Access denied.", "message": "Error: User answer id mismatch between submission data and provided idUserAnswer from the token: 999999" } + """ + + Scenario: Get submission by user answer id + Given the database has the following table "tm_submissions": + | ID | idUser | idPlatform | idTask | sDate | idSourceCode | bManualCorrection | bSuccess | nbTestsTotal | nbTestsPassed | iScore | bCompilError | bEvaluated | bConfirmed | sMode | idUserAnswer | iChecksum | iVersion | + | 6000 | 1 | 1 | 1000 | 2023-04-03 | 7001 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Submitted | 1 | 0 | 2147483647 | + And the database has the following table "tm_source_codes": + | ID | idUser | idPlatform | idTask | sDate | sParams | sName | sSource | bEditable | bSubmission | sType | bActive | iRank | iVersion | + | 7001 | 1 | 1 | 1000 | 2023-04-03 | {"sLangProg":"python"} | 485380303499640413 | print("ici") | 0 | 1 | User | 0 | 0 | 2147483647 | + When I send a GET request to "/submissions/user-answer/1?token={{taskToken}}&platform=codecast-test" + Then the response status code should be 200 + And the response body should be the following JSON: + """ + { + "id": "6000", + "success": false, + "totalTestsCount": 0, + "passedTestsCount": 0, + "score": 0, + "compilationError": false, + "compilationMessage": null, + "date": "2023-04-03T00:00:00.000Z", + "errorMessage": null, + "evaluated": false, + "confirmed": false, + "manualCorrection": false, + "manualScoreDiffComment": null, + "metadata": null, + "sourceCode": { + "id": "7001", + "name": "485380303499640413", + "source": "print(\"ici\")", + "type": "User", + "params": { + "sLangProg": "python" + }, + "rank": 0, + "active": false, + "editable": false + }, + "mode": "Submitted" + } """ \ No newline at end of file diff --git a/src/platform_interface.ts b/src/platform_interface.ts index be2042f..9117bee 100644 --- a/src/platform_interface.ts +++ b/src/platform_interface.ts @@ -26,13 +26,20 @@ export interface PlatformAnswerTokenData { platform: Platform, } -export async function extractPlatformTaskTokenData(token: string|null|undefined, platformName: string|null|undefined, taskId: string|null = null): Promise { +export async function extractPlatformTaskTokenData(token: string|null|undefined, platformName: string|null|undefined, taskId: string|(() => Promise)|null = null): Promise { const platformEntity = await getPlatformByName(platformName); let payload: PlatformTaskTokenPayload; try { payload = await decodePlatformTaskToken(token, platformEntity); } catch (e) { if (appConfig.testMode.enabled && null !== taskId) { + if (typeof taskId === 'function') { + taskId = await taskId(); + if (!taskId) { + throw e; + } + } + payload = getTestTokenParameters(taskId); const jwesDecoder = new JwesDecoder(); diff --git a/src/server.ts b/src/server.ts index e0ae3f9..65acaa3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,7 +4,10 @@ import {getTask, taskQueryDecoder, TaskQueryParameters} from './tasks'; import { createOfflineSubmission, createSubmission, - getSubmission, offlineSubmissionDataDecoder, OfflineSubmissionParameters, + getSubmission, + getSubmissionByUserAnswer, + offlineSubmissionDataDecoder, + OfflineSubmissionParameters, submissionDataDecoder, SubmissionParameters, submissionQueryDecoder, @@ -132,6 +135,23 @@ export async function init(): Promise { } }); + server.route({ + method: 'GET', + path: '/submissions/user-answer/{userAnswerId}', + options: { + handler: async (request, h) => { + const submissionQueryParameters: SubmissionQueryParameters = decode(submissionQueryDecoder)(request.query); + + let submissionData = await getSubmissionByUserAnswer(String(request.params.userAnswerId), submissionQueryParameters); + if (null === submissionData) { + throw new NotFoundError(`Submission not found with this user answer id: ${String(request.params.userAnswerId)}`); + } + + return h.response(submissionData); + } + } + }); + server.route({ method: 'POST', path: '/remote-execution', diff --git a/src/submissions.ts b/src/submissions.ts index 63ff783..07f77bd 100644 --- a/src/submissions.ts +++ b/src/submissions.ts @@ -284,6 +284,10 @@ export async function findSubmissionById(submissionId: string): Promise('SELECT * FROM tm_submissions WHERE ID = ?', [submissionId]); } +export async function findSubmissionByUserAnswerId(userAnswerId: string): Promise { + return await Db.querySingleResult('SELECT * FROM tm_submissions WHERE idUserAnswer = ?', [userAnswerId]); +} + function normalizeSubmission(submission: Submission): SubmissionNormalized { return { id: submission.ID, @@ -348,13 +352,29 @@ export function normalizeSourceCode(sourceCode: SourceCode): SourceCodeNormalize }; } +export async function getSubmissionByUserAnswer(userAnswerId: string, submissionQueryParameters: SubmissionQueryParameters): Promise { + return await exportSubmission(submissionQueryParameters, () => findSubmissionByUserAnswerId(userAnswerId)); +} + export async function getSubmission(submissionId: string, submissionQueryParameters: SubmissionQueryParameters): Promise { - const submission = await findSubmissionById(submissionId); + return await exportSubmission(submissionQueryParameters, () => findSubmissionById(submissionId)); +} + +async function exportSubmission(submissionQueryParameters: SubmissionQueryParameters, getSubmission: () => Promise) { + if (!appConfig.testMode.enabled && (!submissionQueryParameters.token || !submissionQueryParameters.platform)) { + throw new InvalidInputError('Missing token or platform parameters'); + } + + const taskTokenData = await extractPlatformTaskTokenData(submissionQueryParameters.token, submissionQueryParameters.platform, async () => { + return (await getSubmission())?.idTask || null; + }); + + const submission = await getSubmission(); if (null === submission) { return null; } - await checkAuthorizedToGetSubmission(submissionQueryParameters, submission); + await checkAuthorizedToGetSubmission(taskTokenData, submission); const sourceCode = await findSourceCodeById(submission.idSourceCode); @@ -365,9 +385,9 @@ export async function getSubmission(submissionId: string, submissionQueryParamet }; } - const submissionSubtasks = await Db.execute('SELECT * FROM tm_submissions_subtasks WHERE idSubmission = ?', [submissionId]); - const submissionTestResults = await Db.execute('SELECT * FROM tm_submissions_tests WHERE idSubmission = ?', [submissionId]); - const submissionTests = await Db.execute(`SELECT * FROM tm_tasks_tests WHERE (idSubmission = ? AND sGroupType = "User")${submissionQueryParameters.withTests ? " OR (idTask = ?)" : ''}`, [submissionId, ...(submissionQueryParameters.withTests ? [submission.idTask] : [])]); + const submissionSubtasks = await Db.execute('SELECT * FROM tm_submissions_subtasks WHERE idSubmission = ?', [submission.ID]); + const submissionTestResults = await Db.execute('SELECT * FROM tm_submissions_tests WHERE idSubmission = ?', [submission.ID]); + const submissionTests = await Db.execute(`SELECT * FROM tm_tasks_tests WHERE (idSubmission = ? AND sGroupType = "User")${submissionQueryParameters.withTests ? " OR (idTask = ?)" : ''}`, [submission.ID, ...(submissionQueryParameters.withTests ? [submission.idTask] : [])]); return { ...normalizeSubmission(submission), @@ -378,13 +398,7 @@ export async function getSubmission(submissionId: string, submissionQueryParamet }; } -async function checkAuthorizedToGetSubmission(submissionQueryParameters: SubmissionQueryParameters, submission: Submission) { - if (!appConfig.testMode.enabled && (!submissionQueryParameters.token || !submissionQueryParameters.platform)) { - throw new InvalidInputError('Missing token or platform parameters'); - } - - const taskTokenData = await extractPlatformTaskTokenData(submissionQueryParameters.token, submissionQueryParameters.platform, submission.idTask); - +async function checkAuthorizedToGetSubmission(taskTokenData: PlatformTaskTokenData, submission: Submission) { // Check task token data match submission data if (submission.idTask !== taskTokenData.taskId) { throw new AccessDeniedError(`Task id mismatch between submission data and provided task id from the token: ${taskTokenData.taskId}`); From 004bfd284286bfdc073622be6fe28d0488d8fee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Tainon?= Date: Fri, 12 Jun 2026 10:01:42 +0200 Subject: [PATCH 2/2] Add index and ordering on idUserAnswer --- schema.sql | 4 +++- src/submissions.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/schema.sql b/schema.sql index c4b5ad6..aea7212 100644 --- a/schema.sql +++ b/schema.sql @@ -1220,6 +1220,7 @@ ALTER TABLE `history_tm_submissions` ADD KEY `user` (`idUser`,`idPlatform`), ADD KEY `idTask` (`idTask`), ADD KEY `userTask` (`idTask`,`idUser`,`idPlatform`), + ADD KEY `idUserAnswer` (`idUserAnswer`), ADD KEY `idSourceCode` (`idSourceCode`); -- @@ -1412,7 +1413,8 @@ ALTER TABLE `tm_submissions` ADD KEY `idUser` (`idUser`,`idPlatform`), ADD KEY `idTask` (`idTask`), ADD KEY `userTask` (`idTask`,`idUser`,`idPlatform`), - ADD KEY `idSourceCode` (`idSourceCode`); + ADD KEY `idSourceCode` (`idSourceCode`), + ADD KEY `idUserAnswer` (`idUserAnswer`); -- -- Indexes for table `tm_submissions_subtasks` diff --git a/src/submissions.ts b/src/submissions.ts index 07f77bd..7da090f 100644 --- a/src/submissions.ts +++ b/src/submissions.ts @@ -285,7 +285,7 @@ export async function findSubmissionById(submissionId: string): Promise { - return await Db.querySingleResult('SELECT * FROM tm_submissions WHERE idUserAnswer = ?', [userAnswerId]); + return await Db.querySingleResult('SELECT * FROM tm_submissions WHERE idUserAnswer = ? ORDER BY ID DESC LIMIT 1', [userAnswerId]); } function normalizeSubmission(submission: Submission): SubmissionNormalized {