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 (10)
Showing
with 246 additions and 41 deletions
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"@prisma/client": "^5.0.0", "@prisma/client": "^5.0.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"compression": "^1.7.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",
...@@ -24,17 +25,21 @@ ...@@ -24,17 +25,21 @@
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"node": "^20.2.0", "node": "^20.2.0",
"parse-link-header": "^2.0.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"winston": "^3.8.2" "winston": "^3.8.2",
"yaml": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/compression": "^1.7.2",
"@types/cors": "^2.8.13", "@types/cors": "^2.8.13",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.2", "@types/jsonwebtoken": "^9.0.2",
"@types/morgan": "^1.9.4", "@types/morgan": "^1.9.4",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^20.2.4", "@types/node": "^20.2.4",
"@types/parse-link-header": "^2.0.1",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"nodemon": "^2.0.22", "nodemon": "^2.0.22",
"npm": "^9.6.7", "npm": "^9.6.7",
...@@ -170,6 +175,15 @@ ...@@ -170,6 +175,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/compression": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz",
"integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/connect": { "node_modules/@types/connect": {
"version": "3.4.35", "version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
...@@ -251,6 +265,12 @@ ...@@ -251,6 +265,12 @@
"integrity": "sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==", "integrity": "sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==",
"dev": true "dev": true
}, },
"node_modules/@types/parse-link-header": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/parse-link-header/-/parse-link-header-2.0.1.tgz",
"integrity": "sha512-BrKNSrRTqn3UkMXvdVtr/znJch0PMBpEvEP8oBkxDx7eEGntuFLI+WpA5HGsNHK4SlqyhaMa+Ks0ViwyixQB5w==",
"dev": true
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.7", "version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
...@@ -603,6 +623,47 @@ ...@@ -603,6 +623,47 @@
"node": "^12.20.0 || >=14" "node": "^12.20.0 || >=14"
} }
}, },
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"dependencies": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/compression/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
...@@ -4930,6 +4991,14 @@ ...@@ -4930,6 +4991,14 @@
"fn.name": "1.x.x" "fn.name": "1.x.x"
} }
}, },
"node_modules/parse-link-header": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-link-header/-/parse-link-header-2.0.0.tgz",
"integrity": "sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==",
"dependencies": {
"xtend": "~4.0.1"
}
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
...@@ -5575,6 +5644,14 @@ ...@@ -5575,6 +5644,14 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}, },
"node_modules/yaml": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"engines": {
"node": ">= 14"
}
},
"node_modules/yn": { "node_modules/yn": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
"@prisma/client" : "^5.0.0", "@prisma/client" : "^5.0.0",
"axios" : "^1.4.0", "axios" : "^1.4.0",
"bcryptjs" : "^2.4.3", "bcryptjs" : "^2.4.3",
"compression" : "^1.7.4",
"cors" : "^2.8.5", "cors" : "^2.8.5",
"dotenv" : "^16.0.3", "dotenv" : "^16.0.3",
"express" : "^4.18.2", "express" : "^4.18.2",
...@@ -35,17 +36,21 @@ ...@@ -35,17 +36,21 @@
"multer" : "^1.4.5-lts.1", "multer" : "^1.4.5-lts.1",
"mysql" : "^2.18.1", "mysql" : "^2.18.1",
"node" : "^20.2.0", "node" : "^20.2.0",
"parse-link-header": "^2.0.0",
"uuid" : "^9.0.0", "uuid" : "^9.0.0",
"winston" : "^3.8.2" "winston" : "^3.8.2",
"yaml" : "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs" : "^2.4.2", "@types/bcryptjs" : "^2.4.2",
"@types/compression" : "^1.7.2",
"@types/cors" : "^2.8.13", "@types/cors" : "^2.8.13",
"@types/express" : "^4.17.17", "@types/express" : "^4.17.17",
"@types/jsonwebtoken" : "^9.0.2", "@types/jsonwebtoken" : "^9.0.2",
"@types/morgan" : "^1.9.4", "@types/morgan" : "^1.9.4",
"@types/multer" : "^1.4.7", "@types/multer" : "^1.4.7",
"@types/node" : "^20.2.4", "@types/node" : "^20.2.4",
"@types/parse-link-header": "^2.0.1",
"@types/uuid" : "^9.0.2", "@types/uuid" : "^9.0.2",
"nodemon" : "^2.0.22", "nodemon" : "^2.0.22",
"prisma" : "^5.0.0", "prisma" : "^5.0.0",
......
...@@ -16,7 +16,7 @@ class Config { ...@@ -16,7 +16,7 @@ class Config {
}; };
public enonce: { public enonce: {
default: { description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: string; wikiEnabled: boolean; template: string }; default: { description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: string; wikiEnabled: boolean; template: string }; baseFiles: Array<string>; filename: string
}; };
public exercice: { public exercice: {
...@@ -64,7 +64,9 @@ class Config { ...@@ -64,7 +64,9 @@ class Config {
visibility : process.env.ENONCE_DEFAULT_VISIBILITY, visibility : process.env.ENONCE_DEFAULT_VISIBILITY,
wikiEnabled : process.env.ENONCE_DEFAULT_WIKI_ENABLED.toBoolean(), wikiEnabled : process.env.ENONCE_DEFAULT_WIKI_ENABLED.toBoolean(),
template : process.env.ENONCE_DEFAULT_TEMPLATE.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) template : process.env.ENONCE_DEFAULT_TEMPLATE.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token)
} },
baseFiles: JSON.parse(process.env.ENONCE_BASE_FILES || '[]'),
filename : process.env.ENONCE_FILENAME || ''
}; };
this.exercice = { this.exercice = {
......
...@@ -5,7 +5,7 @@ import Config from '../config/Config'; ...@@ -5,7 +5,7 @@ import Config from '../config/Config';
import express from 'express'; import express from 'express';
import ApiRequest from '../types/ApiRequest'; import ApiRequest from '../types/ApiRequest';
import UserManager from '../managers/UserManager'; import UserManager from '../managers/UserManager';
import DojoResponse from '../shared/types/DojoResponse'; import DojoResponse from '../shared/types/Dojo/DojoResponse';
import { User } from '../types/DatabaseTypes'; import { User } from '../types/DatabaseTypes';
...@@ -26,6 +26,7 @@ class Session { ...@@ -26,6 +26,7 @@ class Session {
async initSession(req: ApiRequest) { async initSession(req: ApiRequest) {
const authorization = req.headers.authorization; const authorization = req.headers.authorization;
if ( authorization ) { if ( authorization ) {
if ( authorization.startsWith('Bearer ') ) {
const jwtToken = authorization.replace('Bearer ', ''); const jwtToken = authorization.replace('Bearer ', '');
try { try {
...@@ -38,9 +39,10 @@ class Session { ...@@ -38,9 +39,10 @@ class Session {
} catch ( err ) { } } catch ( err ) { }
} }
} }
}
private static getToken(profileJson: any): string { private static getToken(profileJson: any): string {
return profileJson.id === null ? null : jwt.sign({ profile: profileJson }, Config.jwtConfig.secret, Config.jwtConfig.expiresIn > 0 ? { expiresIn: Config.jwtConfig.expiresIn } : {}); return profileJson === null ? null : jwt.sign({ profile: profileJson }, Config.jwtConfig.secret, Config.jwtConfig.expiresIn > 0 ? { expiresIn: Config.jwtConfig.expiresIn } : {});
} }
private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoResponse<T>> { private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoResponse<T>> {
......
...@@ -12,6 +12,7 @@ import Config from '../config/Config'; ...@@ -12,6 +12,7 @@ import Config from '../config/Config';
import logger from '../shared/logging/WinstonLogger'; import logger from '../shared/logging/WinstonLogger';
import ParamsCallbackManager from '../middlewares/ParamsCallbackManager'; import ParamsCallbackManager from '../middlewares/ParamsCallbackManager';
import ApiRoutesManager from '../routes/ApiRoutesManager'; import ApiRoutesManager from '../routes/ApiRoutesManager';
import compression from 'compression';
class API implements WorkerTask { class API implements WorkerTask {
...@@ -27,6 +28,7 @@ class API implements WorkerTask { ...@@ -27,6 +28,7 @@ class API implements WorkerTask {
this.backend.use(morganMiddleware); //Log API accesses this.backend.use(morganMiddleware); //Log API accesses
this.backend.use(helmet()); //Help to secure express, https://helmetjs.github.io/ this.backend.use(helmet()); //Help to secure express, https://helmetjs.github.io/
this.backend.use(cors()); //Allow CORS requests this.backend.use(cors()); //Allow CORS requests
this.backend.use(compression()); //Compress responses
ParamsCallbackManager.register(this.backend); ParamsCallbackManager.register(this.backend);
......
import { Prisma } from '@prisma/client';
import { Enonce } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper';
class ExerciceManager {
get(id: string, include: Prisma.ExerciceInclude | undefined = undefined): Promise<Enonce | undefined> {
return db.exercice.findUnique({
where : {
id: id
},
include: include
});
}
}
export default new ExerciceManager();
...@@ -8,6 +8,9 @@ import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; ...@@ -8,6 +8,9 @@ import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
import ApiRequest from '../types/ApiRequest'; import ApiRequest from '../types/ApiRequest';
import GitlabUser from '../shared/types/Gitlab/GitlabUser'; import GitlabUser from '../shared/types/Gitlab/GitlabUser';
import GitlabRoutes from '../shared/types/Gitlab/GitlabRoutes'; import GitlabRoutes from '../shared/types/Gitlab/GitlabRoutes';
import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile';
import parseLinkHeader from 'parse-link-header';
import GitlabFile from '../shared/types/Gitlab/GitlabFile';
class GitlabManager { class GitlabManager {
...@@ -145,6 +148,45 @@ class GitlabManager { ...@@ -145,6 +148,45 @@ class GitlabManager {
return response.data; return response.data;
} }
async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<GitlabTreeFile>> {
let address: string | undefined = this.getApiUrl(GitlabRoutes.REPOSITORY_TREE).replace('{{id}}', String(repoId));
let params: any = {
pagination: 'keyset',
recursive : recursive,
per_page : 100,
ref : branch
};
let results: Array<GitlabTreeFile> = [];
while ( params !== undefined ) {
const response = await axios.get<Array<GitlabTreeFile>>(address, {
params: params
});
results.push(...response.data);
if ( 'link' in response.headers ) {
const link = parseLinkHeader(response.headers['link']);
params = link.next;
} else {
params = undefined;
}
}
return results;
}
async getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<GitlabFile> {
const response = await axios.get<GitlabFile>(this.getApiUrl(GitlabRoutes.REPOSITORY_FILE).replace('{{id}}', String(repoId)).replace('{{filePath}}', encodeURIComponent(filePath)), {
params: {
ref: branch
}
});
return response.data;
}
} }
......
...@@ -3,6 +3,7 @@ import ApiRequest from '../types/ApiRequest'; ...@@ -3,6 +3,7 @@ import ApiRequest from '../types/ApiRequest';
import express from 'express'; import express from 'express';
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
import EnonceManager from '../managers/EnonceManager'; import EnonceManager from '../managers/EnonceManager';
import ExerciceManager from '../managers/ExerciceManager';
class ParamsCallbackManager { class ParamsCallbackManager {
...@@ -24,7 +25,8 @@ class ParamsCallbackManager { ...@@ -24,7 +25,8 @@ class ParamsCallbackManager {
initBoundParams(req: ApiRequest) { initBoundParams(req: ApiRequest) {
if ( !req.boundParams ) { if ( !req.boundParams ) {
req.boundParams = { req.boundParams = {
enonce: null enonce : null,
exercice: null
}; };
} }
} }
...@@ -34,6 +36,12 @@ class ParamsCallbackManager { ...@@ -34,6 +36,12 @@ class ParamsCallbackManager {
exercices: true, exercices: true,
staff : true staff : true
} ], 'enonce'); } ], 'enonce');
this.listenParam('exerciceId', backend, ExerciceManager.get.bind(ExerciceManager), [ {
enonce : true,
members: true,
results: true
} ], 'exercice');
} }
} }
......
...@@ -11,7 +11,7 @@ class SecurityMiddleware { ...@@ -11,7 +11,7 @@ class SecurityMiddleware {
check(checkIfConnected: boolean, ...checkTypes: Array<SecurityCheckType>): (req: ApiRequest, res: express.Response, next: express.NextFunction) => void { check(checkIfConnected: boolean, ...checkTypes: Array<SecurityCheckType>): (req: ApiRequest, res: express.Response, next: express.NextFunction) => void {
return async (req: ApiRequest, res: express.Response, next: express.NextFunction) => { return async (req: ApiRequest, res: express.Response, next: express.NextFunction) => {
if ( checkIfConnected ) { if ( checkIfConnected ) {
if ( req.session.profile.id === null ) { if ( req.session.profile === null ) {
return req.session.sendResponse(res, StatusCodes.UNAUTHORIZED); return req.session.sendResponse(res, StatusCodes.UNAUTHORIZED);
} }
} }
...@@ -19,9 +19,9 @@ class SecurityMiddleware { ...@@ -19,9 +19,9 @@ class SecurityMiddleware {
let isAllowed = checkTypes.length === 0; let isAllowed = checkTypes.length === 0;
if ( !isAllowed ) { if ( !isAllowed ) {
for ( let checkType of checkTypes ) { for ( const checkType of checkTypes ) {
try { try {
switch ( checkType ) { switch ( String(checkType) ) {
case SecurityCheckType.TEACHING_STAFF: case SecurityCheckType.TEACHING_STAFF:
isAllowed = isAllowed || req.session.profile.isTeachingStaff; isAllowed = isAllowed || req.session.profile.isTeachingStaff;
break; break;
...@@ -31,8 +31,10 @@ class SecurityMiddleware { ...@@ -31,8 +31,10 @@ class SecurityMiddleware {
case SecurityCheckType.ENONCE_IS_PUBLISHED: case SecurityCheckType.ENONCE_IS_PUBLISHED:
isAllowed = isAllowed || req.boundParams.enonce.published; isAllowed = isAllowed || req.boundParams.enonce.published;
break; break;
case SecurityCheckType.EXERCICE_SECRET:
isAllowed = isAllowed || (req.headers.authorization && req.headers.authorization && req.headers.authorization.replace('ExerciceSecret ', '') === req.boundParams.exercice.secret);
break;
default: default:
isAllowed = isAllowed || false;
break; break;
} }
} catch ( e ) { } catch ( e ) {
......
...@@ -75,6 +75,8 @@ class EnonceRoutes implements RoutesManager { ...@@ -75,6 +75,8 @@ class EnonceRoutes implements RoutesManager {
let repository: GitlabRepository; let repository: GitlabRepository;
try { try {
repository = await GitlabManager.createRepository(params.name, Config.enonce.default.description.replace('{{ENONCE_NAME}}', params.name), Config.enonce.default.visibility, Config.enonce.default.initReadme, Config.gitlab.group.enonces, Config.enonce.default.sharedRunnersEnabled, Config.enonce.default.wikiEnabled, params.template); repository = await GitlabManager.createRepository(params.name, Config.enonce.default.description.replace('{{ENONCE_NAME}}', params.name), Config.enonce.default.visibility, Config.enonce.default.initReadme, Config.gitlab.group.enonces, Config.enonce.default.sharedRunnersEnabled, Config.enonce.default.wikiEnabled, params.template);
await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
} catch ( error ) { } catch ( error ) {
if ( error instanceof AxiosError ) { if ( error instanceof AxiosError ) {
if ( error.response.data.message.name && error.response.data.message.name == 'has already been taken' ) { if ( error.response.data.message.name && error.response.data.message.name == 'has already been taken' ) {
......
...@@ -20,6 +20,11 @@ import { Prisma } from '@prisma/client'; ...@@ -20,6 +20,11 @@ import { Prisma } from '@prisma/client';
import { Enonce, Exercice } from '../types/DatabaseTypes'; import { Enonce, Exercice } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper'; import db from '../helpers/DatabaseHelper';
import SecurityCheckType from '../types/SecurityCheckType'; import SecurityCheckType from '../types/SecurityCheckType';
import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile';
import GitlabFile from '../shared/types/Gitlab/GitlabFile';
import YAML from 'yaml';
import EnonceFile from '../shared/types/Dojo/EnonceFile';
import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType';
class ExerciceRoutes implements RoutesManager { class ExerciceRoutes implements RoutesManager {
...@@ -33,6 +38,8 @@ class ExerciceRoutes implements RoutesManager { ...@@ -33,6 +38,8 @@ class ExerciceRoutes implements RoutesManager {
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));
} }
private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string { private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string {
...@@ -58,7 +65,7 @@ class ExerciceRoutes implements RoutesManager { ...@@ -58,7 +65,7 @@ class ExerciceRoutes implements RoutesManager {
try { try {
repository = await GitlabManager.forkRepository((enonce.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciceName(enonce, params.members, suffix), this.getExercicePath(req.boundParams.enonce, exerciceId), Config.exercice.default.description.replace('{{ENONCE_NAME}}', enonce.name), Config.exercice.default.visibility, Config.gitlab.group.exercices); repository = await GitlabManager.forkRepository((enonce.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciceName(enonce, params.members, suffix), this.getExercicePath(req.boundParams.enonce, exerciceId), Config.exercice.default.description.replace('{{ENONCE_NAME}}', enonce.name), Config.exercice.default.visibility, Config.gitlab.group.exercices);
await GitlabManager.protectBranch(repository.id, '*', false, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER); await GitlabManager.protectBranch(repository.id, '*', false, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
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);
...@@ -127,6 +134,42 @@ class ExerciceRoutes implements RoutesManager { ...@@ -127,6 +134,42 @@ class ExerciceRoutes implements RoutesManager {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send(); return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
} }
} }
private async getEnonce(req: ApiRequest, res: express.Response) {
const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercice.enonce.gitlabId);
let enonceYamlFile: GitlabFile;
let immutableFiles: Array<GitlabFile> = await Promise.all(Config.enonce.baseFiles.map(async (baseFile: string) => {
let file = await GitlabManager.getFile(req.boundParams.exercice.enonce.gitlabId, baseFile);
if ( baseFile === Config.enonce.filename ) {
enonceYamlFile = file;
}
return file;
}));
const dojoEnonceFile: EnonceFile = YAML.parse(atob(enonceYamlFile.content));
const immutablePaths = dojoEnonceFile.immutable.map(fileDescriptor => fileDescriptor.path);
await Promise.all(repoTree.map(async gitlabTreeFile => {
if ( gitlabTreeFile.type == GitlabTreeFileType.BLOB ) {
for ( const immutablePath of immutablePaths ) {
if ( gitlabTreeFile.path.startsWith(immutablePath) ) {
immutableFiles.push(await GitlabManager.getFile(req.boundParams.exercice.enonce.gitlabId, gitlabTreeFile.path));
break;
}
}
}
}));
return req.session.sendResponse(res, StatusCodes.OK, {
enonce : (req.boundParams.exercice as Exercice).enonce,
enonceFile: dojoEnonceFile,
immutable : immutableFiles
});
}
} }
......
Subproject commit c9154d42dac81311cf1957f0d75f806737849b40 Subproject commit bfca2c401e4b5ff69b0a515fd9dcab49d36ee212
import express from 'express'; import express from 'express';
import Session from '../controllers/Session'; import Session from '../controllers/Session';
import { Enonce } from './DatabaseTypes'; import { Enonce, Exercice } from './DatabaseTypes';
type ApiRequest = express.Request & { type ApiRequest = express.Request & {
session: Session, boundParams: { session: Session, boundParams: {
enonce: Enonce enonce: Enonce, exercice: Exercice
} }
} }
......
...@@ -14,6 +14,7 @@ const enonceBase = Prisma.validator<Prisma.EnonceArgs>()({ ...@@ -14,6 +14,7 @@ const enonceBase = Prisma.validator<Prisma.EnonceArgs>()({
}); });
const exerciceBase = Prisma.validator<Prisma.ExerciceArgs>()({ const exerciceBase = Prisma.validator<Prisma.ExerciceArgs>()({
include: { include: {
enonce : true,
members: true, members: true,
results: true results: true
} }
......
...@@ -2,6 +2,7 @@ enum SecurityCheckType { ...@@ -2,6 +2,7 @@ enum SecurityCheckType {
TEACHING_STAFF = 'teachingStaff', TEACHING_STAFF = 'teachingStaff',
ENONCE_STAFF = 'enonceStaff', ENONCE_STAFF = 'enonceStaff',
ENONCE_IS_PUBLISHED = 'enonceIsPublished', ENONCE_IS_PUBLISHED = 'enonceIsPublished',
EXERCICE_SECRET = 'exerciceSecret',
} }
......