Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions features/get_submission.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
"""
4 changes: 3 additions & 1 deletion schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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`);

--
Expand Down Expand Up @@ -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`
Expand Down
9 changes: 8 additions & 1 deletion src/platform_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlatformTaskTokenData> {
export async function extractPlatformTaskTokenData(token: string|null|undefined, platformName: string|null|undefined, taskId: string|(() => Promise<string|null>)|null = null): Promise<PlatformTaskTokenData> {
const platformEntity = await getPlatformByName(platformName);
let payload: PlatformTaskTokenPayload;
try {
payload = await decodePlatformTaskToken<PlatformTaskTokenPayload>(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();

Expand Down
22 changes: 21 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {getTask, taskQueryDecoder, TaskQueryParameters} from './tasks';
import {
createOfflineSubmission,
createSubmission,
getSubmission, offlineSubmissionDataDecoder, OfflineSubmissionParameters,
getSubmission,
getSubmissionByUserAnswer,
offlineSubmissionDataDecoder,
OfflineSubmissionParameters,
submissionDataDecoder,
SubmissionParameters,
submissionQueryDecoder,
Expand Down Expand Up @@ -132,6 +135,23 @@ export async function init(): Promise<Server> {
}
});

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',
Expand Down
38 changes: 26 additions & 12 deletions src/submissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@ export async function findSubmissionById(submissionId: string): Promise<Submissi
return await Db.querySingleResult<Submission>('SELECT * FROM tm_submissions WHERE ID = ?', [submissionId]);
}

export async function findSubmissionByUserAnswerId(userAnswerId: string): Promise<Submission|null> {
return await Db.querySingleResult<Submission>('SELECT * FROM tm_submissions WHERE idUserAnswer = ? ORDER BY ID DESC LIMIT 1', [userAnswerId]);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The db schema indicates idUserAnswer is not unique, so technically you may still have cases (even related when manual tests) where you have twice the same "idUserAnswer" in the table. If so, the fact that you have no ordering, make this function not really deterministic. An "order by" would at least make sure the output of this function is more predictable.


function normalizeSubmission(submission: Submission): SubmissionNormalized {
return {
id: submission.ID,
Expand Down Expand Up @@ -348,13 +352,29 @@ export function normalizeSourceCode(sourceCode: SourceCode): SourceCodeNormalize
};
}

export async function getSubmissionByUserAnswer(userAnswerId: string, submissionQueryParameters: SubmissionQueryParameters): Promise<SubmissionOutput|null> {
return await exportSubmission(submissionQueryParameters, () => findSubmissionByUserAnswerId(userAnswerId));
}

export async function getSubmission(submissionId: string, submissionQueryParameters: SubmissionQueryParameters): Promise<SubmissionOutput|null> {
const submission = await findSubmissionById(submissionId);
return await exportSubmission(submissionQueryParameters, () => findSubmissionById(submissionId));
}

async function exportSubmission(submissionQueryParameters: SubmissionQueryParameters, getSubmission: () => Promise<Submission|null>) {
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);

Expand All @@ -365,9 +385,9 @@ export async function getSubmission(submissionId: string, submissionQueryParamet
};
}

const submissionSubtasks = await Db.execute<SubmissionSubtask[]>('SELECT * FROM tm_submissions_subtasks WHERE idSubmission = ?', [submissionId]);
const submissionTestResults = await Db.execute<SubmissionTest[]>('SELECT * FROM tm_submissions_tests WHERE idSubmission = ?', [submissionId]);
const submissionTests = await Db.execute<TaskTest[]>(`SELECT * FROM tm_tasks_tests WHERE (idSubmission = ? AND sGroupType = "User")${submissionQueryParameters.withTests ? " OR (idTask = ?)" : ''}`, [submissionId, ...(submissionQueryParameters.withTests ? [submission.idTask] : [])]);
const submissionSubtasks = await Db.execute<SubmissionSubtask[]>('SELECT * FROM tm_submissions_subtasks WHERE idSubmission = ?', [submission.ID]);
const submissionTestResults = await Db.execute<SubmissionTest[]>('SELECT * FROM tm_submissions_tests WHERE idSubmission = ?', [submission.ID]);
const submissionTests = await Db.execute<TaskTest[]>(`SELECT * FROM tm_tasks_tests WHERE (idSubmission = ? AND sGroupType = "User")${submissionQueryParameters.withTests ? " OR (idTask = ?)" : ''}`, [submission.ID, ...(submissionQueryParameters.withTests ? [submission.idTask] : [])]);

return {
...normalizeSubmission(submission),
Expand All @@ -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}`);
Expand Down
Loading