diff --git a/ExpressAPI/assets/assignment_gitlab_ci.yml b/ExpressAPI/assets/assignment_gitlab_ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..ad20a046e037406773064a11f934ebcf69e42f77 --- /dev/null +++ b/ExpressAPI/assets/assignment_gitlab_ci.yml @@ -0,0 +1,26 @@ +################################################################################################################### +# DO NOT MODIFY THIS FILE +# This file is the ci/cd pipeline that will be used to test your assignment +################################################################################################################### + +variables: + GIT_SUBMODULE_STRATEGY: recursive + GIT_SUBMODULE_FORCE_HTTPS: "true" + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: + DOCKER_DRIVER: overlay2 + +stages: + - dojo + +dojo: + stage: dojo + tags: + - dojo_assignment + services: + - docker:dind + image: + name: dojohesso/dojo_assignment_checker:latest + script: + - dojo_assignment_checker + allow_failure: false \ No newline at end of file diff --git a/ExpressAPI/package-lock.json b/ExpressAPI/package-lock.json index 2a4fa5581141b6bddd9339f35135f03ed768b67c..8f963c7eac7601ac218372bff36cd4a480d1dc0e 100644 --- a/ExpressAPI/package-lock.json +++ b/ExpressAPI/package-lock.json @@ -16,6 +16,7 @@ "compression": "^1.7.4", "cors": "^2.8.5", "dotenv": "^16.3.1", + "dotenv-expand": "^10.0.0", "express": "^4.18.2", "express-validator": "^7.0.1", "form-data": "^4.0.0", @@ -1623,6 +1624,14 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "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==", + "engines": { + "node": ">=12" + } + }, "node_modules/dotenv-vault": { "version": "1.25.0", "resolved": "https://registry.npmjs.org/dotenv-vault/-/dotenv-vault-1.25.0.tgz", diff --git a/ExpressAPI/package.json b/ExpressAPI/package.json index 83a0dd6c7453a2906dc2b2b6f0915ed64602a7fb..c251fe54ccd93983fb524e22c7c20c6ff99e0388 100644 --- a/ExpressAPI/package.json +++ b/ExpressAPI/package.json @@ -28,6 +28,7 @@ "compression" : "^1.7.4", "cors" : "^2.8.5", "dotenv" : "^16.3.1", + "dotenv-expand" : "^10.0.0", "express" : "^4.18.2", "express-validator": "^7.0.1", "form-data" : "^4.0.0", diff --git a/ExpressAPI/src/app.ts b/ExpressAPI/src/app.ts index 1250af3da79a7764038ae9c16bc9934e2a4a53ed..bfbc0494636c237af8dae3185fc2670eb0c51b67 100644 --- a/ExpressAPI/src/app.ts +++ b/ExpressAPI/src/app.ts @@ -3,10 +3,12 @@ const path = require('node:path'); if ( process.env.NODE_ENV && process.env.NODE_ENV === 'production' ) { - require('dotenv').config(); + const myEnv = require('dotenv').config(); + require('dotenv-expand').expand(myEnv); } else { require('dotenv').config({ path: path.join(__dirname, '../.env.keys') }); - require('dotenv').config({ DOTENV_KEY: process.env.DOTENV_KEY_DEVELOPMENT }); + const myEnv = require('dotenv').config({ DOTENV_KEY: process.env.DOTENV_KEY_DEVELOPMENT }); + require('dotenv-expand').expand(myEnv); } require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be after the dotenv.config() calls diff --git a/ExpressAPI/src/config/Config.ts b/ExpressAPI/src/config/Config.ts index 49161651818ac940bc706b709375255564acaaff..574f9b2ea0e987c896a5075d4ac6dac7f7dbac55 100644 --- a/ExpressAPI/src/config/Config.ts +++ b/ExpressAPI/src/config/Config.ts @@ -24,7 +24,7 @@ class Config { }; public readonly gitlab: { - apiURL: string; urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; assignments: number; exercises: number; }, badges: { pipeline: ConfigGitlabBadge } + urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; assignments: number; exercises: number; }, badges: { pipeline: ConfigGitlabBadge } }; public readonly assignment: { @@ -55,7 +55,6 @@ class Config { }; this.gitlab = { - apiURL : process.env.GITLAB_API_URL || '', urls : JSON5.parse(process.env.GITLAB_URLS || '[]'), account: { id : Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0), diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts index fc80949e8f8a4878cf3d891a3467c907ecd09c2a..fca3977ce07f14eada038511f9106ccf539fba0a 100644 --- a/ExpressAPI/src/managers/GitlabManager.ts +++ b/ExpressAPI/src/managers/GitlabManager.ts @@ -11,11 +11,12 @@ import parseLinkHeader from 'parse-link-header'; import GitlabFile from '../shared/types/Gitlab/GitlabFile'; import express from 'express'; import GitlabRoute from '../shared/types/Gitlab/GitlabRoute'; +import SharedConfig from '../shared/config/SharedConfig'; class GitlabManager { private getApiUrl(route: GitlabRoute): string { - return `${ Config.gitlab.apiURL }${ route }`; + return `${ SharedConfig.gitlab.apiURL }${ route }`; } public async getUserById(id: number): Promise<GitlabUser | undefined> { diff --git a/ExpressAPI/src/managers/HttpManager.ts b/ExpressAPI/src/managers/HttpManager.ts index 80da4d4ec8abf644fa67a3bc6503f462e7625a07..6d04c68f0b0536bfa9bead9a6e78978bf5946c9b 100644 --- a/ExpressAPI/src/managers/HttpManager.ts +++ b/ExpressAPI/src/managers/HttpManager.ts @@ -2,6 +2,7 @@ import axios, { AxiosError, AxiosRequestHeaders } from 'axios'; import Config from '../config/Config'; import FormData from 'form-data'; import logger from '../shared/logging/WinstonLogger'; +import SharedConfig from '../shared/config/SharedConfig'; class HttpManager { @@ -16,7 +17,7 @@ class HttpManager { config.headers = { ...config.headers, ...(config.data as FormData).getHeaders() } as AxiosRequestHeaders; } - if ( config.url && config.url.indexOf(Config.gitlab.apiURL) !== -1 ) { + if ( config.url && config.url.indexOf(SharedConfig.gitlab.apiURL) !== -1 ) { config.headers['PRIVATE-TOKEN'] = Config.gitlab.account.token; } diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index af42e42fe67513b9c926e4c9de601aaed55b2e6a..0e8926263d792b77a20c60ca36b1c64134a5f3d5 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -20,16 +20,26 @@ import db from '../helpers/DatabaseHelper'; import { Assignment } from '../types/DatabaseTypes'; import AssignmentManager from '../managers/AssignmentManager'; import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; +import fs from 'fs'; +import path from 'path'; +import SharedAssignmentHelper from '../shared/helpers/Dojo/SharedAssignmentHelper'; 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 + name : { + trim : true, + notEmpty: true + }, + members : { + trim : true, + notEmpty : true, + customSanitizer: DojoValidators.jsonSanitizer + }, + template: { + trim : true, + custom : DojoValidators.templateUrlValidator, + customSanitizer: DojoValidators.templateUrlSanitizer } }; @@ -78,6 +88,8 @@ class AssignmentRoutes implements RoutesManager { 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); await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER); + + await GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status'); } catch ( error ) { if ( error instanceof AxiosError ) { if ( error.response?.data.message.name && error.response.data.message.name == 'has already been taken' ) { @@ -91,6 +103,18 @@ class AssignmentRoutes implements RoutesManager { return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send(); } + try { + await 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)'); + } catch ( error ) { + logger.error(error); + + if ( error instanceof AxiosError ) { + return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send(); + } + + return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send(); + } + try { await Promise.all(params.members.map(member => member.id).map(async (memberId: number): Promise<GitlabMember | false> => { try { @@ -102,12 +126,20 @@ class AssignmentRoutes implements RoutesManager { const assignment: Assignment = await 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: { + 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 : { - gitlabId: gitlabUser.id, firstname: gitlabUser.name - }, where: { + create: { + gitlabId : gitlabUser.id, + firstname: gitlabUser.name + }, + where : { gitlabId: gitlabUser.id } }; @@ -129,15 +161,23 @@ class AssignmentRoutes implements RoutesManager { 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 ) { + return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { lastPipeline: isPublishable.lastPipeline }, isPublishable.status?.message, isPublishable.status?.code); + } + } + try { await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE); await db.assignment.update({ - where : { + where: { name: req.boundParams.assignment!.name - }, data: { - published: publish - } + }, + data : { + published: publish + } }); req.session.sendResponse(res, StatusCodes.OK); diff --git a/ExpressAPI/src/shared b/ExpressAPI/src/shared index 8424367748a6fc47f8da10b85e7663f3f7d07620..efe1bf313f57d1826faf935c183d37a0835f8c2d 160000 --- a/ExpressAPI/src/shared +++ b/ExpressAPI/src/shared @@ -1 +1 @@ -Subproject commit 8424367748a6fc47f8da10b85e7663f3f7d07620 +Subproject commit efe1bf313f57d1826faf935c183d37a0835f8c2d