diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts index 56e81f79d705a44dbac9fe77c73e5bb33a70b990..7daaf8d27b0d2f95ddc51076ce30887613c170bb 100644 --- a/ExpressAPI/src/managers/GitlabManager.ts +++ b/ExpressAPI/src/managers/GitlabManager.ts @@ -8,7 +8,7 @@ import logger import { AccessLevel, EditProjectOptions, ProjectVariableSchema, ProtectedBranchAccessLevel, ProtectedBranchSchema } from '@gitbeaker/core'; import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js'; import SharedGitlabManager from '../shared/managers/SharedGitlabManager.js'; - +import * as fs from 'fs'; class GitlabManager extends SharedGitlabManager { constructor() { @@ -268,11 +268,22 @@ class GitlabManager extends SharedGitlabManager { } } - async exportRepository(repoId: number, extension: ArchiveType): Promise<Blob> { + async exportRepository(repoId: number) : Promise<Blob>{ try { - return await this.api.Repositories.showArchive(repoId, { - fileType: extension - }); + this.api.Repositories.showArchive(repoId).then(async archive => { + const buffer = Buffer.from(await archive.arrayBuffer()); + + fs.writeFile('repository-archive.tar.gz', buffer, (err) => { + if (err) { + console.error('Error saving archive:', err); + } else { + console.log('Archive saved successfully!'); + } + }); + }).catch(error => { + console.error('Error fetching archive:', error); + }); + return await this.api.Repositories.showArchive(repoId); } catch ( e ) { logger.error(JSON.stringify(e)); return Promise.reject(e); diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index acac72d10747afad42f00b81099bf2651728e80a..cfe103cefacc5396c107c8c0588f8e66147aadc5 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -42,7 +42,7 @@ class AssignmentRoutes implements RoutesManager { customSanitizer: DojoValidators.templateUrlSanitizer } }; - + private readonly assignmentAddCorrigeValidator: ExpressValidator.Schema = { exerciseIdOrUrl: { trim : true, @@ -58,7 +58,7 @@ class AssignmentRoutes implements RoutesManager { notEmpty: false } }; - + private readonly assignmentUpdateCorrigeValidator: ExpressValidator.Schema = { commit : { trim : true, @@ -69,24 +69,24 @@ class AssignmentRoutes implements RoutesManager { notEmpty: false } }; - + 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), ParamsValidatorMiddleware.validate(this.assignmentUpdateCorrigeValidator), this.linkUpdateAssignmentCorrection(true).bind(this) as RequestHandler); backend.delete('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.unlinkAssignmentCorrection.bind(this) as RequestHandler); backend.get('/assignments/:assignmentNameOrUrl/export', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.exportAssignment.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 ) { if ( !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment as Assignment, req.session.profile) ) { delete assignment.gitlabId; @@ -97,48 +97,48 @@ class AssignmentRoutes implements RoutesManager { delete assignment.staff; delete assignment.exercises; } - + const getExercises = req.query.getMyExercises; let exercises: Array<Omit<Exercise, 'assignment'>> = []; if ( getExercises ) { exercises = await db.exercise.findMany({ - where : { - assignmentName: assignment.name, - members : { - some: { - id: req.session.profile.id - } - } - }, - include: { - assignment: false, - members : true, - results : true - } - }); + where : { + assignmentName: assignment.name, + members : { + some: { + id: req.session.profile.id + } + } + }, + include: { + assignment: false, + members : true, + results : true + } + }); } - + return req.session.sendResponse(res, StatusCodes.OK, DojoModelsHelper.getFullSerializableObject(Object.assign(assignment, { myExercises: exercises }))); } else { return 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; @@ -147,57 +147,57 @@ class AssignmentRoutes implements RoutesManager { 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; - + 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 ) { @@ -207,104 +207,103 @@ class AssignmentRoutes implements RoutesManager { 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 - } - }); - + 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 commit: Gitlab.CommitSchema | undefined = req.body.commit ? await GitlabManager.getRepositoryCommit(req.boundParams.exercise!.gitlabId, req.body.commit as string) : await GitlabManager.getRepositoryLastCommit(req.boundParams.exercise!.gitlabId); - + if ( commit ) { 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 : Object.assign({ - correctionCommit: commit - }, isUpdate && req.body.description === undefined ? {} : { - correctionDescription: req.body.description - }) - }); - + where: { + id: req.boundParams.exercise!.id + }, + data : Object.assign({ + correctionCommit: commit + }, isUpdate && req.body.description === undefined ? {} : { + correctionDescription: req.body.description + }) + }); + return req.session.sendResponse(res, StatusCodes.OK); } else { return req.session.sendResponse(res, StatusCodes.NOT_FOUND, undefined, 'Commit not found'); } }; } - + private async unlinkAssignmentCorrection(req: express.Request, res: express.Response) { 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.exercise?.isCorrection ) { return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is not a correction', DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST); } - + if ( 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, 'private'); } - + await db.exercise.update({ - where: { - id: req.boundParams.exercise.id - }, - data : { - correctionCommit : Prisma.DbNull, - correctionDescription: null - } - }); - + where: { + id: req.boundParams.exercise.id + }, + data : { + correctionCommit : Prisma.DbNull, + correctionDescription: null + } + }); + return req.session.sendResponse(res, StatusCodes.OK); } - + private async exportAssignment(req: express.Request, res: express.Response) { - let ext : Gitlab.ArchiveType = 'zip'; - await GitlabManager.exportRepository(req.boundParams.assignment!.gitlabId, ext); - return req.session.sendResponse(res, StatusCodes.OK); + const resDl = await GitlabManager.exportRepository(req.boundParams.assignment!.gitlabId); + return req.session.sendResponse(res, StatusCodes.OK, resDl); } }