Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • Dojo_Project_Nguyen/backend/dojobackendapi
  • dojo_project/projects/backend/dojobackendapi
2 results
Select Git revision
Show changes
Commits on Source (5)
......@@ -28,6 +28,7 @@
"mysql": "^2.18.1",
"node": "^20.5.0",
"parse-link-header": "^2.0.0",
"tar-stream": "^3.1.6",
"uuid": "^9.0.0",
"winston": "^3.8.2"
},
......@@ -41,6 +42,7 @@
"@types/multer": "^1.4.7",
"@types/node": "^20.4.7",
"@types/parse-link-header": "^2.0.1",
"@types/tar-stream": "^2.2.2",
"@types/uuid": "^9.0.2",
"nodemon": "^3.0.1",
"npm": "^9.8.1",
......@@ -304,6 +306,15 @@
"@types/node": "*"
}
},
"node_modules/@types/tar-stream": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.2.tgz",
"integrity": "sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/triple-beam": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz",
......@@ -418,6 +429,11 @@
"proxy-from-env": "^1.1.0"
}
},
"node_modules/b4a": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz",
"integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
......@@ -918,6 +934,11 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-fifo": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.0.tgz",
"integrity": "sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw=="
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
......@@ -4942,6 +4963,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
......@@ -5196,6 +5222,15 @@
"node": ">=10.0.0"
}
},
"node_modules/streamx": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz",
"integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==",
"dependencies": {
"fast-fifo": "^1.1.0",
"queue-tick": "^1.0.1"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
......@@ -5232,6 +5267,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tar-stream": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz",
"integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/tarn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
......
......@@ -39,6 +39,7 @@
"mysql" : "^2.18.1",
"node" : "^20.5.0",
"parse-link-header": "^2.0.0",
"tar-stream" : "^3.1.6",
"uuid" : "^9.0.0",
"winston" : "^3.8.2"
},
......@@ -52,6 +53,7 @@
"@types/multer" : "^1.4.7",
"@types/node" : "^20.4.7",
"@types/parse-link-header": "^2.0.1",
"@types/tar-stream" : "^2.2.2",
"@types/uuid" : "^9.0.2",
"nodemon" : "^3.0.1",
"prisma" : "^5.1.1",
......
/*
Warnings:
- You are about to drop the column `details` on the `Result` table. All the data in the column will be lost.
- You are about to drop the column `pass` on the `Result` table. All the data in the column will be lost.
- Added the required column `commit` to the `Result` table without a default value. This is not possible if the table is not empty.
- Added the required column `exitCode` to the `Result` table without a default value. This is not possible if the table is not empty.
- Added the required column `files` to the `Result` table without a default value. This is not possible if the table is not empty.
- Added the required column `results` to the `Result` table without a default value. This is not possible if the table is not empty.
- Added the required column `success` to the `Result` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `Result` DROP COLUMN `details`,
DROP COLUMN `pass`,
ADD COLUMN `commit` JSON NOT NULL,
ADD COLUMN `exitCode` INTEGER NOT NULL,
ADD COLUMN `files` JSON NOT NULL,
ADD COLUMN `results` JSON NOT NULL,
ADD COLUMN `success` BOOLEAN NOT NULL;
......@@ -54,8 +54,11 @@ model Exercice {
model Result {
exerciceId String @db.Char(36)
dateTime DateTime @default(now())
pass Boolean
details String @db.Text
success Boolean
exitCode Int
commit Json @db.Json
results Json @db.Json
files Json @db.Json
exercice Exercice @relation(fields: [exerciceId], references: [id], onDelete: Cascade, onUpdate: Cascade)
......
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
import { Exercice } from '../types/DatabaseTypes';
import path from 'path';
import fs from 'fs';
class Config {
......@@ -23,7 +26,7 @@ class Config {
};
public exercice: {
maxSameName: number; resultsFolder: string; default: { description: string; visibility: string; };
maxSameName: number; resultsFolder: string, pipelineResultsFolder: string; default: { description: string; visibility: string; };
};
public readonly userPasswordLength: number;
......@@ -74,7 +77,8 @@ class Config {
this.exercice = {
maxSameName : Number(process.env.EXERCICE_MAX_SAME_NAME || 0),
resultsFolder: process.env.EXERCICE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercice creation and muste be interpreted at exercice runtime
resultsFolder : process.env.EXERCICE_RESULTS_FOLDER?.convertWithEnvVars() ?? '',
pipelineResultsFolder: process.env.EXERCICE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercice creation and muste be interpreted at exercice runtime
default : {
description: process.env.EXERCICE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
visibility : process.env.EXERCICE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
......@@ -84,6 +88,14 @@ class Config {
this.userPasswordLength = Number(process.env.USER_PASSWORD_LENGTH || 0);
this.userPasswordSaltRounds = Number(process.env.USER_PASSWORD_SALT_ROUNDS || 10);
}
public getResultsFolder(exercice: Exercice): string {
const folderPath = path.join(this.exercice.resultsFolder, exercice.enonceName, exercice.id);
fs.mkdirSync(folderPath, { recursive: true });
return folderPath;
}
}
......
......@@ -4,6 +4,7 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres
import { BailOptions, ValidationChain } from 'express-validator/src/chain';
import GitlabManager from '../managers/GitlabManager';
import express from 'express';
import ExerciceHelper from '../shared/helpers/ExerciceHelper';
declare type DojoMeta = Meta & {
......@@ -81,6 +82,24 @@ class DojoValidators {
return value;
}
});
readonly exerciceResultsValidator = this.toValidatorSchemaOptions({
bail : true,
errorMessage: 'Results: not provided or invalid format',
options : (value, {
req,
path
}) => {
return new Promise((resolve, reject) => {
const results = this.getParamValue(req, path);
if ( results ) {
ExerciceHelper.validateResultFile(results, false).isValid ? resolve(true) : reject();
} else {
reject();
}
});
}
});
}
......
......@@ -24,6 +24,9 @@ import GitlabFile from '../shared/types/Gitlab/GitlabFile';
import EnonceFile from '../shared/types/Dojo/EnonceFile';
import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType';
import JSON5 from 'json5';
import ExerciceResultsFile from '../shared/types/Dojo/ExerciceResultsFile';
import fs from 'fs';
import path from 'path';
class ExerciceRoutes implements RoutesManager {
......@@ -35,10 +38,39 @@ class ExerciceRoutes implements RoutesManager {
}
};
private readonly resultValidator: ExpressValidator.Schema = {
exitCode : {
isInt: true,
toInt: true
},
commit : {
trim : true,
notEmpty : true,
customSanitizer: DojoValidators.jsonSanitizer
},
results : {
trim : true,
notEmpty : true,
custom : DojoValidators.exerciceResultsValidator,
customSanitizer: DojoValidators.jsonSanitizer
},
files : {
trim : true,
notEmpty : true,
customSanitizer: DojoValidators.jsonSanitizer
},
archiveBase64: {
isBase64: true,
notEmpty: true
}
};
registerOnBackend(backend: Express) {
backend.post('/enonces/:enonceNameOrUrl/exercices', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciceValidator), this.createExercice.bind(this));
backend.get('/exercices/:exerciceId/enonce', SecurityMiddleware.check(false, SecurityCheckType.EXERCICE_SECRET), this.getEnonce.bind(this));
backend.post('/exercices/:exerciceId/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCICE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this));
}
private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string {
......@@ -68,7 +100,7 @@ class ExerciceRoutes implements RoutesManager {
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_EXERCICE_ID', exerciceId, false, true);
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_SECRET', secret, false, true);
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_RESULTS_FOLDER', Config.exercice.resultsFolder, false, false);
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_RESULTS_FOLDER', Config.exercice.pipelineResultsFolder, false, false);
break;
} catch ( error ) {
......@@ -170,6 +202,26 @@ class ExerciceRoutes implements RoutesManager {
immutable : immutableFiles
});
}
private async createResult(req: express.Request, res: express.Response) {
const params: { exitCode: number, commit: any, results: ExerciceResultsFile, files: any, archiveBase64: string } = req.body;
const exercice: Exercice = req.boundParams.exercice!;
const result = await db.result.create({
data: {
exerciceId: exercice.id,
exitCode : params.exitCode,
success : params.results.success,
commit : params.commit,
results : params.results as unknown as Prisma.JsonObject,
files : params.files
}
});
fs.writeFileSync(path.join(Config.getResultsFolder(exercice), `${ result.dateTime.toISOString().replace(':', '-') }.tar.gz`), params.archiveBase64, 'base64');
req.session.sendResponse(res, StatusCodes.OK);
}
}
......
Subproject commit 4dc9da6a4b20987a1f0df47c884b6f2891f4e415
Subproject commit eab5c0a5a32079fcb439a1ad79453611c8605536