From 6efeccd2c53ab3b8a922cbd47609cd1cc170ede5 Mon Sep 17 00:00:00 2001 From: "kelly.nguyen" <kelly.nguyen@etu.hesge.ch> Date: Thu, 18 Jul 2024 23:47:57 +0200 Subject: [PATCH] add user route to export portfolio --- ExpressAPI/package-lock.json | 27 ++ ExpressAPI/package.json | 2 + .../migration.sql | 2 + ExpressAPI/prisma/schema.prisma | 1 + ExpressAPI/src/routes/AssignmentRoutes.ts | 53 +++- ExpressAPI/src/routes/UserRoutes.ts | 235 ++++++++++++++++-- 6 files changed, 305 insertions(+), 15 deletions(-) create mode 100644 ExpressAPI/prisma/migrations/20240705152946_add_deleted_to_assignment/migration.sql diff --git a/ExpressAPI/package-lock.json b/ExpressAPI/package-lock.json index 0987c18..7b82bc4 100644 --- a/ExpressAPI/package-lock.json +++ b/ExpressAPI/package-lock.json @@ -16,6 +16,8 @@ "axios": "^1.7.2", "compression": "^1.7.4", "cors": "^2.8.5", + "dotenv": "^16.4.5", + "dotenv-cli": "^7.4.2", "express": "^4.19.2", "express-validator": "^7.1.0", "form-data": "^4.0.0", @@ -3026,6 +3028,7 @@ "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -3033,6 +3036,30 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.2.tgz", + "integrity": "sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-cli/node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/dotenv-expand": { "version": "11.0.6", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", diff --git a/ExpressAPI/package.json b/ExpressAPI/package.json index 78d1a12..0b16234 100644 --- a/ExpressAPI/package.json +++ b/ExpressAPI/package.json @@ -35,6 +35,8 @@ "axios": "^1.7.2", "compression": "^1.7.4", "cors": "^2.8.5", + "dotenv": "^16.4.5", + "dotenv-cli": "^7.4.2", "express": "^4.19.2", "express-validator": "^7.1.0", "form-data": "^4.0.0", diff --git a/ExpressAPI/prisma/migrations/20240705152946_add_deleted_to_assignment/migration.sql b/ExpressAPI/prisma/migrations/20240705152946_add_deleted_to_assignment/migration.sql new file mode 100644 index 0000000..0e61d6e --- /dev/null +++ b/ExpressAPI/prisma/migrations/20240705152946_add_deleted_to_assignment/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE `Assignment` ADD COLUMN `deleted` BOOLEAN NOT NULL DEFAULT false; diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma index 8d7143a..dfc6ef0 100644 --- a/ExpressAPI/prisma/schema.prisma +++ b/ExpressAPI/prisma/schema.prisma @@ -34,6 +34,7 @@ model Assignment { gitlabLastInfo Json @db.Json gitlabLastInfoDate DateTime published Boolean @default(false) + deleted Boolean @default(false) exercises Exercise[] staff User[] diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index 30f970e..032987b 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -82,6 +82,7 @@ class AssignmentRoutes implements RoutesManager { backend.delete('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.unlinkAssignmentCorrection.bind(this) as RequestHandler); backend.get('/assignments/:assignmentNameOrUrl/export/:folderName', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.exportLightAssignment.bind(this) as RequestHandler); backend.get('/assignments/:folderName/zip', SecurityMiddleware.check(false, SecurityCheckType.ASSIGNMENT_STAFF), this.zipAssignment.bind(this) as RequestHandler); + backend.get('/assignments/:assignmentNameOrUrl/resume', SecurityMiddleware.check(false, SecurityCheckType.ASSIGNMENT_STAFF), this.createResume.bind(this) as RequestHandler); } // Get an assignment by its name or gitlab url @@ -305,7 +306,7 @@ class AssignmentRoutes implements RoutesManager { private async exportLightAssignment(req: express.Request, res: express.Response) { try { const folderName = req.params.folderName; - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + // const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); // const parentDir = path.join('/tmp', `export_${timestamp}`); const parentDir = path.join('/tmp', folderName); fs.mkdirSync(parentDir, { recursive: true }); @@ -363,6 +364,56 @@ class AssignmentRoutes implements RoutesManager { } }); } + + private async createResume(req : express.Request, res : express.Response) { + const assignmentData = await db.assignment.findUnique({ + where : { + name : req.boundParams.assignment?.name + } + }); + + const templateResume = `+++ +title = "Resume" +description = "Documentation of the Dojo Command Line interface." +template = "docs/section.html" +sort_by = "weight" +weight = 300 +draft = false ++++`; + const folderPath = '/tmp/test'; + // create folder + fs.mkdir(folderPath, (err) => { + if (err) { + return console.error(err); + } + console.log('Directory created successfully!'); + }); + + const indexPath = path.join(folderPath, "_index.md"); + const filePath = path.join(folderPath, "tmp.md"); + // write _index file + fs.writeFile(indexPath, templateResume, (err) => { + if (err) + console.log(err); + else { + console.log("_index file written successfully\n"); + console.log("The written has the following contents:"); + console.log(fs.readFileSync(indexPath, "utf8")); + } + }); + + fs.writeFile(filePath, templateResume, (err) => { + if (err) + console.log(err); + else { + console.log("File written successfully\n"); + console.log("The written has the following contents:"); + console.log(fs.readFileSync(filePath, "utf8")); + } + }); + + return req.session.sendResponse(res, StatusCodes.OK, assignmentData); + } } diff --git a/ExpressAPI/src/routes/UserRoutes.ts b/ExpressAPI/src/routes/UserRoutes.ts index 247f7d8..b48a646 100644 --- a/ExpressAPI/src/routes/UserRoutes.ts +++ b/ExpressAPI/src/routes/UserRoutes.ts @@ -1,10 +1,16 @@ -import { Express } from 'express-serve-static-core'; -import express, { RequestHandler } from 'express'; -import RoutesManager from '../express/RoutesManager'; -import SecurityMiddleware from '../middlewares/SecurityMiddleware'; -import SecurityCheckType from '../types/SecurityCheckType'; -import db from '../helpers/DatabaseHelper'; -import { StatusCodes } from 'http-status-codes'; +import { Express } from 'express-serve-static-core'; +import express, { RequestHandler } from 'express'; +import RoutesManager from '../express/RoutesManager'; +import SecurityMiddleware from '../middlewares/SecurityMiddleware'; +import SecurityCheckType from '../types/SecurityCheckType'; +import db from '../helpers/DatabaseHelper'; +import { StatusCodes } from 'http-status-codes'; +import fs from 'fs'; +import path from 'path'; +import logger from '../shared/logging/WinstonLogger.js'; +import GitlabManager from '../managers/GitlabManager.js'; +import archiver from 'archiver'; + class UserRoutes implements RoutesManager { registerOnBackend(backend: Express): void { @@ -12,6 +18,9 @@ class UserRoutes implements RoutesManager { backend.patch('/users/:userId/role', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.changeRole.bind(this) as RequestHandler); backend.get('/users/:userId/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.getUsersAssignments.bind(this) as RequestHandler); backend.get('/users/:userId/exercise', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.getUsersExercise.bind(this) as RequestHandler); + backend.get('/users/:userId/resume', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.createResume.bind(this) as RequestHandler); + backend.get('/users/:userId/portfolio', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.createPortfolio.bind(this) as RequestHandler); + backend.get('/users/:userId/zip', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.zipExport.bind(this) as RequestHandler); } private async getUsers(req: express.Request, res: express.Response) { @@ -26,12 +35,11 @@ class UserRoutes implements RoutesManager { id: id, }, include: { - assignments: true - // assignments: { - // where: { - // deleted: false - // } - // } + assignments: { + where: { + deleted: false + } + } }, // Include the assignments related to the user }); return req.session.sendResponse(res, StatusCodes.OK, user); @@ -57,7 +65,7 @@ class UserRoutes implements RoutesManager { return req.session.sendResponse(res, StatusCodes.FORBIDDEN); } } - + private async getUsersExercise(req: express.Request, res: express.Response) { const id = +req.params.userId; const user = await db.user.findUnique({ @@ -70,6 +78,205 @@ class UserRoutes implements RoutesManager { }); return req.session.sendResponse(res, StatusCodes.OK, user); } + + private async createResume(req: express.Request, res: express.Response) { + const id = +req.params.userId; + + const dataUser = await db.user.findUnique({ + where: { + id: id, + }, + include: { + assignments: { + where: { + deleted: false + } + } + }, + }); + const contentIndex = `+++ +title = "Resume" +description = "Resume for all assignments" +template = "docs/section.html" +sort_by = "weight" +weight = 0 ++++`; + + const folderPath = `/tmp/` + dataUser?.gitlabUsername; + const pathResume = path.join(folderPath, "resume"); + // create folder + try { + fs.mkdirSync(folderPath); + } catch (err) { + logger.error('Folder creation error'); + return req.session.sendResponse(res, StatusCodes.OK, false); + } + + try { + fs.mkdirSync(pathResume); + } catch (err) { + logger.error('Folder creation error'); + return req.session.sendResponse(res, StatusCodes.OK, false); + } + + const indexPath = path.join(pathResume, "_index.md"); + // write _index file + fs.writeFile(indexPath, contentIndex, (err) => { + if (err) { + console.log(err); + return req.session.sendResponse(res, StatusCodes.OK, false); + } + else { + console.log("_index file written successfully\n"); + } + }); + if (dataUser) { + let cpt = 1; + dataUser.assignments.forEach(a => { + const gitlabCreationInfo = a.gitlabCreationInfo as any; + const nameFile = a.name.replace(/ /g, "_"); + const filePath = path.join(pathResume, `${cpt}_${nameFile}.md`); + const templateFile = `+++ +title = "Resume of ${a.name}" +description = "TEST" +slug = "${nameFile}" +template = "docs/page.html" +weight = ${cpt} ++++ + +## Description + +${gitlabCreationInfo.description} + +## Lien git +[Repository link](${a.gitlabLink})`; + cpt++; + + fs.writeFile(filePath, templateFile, (err) => { + if (err) { + console.log(err); + return req.session.sendResponse(res, StatusCodes.OK, false); + } + else { + console.log(`File ${a.name} written successfully\n`); + } + }); + }); + } + + return req.session.sendResponse(res, StatusCodes.OK, true); + } + + private async createPortfolio(req: express.Request, res: express.Response) { + try { + const id = +req.params.userId; + + const dataUser = await db.user.findUnique({ + where: { + id: id, + }, + include: { + assignments: { + where: { + deleted: false + } + }, + exercises : true + }, + }); + + const folderPath = `/tmp/` + dataUser?.gitlabUsername; + const pathPortfolio = path.join(folderPath, "portfolio"); + + try { + fs.mkdirSync(pathPortfolio); + fs.mkdirSync(pathPortfolio + '/assignments'); + fs.mkdirSync(pathPortfolio + '/exercises'); + } catch (err) { + logger.error('Folder creation error'); + return req.session.sendResponse(res, StatusCodes.OK, false); + } + if (dataUser) { + dataUser.assignments.forEach(async a => { + const archive = await GitlabManager.archiveRepository(a.gitlabId); + const buffer = Buffer.from(await archive.arrayBuffer()); + const zipName = a.name.replace(/ /g, "_") + '.tar.gz'; + const zipPath = path.join(pathPortfolio+ '/assignments', zipName); + + fs.writeFile(zipPath, buffer, (err) => { + if (err) { + console.error('Error saving archive:', err); + } else { + console.log(`Archive ${zipName} saved successfully!`); + } + }); + }); + + dataUser.exercises.forEach(async a => { + const archive = await GitlabManager.archiveRepository(a.gitlabId); + const buffer = Buffer.from(await archive.arrayBuffer()); + const zipName = a.name.replace(/ /g, "_") + '.tar.gz'; + const zipPath = path.join(pathPortfolio + '/exercises', zipName); + + fs.writeFile(zipPath, buffer, (err) => { + if (err) { + console.error('Error saving archive:', err); + } else { + console.log(`Archive ${zipName} saved successfully!`); + } + }); + }); + return req.session.sendResponse(res, StatusCodes.OK, true); + } + } catch (error) { + console.error('Error exporting assignment:', error); + res.status(500).send('Error exporting assignment'); + } + } + + private async zipExport(req : express.Request, res : express.Response) { + const id = +req.params.userId; + const dataUser = await db.user.findUnique({ + where: { + id: id, + } + }); + if (dataUser) { + const folderPath = path.join('/tmp', `${dataUser.gitlabUsername}`); + const zipPath = path.join('/tmp', `${dataUser.gitlabUsername}.zip`); + const output = fs.createWriteStream(zipPath); + const archiveZip = archiver('zip', { + zlib: { level: 9 } // Compression maximale + }); + + try { + archiveZip.pipe(output); + archiveZip.directory(folderPath, false); + await archiveZip.finalize(); + } catch (error) { + console.error('Error exporting assignment:', error); + res.status(500).send('Error exporting assignment'); + } + + // Attendre la fin de l'écriture dans le fichier zip + output.on('close', function() { + const options = { + root: path.join('/tmp') + }; + + const fileName = `${dataUser.gitlabUsername}.zip`; + res.sendFile(fileName, options, function (err) { + if (err) { + console.error('Error sending file:', err); + } else { + console.log('Sent:', fileName); + fs.rmSync(folderPath, { recursive: true, force: true }); + fs.unlinkSync(zipPath); + } + }); + }); + } + } } export default new UserRoutes(); -- GitLab