Select Git revision
GitlabRoutes.ts
ExerciceRoutes.ts 9.69 KiB
import { Express } from 'express-serve-static-core';
import express from 'express';
import * as ExpressValidator from 'express-validator';
import { StatusCodes } from 'http-status-codes';
import RoutesManager from '../express/RoutesManager';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware';
import ApiRequest from '../types/ApiRequest';
import SecurityMiddleware from '../middlewares/SecurityMiddleware';
import GitlabUser from '../shared/types/Gitlab/GitlabUser';
import GitlabManager from '../managers/GitlabManager';
import Config from '../config/Config';
import GitlabRepository from '../shared/types/Gitlab/GitlabRepository';
import { AxiosError } from 'axios';
import logger from '../shared/logging/WinstonLogger';
import DojoValidators from '../helpers/DojoValidators';
import { v4 as uuidv4 } from 'uuid';
import GitlabMember from '../shared/types/Gitlab/GitlabMember';
import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel';
import { Prisma } from '@prisma/client';
import { Enonce, Exercice } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper';
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 {
private readonly exerciceValidator: ExpressValidator.Schema = {
members: {
trim : true,
notEmpty : true,
customSanitizer: DojoValidators.jsonSanitizer
}
};
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));
}
private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string {
return `DojoEx - ${ enonce.name } - ${ members.map(member => member.username).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`;
}
private getExercicePath(enonce: Enonce, exerciceId: string): string {
return `dojo-ex_${ (enonce.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciceId }`;
}
private async createExercice(req: ApiRequest, res: express.Response) {
const params: { members: Array<GitlabUser> } = req.body;
params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ].removeObjectDuplicates(gitlabUser => gitlabUser.id);
const enonce: Enonce = req.boundParams.enonce;
const exerciceId: string = uuidv4();
const secret: string = uuidv4();
let repository: GitlabRepository;
let suffix: number = 0;
do {
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);
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_SECRET', secret, false, true);
break;
} catch ( error ) {
if ( error instanceof AxiosError ) {
if ( error.response.data.message.name && error.response.data.message.name == 'has already been taken' ) {
suffix++;
} else {
return res.status(error.response.status).send();
}
} else {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
}
}
} while ( suffix < Config.exercice.maxSameName );
if ( suffix >= Config.exercice.maxSameName ) {
return res.status(StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE).send();
}
try {
await Promise.all([ ...new Set([ ...enonce.staff.map(user => user.gitlabId), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<GitlabMember | false> => {
try {
return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER);
} catch ( e ) {
return false;
}
}));
const exercice: Exercice = await db.exercice.create({
data: {
id : exerciceId,
enonceName : enonce.name,
name : repository.name,
secret : secret,
gitlabId : repository.id,
gitlabLink : repository.web_url,
gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
gitlabLastInfo : repository as unknown as Prisma.JsonObject,
gitlabLastInfoDate: new Date(),
members : {
connectOrCreate: [ ...params.members.map(gitlabUser => {
return {
create: {
gitlabId : gitlabUser.id,
firstname: gitlabUser.name
},
where : {
gitlabId: gitlabUser.id
}
};
}) ]
}
}
});
return req.session.sendResponse(res, StatusCodes.OK, exercice);
} catch ( error ) {
if ( error instanceof AxiosError ) {
return res.status(error.response.status).send();
}
logger.error(error);
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.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.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.gitlabId, gitlabTreeFile.path));
break;
}
}
}
}));
return req.session.sendResponse(res, StatusCodes.OK, {
enonce : (req.boundParams.exercice as Exercice).enonce,
enonceFile: dojoEnonceFile,
immutable : immutableFiles
});
}
}
export default new ExerciceRoutes();