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
  • add_route_assignments
  • ask-user-to-delete-exercises-on-duplicates
  • bedran_exercise-list
  • jw_sonar
  • jw_sonar_backup
  • main
  • update-dependencies
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.1.3
  • 3.2.0
  • 3.3.0
  • 3.4.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 3.5.1
  • 3.5.2
  • 3.5.3
  • 4.0.0
  • 4.1.0
  • 5.0.0
  • 5.0.1
  • 6.0.0-dev
  • v1.0.1
32 results

Target

Select target project
  • Dojo_Project_Nguyen/backend/dojobackendapi
  • dojo_project/projects/backend/dojobackendapi
2 results
Select Git revision
  • add_route_assignments
  • ask-user-to-delete-exercises-on-duplicates
  • bedran_exercise-list
  • jw_sonar
  • jw_sonar_backup
  • main
  • update-dependencies
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.1.3
  • 3.2.0
  • 3.3.0
  • 3.4.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 3.5.1
  • 3.5.2
  • 3.5.3
  • 4.0.0
  • 4.1.0
  • 5.0.0
  • 5.0.1
  • 6.0.0-dev
  • v1.0.1
32 results
Show changes
Commits on Source (5)
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"mysql": "^2.18.1", "mysql": "^2.18.1",
"node": "^20.5.0", "node": "^20.5.0",
"parse-link-header": "^2.0.0", "parse-link-header": "^2.0.0",
"tar-stream": "^3.1.6",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"winston": "^3.8.2" "winston": "^3.8.2"
}, },
...@@ -41,6 +42,7 @@ ...@@ -41,6 +42,7 @@
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^20.4.7", "@types/node": "^20.4.7",
"@types/parse-link-header": "^2.0.1", "@types/parse-link-header": "^2.0.1",
"@types/tar-stream": "^2.2.2",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"npm": "^9.8.1", "npm": "^9.8.1",
...@@ -304,6 +306,15 @@ ...@@ -304,6 +306,15 @@
"@types/node": "*" "@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": { "node_modules/@types/triple-beam": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz",
...@@ -418,6 +429,11 @@ ...@@ -418,6 +429,11 @@
"proxy-from-env": "^1.1.0" "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": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
...@@ -918,6 +934,11 @@ ...@@ -918,6 +934,11 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "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": { "node_modules/fecha": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
...@@ -4942,6 +4963,11 @@ ...@@ -4942,6 +4963,11 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
...@@ -5196,6 +5222,15 @@ ...@@ -5196,6 +5222,15 @@
"node": ">=10.0.0" "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": { "node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
...@@ -5232,6 +5267,16 @@ ...@@ -5232,6 +5267,16 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/tarn": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
"mysql" : "^2.18.1", "mysql" : "^2.18.1",
"node" : "^20.5.0", "node" : "^20.5.0",
"parse-link-header": "^2.0.0", "parse-link-header": "^2.0.0",
"tar-stream" : "^3.1.6",
"uuid" : "^9.0.0", "uuid" : "^9.0.0",
"winston" : "^3.8.2" "winston" : "^3.8.2"
}, },
...@@ -52,6 +53,7 @@ ...@@ -52,6 +53,7 @@
"@types/multer" : "^1.4.7", "@types/multer" : "^1.4.7",
"@types/node" : "^20.4.7", "@types/node" : "^20.4.7",
"@types/parse-link-header": "^2.0.1", "@types/parse-link-header": "^2.0.1",
"@types/tar-stream" : "^2.2.2",
"@types/uuid" : "^9.0.2", "@types/uuid" : "^9.0.2",
"nodemon" : "^3.0.1", "nodemon" : "^3.0.1",
"prisma" : "^5.1.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 { ...@@ -54,8 +54,11 @@ model Exercice {
model Result { model Result {
exerciceId String @db.Char(36) exerciceId String @db.Char(36)
dateTime DateTime @default(now()) dateTime DateTime @default(now())
pass Boolean success Boolean
details String @db.Text 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) exercice Exercice @relation(fields: [exerciceId], references: [id], onDelete: Cascade, onUpdate: Cascade)
......
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
import { Exercice } from '../types/DatabaseTypes';
import path from 'path';
import fs from 'fs';
class Config { class Config {
...@@ -23,7 +26,7 @@ class Config { ...@@ -23,7 +26,7 @@ class Config {
}; };
public exercice: { 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; public readonly userPasswordLength: number;
...@@ -74,7 +77,8 @@ class Config { ...@@ -74,7 +77,8 @@ class Config {
this.exercice = { this.exercice = {
maxSameName : Number(process.env.EXERCICE_MAX_SAME_NAME || 0), 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 : { default : {
description: process.env.EXERCICE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', description: process.env.EXERCICE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
visibility : process.env.EXERCICE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE visibility : process.env.EXERCICE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
...@@ -84,6 +88,14 @@ class Config { ...@@ -84,6 +88,14 @@ class Config {
this.userPasswordLength = Number(process.env.USER_PASSWORD_LENGTH || 0); this.userPasswordLength = Number(process.env.USER_PASSWORD_LENGTH || 0);
this.userPasswordSaltRounds = Number(process.env.USER_PASSWORD_SALT_ROUNDS || 10); 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 ...@@ -4,6 +4,7 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres
import { BailOptions, ValidationChain } from 'express-validator/src/chain'; import { BailOptions, ValidationChain } from 'express-validator/src/chain';
import GitlabManager from '../managers/GitlabManager'; import GitlabManager from '../managers/GitlabManager';
import express from 'express'; import express from 'express';
import ExerciceHelper from '../shared/helpers/ExerciceHelper';
declare type DojoMeta = Meta & { declare type DojoMeta = Meta & {
...@@ -81,6 +82,24 @@ class DojoValidators { ...@@ -81,6 +82,24 @@ class DojoValidators {
return value; 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'; ...@@ -24,6 +24,9 @@ import GitlabFile from '../shared/types/Gitlab/GitlabFile';
import EnonceFile from '../shared/types/Dojo/EnonceFile'; import EnonceFile from '../shared/types/Dojo/EnonceFile';
import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType'; import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType';
import JSON5 from 'json5'; import JSON5 from 'json5';
import ExerciceResultsFile from '../shared/types/Dojo/ExerciceResultsFile';
import fs from 'fs';
import path from 'path';
class ExerciceRoutes implements RoutesManager { class ExerciceRoutes implements RoutesManager {
...@@ -35,10 +38,39 @@ 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) { registerOnBackend(backend: Express) {
backend.post('/enonces/:enonceNameOrUrl/exercices', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciceValidator), this.createExercice.bind(this)); 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.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 { private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string {
...@@ -68,7 +100,7 @@ class ExerciceRoutes implements RoutesManager { ...@@ -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_EXERCICE_ID', exerciceId, false, true);
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_SECRET', secret, 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; break;
} catch ( error ) { } catch ( error ) {
...@@ -170,6 +202,26 @@ class ExerciceRoutes implements RoutesManager { ...@@ -170,6 +202,26 @@ class ExerciceRoutes implements RoutesManager {
immutable : immutableFiles 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