Skip to content
Snippets Groups Projects
Select Git revision
  • ba55296e5d110d5dc455ec8599d243cc5f54bc66
  • main default protected
  • jw_sonar
  • v6.0.0 protected
  • bedran_exercise-list
  • ask-user-to-delete-exercises-on-duplicates
  • update-dependencies
  • jw_sonar_backup
  • add_route_assignments
  • 6.0.0-dev
  • 5.0.1
  • 5.0.0
  • 4.1.0
  • 4.0.0
  • 3.5.3
  • 3.5.2
  • 3.5.1
  • 3.5.0
  • 3.4.2
  • 3.4.1
  • 3.4.0
  • 3.3.0
  • 3.2.0
  • 3.1.3
  • 3.1.2
  • 3.1.1
  • 3.1.0
  • 3.0.1
  • 3.0.0
29 results

ParamsCallbackManager.ts

Blame
  • AssignmentRoutes.ts 14.96 KiB
    import { Express }                 from 'express-serve-static-core';
    import express, { RequestHandler } from 'express';
    import * as ExpressValidator       from 'express-validator';
    import { StatusCodes }             from 'http-status-codes';
    import RoutesManager               from '../express/RoutesManager.js';
    import ParamsValidatorMiddleware   from '../middlewares/ParamsValidatorMiddleware.js';
    import SecurityMiddleware          from '../middlewares/SecurityMiddleware.js';
    import SecurityCheckType           from '../types/SecurityCheckType.js';
    import GitlabManager               from '../managers/GitlabManager.js';
    import Config                      from '../config/Config.js';
    import logger                      from '../shared/logging/WinstonLogger.js';
    import DojoValidators              from '../helpers/DojoValidators.js';
    import { Prisma }                  from '@prisma/client';
    import db                          from '../helpers/DatabaseHelper.js';
    import { Assignment }              from '../types/DatabaseTypes.js';
    import AssignmentManager           from '../managers/AssignmentManager.js';
    import fs                          from 'fs';
    import path                        from 'path';
    import SharedAssignmentHelper      from '../shared/helpers/Dojo/SharedAssignmentHelper.js';
    import GlobalHelper                from '../helpers/GlobalHelper.js';
    import DojoStatusCode              from '../shared/types/Dojo/DojoStatusCode.js';
    import DojoModelsHelper            from '../helpers/DojoModelsHelper.js';
    import * as Gitlab                 from '@gitbeaker/rest';
    import { GitbeakerRequestError }   from '@gitbeaker/requester-utils';
    import SharedConfig                from '../shared/config/SharedConfig.js';
    
    
    class AssignmentRoutes implements RoutesManager {
        private readonly assignmentValidator: ExpressValidator.Schema = {
            name    : {
                trim    : true,
                notEmpty: true
            },
            members : {
                trim           : true,
                notEmpty       : true,
                customSanitizer: DojoValidators.jsonSanitizer
            },
            template: {
                trim           : true,
                custom         : DojoValidators.templateUrlValidator,
                customSanitizer: DojoValidators.templateUrlSanitizer
            }
        };
    
        private readonly assignmentAddCorrigeValidator: ExpressValidator.Schema = {
            exerciseIdOrUrl: {
                trim    : true,
                notEmpty: true,
                custom  : DojoValidators.exerciseIdOrUrlValidator
            }
        };
    
        registerOnBackend(backend: Express) {
            backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment.bind(this) as RequestHandler);
            backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment.bind(this) as RequestHandler);
    
            backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true).bind(this) as RequestHandler);
            backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false).bind(this) as RequestHandler);
    
            backend.post('/assignments/:assignmentNameOrUrl/corrections', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), ParamsValidatorMiddleware.validate(this.assignmentAddCorrigeValidator), this.linkUpdateAssignmentCorrection(false).bind(this) as RequestHandler);
            backend.patch('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.linkUpdateAssignmentCorrection(true).bind(this) as RequestHandler);
        }
    
        // Get an assignment by its name or gitlab url
        private async getAssignment(req: express.Request, res: express.Response) {
            const assignment: Partial<Assignment> | undefined = req.boundParams.assignment;
    
            if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment as Assignment, req.session.profile) ) {
                delete assignment.gitlabId;
                delete assignment.gitlabLink;
                delete assignment.gitlabCreationInfo;
                delete assignment.gitlabLastInfo;
                delete assignment.gitlabLastInfoDate;
                delete assignment.staff;
                delete assignment.exercises;
            }
    
            return assignment ? req.session.sendResponse(res, StatusCodes.OK, DojoModelsHelper.getFullSerializableObject(assignment)) : res.status(StatusCodes.NOT_FOUND).send();
        }
    
        private async createAssignment(req: express.Request, res: express.Response) {
            const params: {
                name: string, members: Array<Gitlab.UserSchema>, template: string
            } = req.body;
            params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ];
            params.members = params.members.removeObjectDuplicates(gitlabUser => gitlabUser.id);
    
    
            let repository: Gitlab.ProjectSchema;
            try {
                repository = await GitlabManager.createRepository(params.name, Config.assignment.default.description.replace('{{ASSIGNMENT_NAME}}', params.name), Config.assignment.default.visibility, Config.assignment.default.initReadme, Config.gitlab.group.assignments, Config.assignment.default.sharedRunnersEnabled, Config.assignment.default.wikiEnabled, params.template);
            } catch ( error ) {
                logger.error('Repo creation error');
                logger.error(JSON.stringify(error));
    
                if ( error instanceof GitbeakerRequestError ) {
                    if ( error.cause?.description ) {
                        const description = error.cause.description as unknown;
                        if ( GlobalHelper.isRepoNameAlreadyTaken(description) ) {
                            req.session.sendResponse(res, StatusCodes.CONFLICT, {}, `Repository name has already been taken`, DojoStatusCode.ASSIGNMENT_NAME_CONFLICT);
                            return;
                        }
                    }
    
                    req.session.sendResponse(res, error.cause?.response.status ?? StatusCodes.INTERNAL_SERVER_ERROR);
                    return;
                }
    
                req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR);
                return;
            }
    
            await new Promise(resolve => setTimeout(resolve, Config.gitlab.repository.timeoutAfterCreation));
    
            const repoCreationFnExec = GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository);
    
            try {
                await repoCreationFnExec(() => GitlabManager.protectBranch(repository.id, '*', true, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.ADMIN), 'Branch protection modification error');
                await repoCreationFnExec(() => GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status'), 'Pipeline badge addition error');
                await repoCreationFnExec(() => GitlabManager.deleteFile(repository.id, '.gitlab-ci.yml', 'Remove .gitlab-ci.yml'));
                await repoCreationFnExec(() => GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/assignment_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)'), 'CI/CD file creation error');
    
                await repoCreationFnExec(() => Promise.all(params.members.map(member => member.id).map(GlobalHelper.addRepoMember(repository.id))), 'Add repository members error');
    
                const assignment: Assignment = await repoCreationFnExec(() => db.assignment.create({
                                                                                                       data: {
                                                                                                           name              : repository.name,
                                                                                                           gitlabId          : repository.id,
                                                                                                           gitlabLink        : repository.web_url,
                                                                                                           gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
                                                                                                           gitlabLastInfo    : repository as unknown as Prisma.JsonObject,
                                                                                                           gitlabLastInfoDate: new Date(),
                                                                                                           staff             : {
                                                                                                               connectOrCreate: [ ...params.members.map(gitlabUser => {
                                                                                                                   return {
                                                                                                                       create: {
                                                                                                                           id            : gitlabUser.id,
                                                                                                                           gitlabUsername: gitlabUser.name
                                                                                                                       },
                                                                                                                       where : {
                                                                                                                           id: gitlabUser.id
                                                                                                                       }
                                                                                                                   };
                                                                                                               }) ]
                                                                                                           }
                                                                                                       }
                                                                                                   }), 'Database error') as Assignment;
    
                req.session.sendResponse(res, StatusCodes.OK, assignment);
            } catch ( error ) {
                /* Empty */
            }
        }
    
        private changeAssignmentPublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> {
            return async (req: express.Request, res: express.Response): Promise<void> => {
                if ( publish ) {
                    const isPublishable = await SharedAssignmentHelper.isPublishable(req.boundParams.assignment!.gitlabId);
                    if ( !isPublishable.isPublishable ) {
                        req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { lastPipeline: isPublishable.lastPipeline }, isPublishable.status?.message, isPublishable.status?.code);
                        return;
                    }
                }
    
                try {
                    await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? 'internal' : 'private');
    
                    await db.assignment.update({
                                                   where: {
                                                       name: req.boundParams.assignment!.name
                                                   },
                                                   data : {
                                                       published: publish
                                                   }
                                               });
    
                    req.session.sendResponse(res, StatusCodes.OK);
                } catch ( error ) {
                    logger.error(JSON.stringify(error));
    
                    if ( error instanceof GitbeakerRequestError ) {
                        req.session.sendResponse(res, error.cause?.response.status ?? StatusCodes.INTERNAL_SERVER_ERROR, undefined, 'Error while updating the assignment state');
                        return;
                    }
    
                    req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, undefined, 'Error while updating the assignment state');
                }
            };
        }
    
        private linkUpdateAssignmentCorrection(isUpdate: boolean): (req: express.Request, res: express.Response) => Promise<void> {
            return async (req: express.Request, res: express.Response): Promise<void> => {
                if ( req.boundParams.exercise?.assignmentName !== req.boundParams.assignment?.name ) {
                    return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'The exercise does not belong to the assignment', DojoStatusCode.ASSIGNMENT_EXERCISE_NOT_RELATED);
                }
    
                if ( !req.boundParams.assignment?.published ) {
                    return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'The assignment must be public', DojoStatusCode.ASSIGNMENT_NOT_PUBLISHED);
                }
    
                if ( !isUpdate && req.boundParams.exercise?.isCorrection ) {
                    return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is already a correction', DojoStatusCode.EXERCISE_CORRECTION_ALREADY_EXIST);
                } else if ( isUpdate && !req.boundParams.exercise?.isCorrection ) {
                    return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is not a correction', DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST);
                }
                
                const lastCommit = await GitlabManager.getRepositoryLastCommit(req.boundParams.exercise!.gitlabId);
    
                if ( lastCommit ) {
                    if ( !isUpdate && SharedConfig.production ) { //Disable in dev env because gitlab dev group is private and we can't change visibility of sub projects
                        await GitlabManager.changeRepositoryVisibility(req.boundParams.exercise!.gitlabId, 'internal');
                    }
    
                    await db.exercise.update({
                                                 where: {
                                                     id: req.boundParams.exercise!.id
                                                 },
                                                 data : {
                                                     correctionCommit: lastCommit
                                                 }
                                             });
    
                    return req.session.sendResponse(res, StatusCodes.OK);
                } else {
                    return req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, undefined, 'No last commit found');
                }
            };
        }
    }
    
    
    export default new AssignmentRoutes();