diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..93659703a0c2039ceefb0f06008028816a3da170 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,17 @@ +variables: + GIT_SUBMODULE_STRATEGY: recursive + GIT_SUBMODULE_FORCE_HTTPS: "true" + SECURE_FILES_DOWNLOAD_PATH: './' + +stages: + - test + +build:test: + image: node:latest + stage: test + tags: + - build + script: + - cd ExpressAPI + - npm install + - npm run build \ No newline at end of file diff --git a/ExpressAPI/assets/exercice_gitlab_ci.yml b/ExpressAPI/assets/exercise_gitlab_ci.yml similarity index 85% rename from ExpressAPI/assets/exercice_gitlab_ci.yml rename to ExpressAPI/assets/exercise_gitlab_ci.yml index 9a9877f10d3ff75170f2fcfa2dba9f7e9b51fd32..f658fa51d5b7dd40f4506c417c52da0e6acef99d 100644 --- a/ExpressAPI/assets/exercice_gitlab_ci.yml +++ b/ExpressAPI/assets/exercise_gitlab_ci.yml @@ -1,6 +1,6 @@ ################################################################################################################### # DO NOT MODIFY THIS FILE -# This file is the ci/cd pipeline that will be used to test your exercice +# This file is the ci/cd pipeline that will be used to test your exercise ################################################################################################################### variables: @@ -16,13 +16,13 @@ stages: dojo: stage: dojo tags: - - dojo_exercice + - dojo_exercise services: - docker:dind image: - name: dojohesso/dojo_exercice_checker:latest + name: dojohesso/dojo_exercise_checker:latest script: - - dojo_exercice_checker + - dojo_exercise_checker artifacts: when: always paths: diff --git a/ExpressAPI/package.json b/ExpressAPI/package.json index 558242be95902e6b48652a9813c7cdccc17de366..f15be4465c065ae5762785b7a9983e875a09edca 100644 --- a/ExpressAPI/package.json +++ b/ExpressAPI/package.json @@ -1,7 +1,7 @@ { "name" : "dojo_backend_api", "description" : "Backend API for the Dojo Project", - "version" : "1.0.1", + "version" : "2.0.0", "license" : "", "author" : "Michaƫl Minelli <michael-jean.minelli@hesge.ch>", "main" : "app.js", diff --git a/ExpressAPI/prisma/migrations/20230913150641_rename_enonce_and_exercice_to_assignment_and_exercise/migration.sql b/ExpressAPI/prisma/migrations/20230913150641_rename_enonce_and_exercice_to_assignment_and_exercise/migration.sql new file mode 100644 index 0000000000000000000000000000000000000000..34fe27141cb69eeacf4f09e7ac2cd554d9e02b4e --- /dev/null +++ b/ExpressAPI/prisma/migrations/20230913150641_rename_enonce_and_exercice_to_assignment_and_exercise/migration.sql @@ -0,0 +1,65 @@ +-- RenameTable +RENAME TABLE `Enonce` TO `Assignment`; + +-- RenameTable +RENAME TABLE `Exercice` TO `Exercise`; + +-- RenameTable +RENAME TABLE `_EnonceToUser` TO `_AssignmentToUser`; + +-- RenameTable +RENAME TABLE `_ExerciceToUser` TO `_ExerciseToUser`; + +-- AlterTable +ALTER TABLE `Exercise` RENAME COLUMN `enonceName` TO `assignmentName`; + +-- AlterTable +ALTER TABLE `Result` RENAME COLUMN `exerciceId` TO `exerciseId`; + +-- RenameIndex +ALTER TABLE `Exercise` RENAME INDEX `Exercice_secret_key` TO `Exercise_secret_key`; + +-- RenameIndex +ALTER TABLE `Exercise` RENAME INDEX `Exercice_enonceName_fkey` TO `Exercise_assignmentName_fkey`; + +-- RenameIndex +ALTER TABLE `_ExerciseToUser` RENAME INDEX `_ExerciceToUser_AB_unique` TO `_ExerciseToUser_AB_unique`; + +-- RenameIndex +ALTER TABLE `_ExerciseToUser` RENAME INDEX `_ExerciceToUser_B_index` TO `_ExerciseToUser_B_index`; + +-- RenameIndex +ALTER TABLE `_AssignmentToUser` RENAME INDEX `_EnonceToUser_AB_unique` TO `_AssignmentToUser_AB_unique`; + +-- RenameIndex +ALTER TABLE `_AssignmentToUser` RENAME INDEX `_EnonceToUser_B_index` TO `_AssignmentToUser_B_index`; + +-- Rename foreign key +ALTER TABLE `Exercise` + DROP FOREIGN KEY `Exercice_enonceName_fkey`, + ADD CONSTRAINT `Exercise_assignmentName_fkey` FOREIGN KEY (`assignmentName`) REFERENCES `Assignment`(`name`) ON DELETE NO ACTION ON UPDATE CASCADE; + +-- Rename foreign key +ALTER TABLE `Result` + DROP FOREIGN KEY `Result_exerciceId_fkey`, + ADD CONSTRAINT `Result_exerciseId_fkey` FOREIGN KEY (`exerciseId`) REFERENCES `Exercise`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- Rename foreign key +ALTER TABLE `_AssignmentToUser` + DROP FOREIGN KEY `_EnonceToUser_A_fkey`, + ADD CONSTRAINT `_AssignmentToUser_A_fkey` FOREIGN KEY (`A`) REFERENCES `Assignment`(`name`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- Rename foreign key +ALTER TABLE `_AssignmentToUser` + DROP FOREIGN KEY `_EnonceToUser_B_fkey`, + ADD CONSTRAINT `_AssignmentToUser_B_fkey` FOREIGN KEY (`B`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- Rename foreign key +ALTER TABLE `_ExerciseToUser` + DROP FOREIGN KEY `_ExerciceToUser_A_fkey`, + ADD CONSTRAINT `_ExerciseToUser_A_fkey` FOREIGN KEY (`A`) REFERENCES `Exercise`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- Rename foreign key +ALTER TABLE `_ExerciseToUser` + DROP FOREIGN KEY `_ExerciceToUser_B_fkey`, + ADD CONSTRAINT `_ExerciseToUser_B_fkey` FOREIGN KEY (`B`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma index 7f3f418b1e9fac3d2d952ea3fda1e1289b41a51c..df6640eb8f98e4e2c1802ac29ac7c194f3800b28 100644 --- a/ExpressAPI/prisma/schema.prisma +++ b/ExpressAPI/prisma/schema.prisma @@ -17,11 +17,11 @@ model User { role String? deleted Boolean @default(false) - enonces Enonce[] - exercices Exercice[] + assignments Assignment[] + exercises Exercise[] } -model Enonce { +model Assignment { name String @id gitlabId Int gitlabLink String @@ -30,13 +30,13 @@ model Enonce { gitlabLastInfoDate DateTime published Boolean @default(false) - exercices Exercice[] + exercises Exercise[] staff User[] } -model Exercice { +model Exercise { id String @id @db.Char(36) - enonceName String + assignmentName String name String secret String @unique @db.Char(36) gitlabId Int @@ -45,14 +45,14 @@ model Exercice { gitlabLastInfo Json @db.Json gitlabLastInfoDate DateTime - enonce Enonce @relation(fields: [enonceName], references: [name], onDelete: NoAction, onUpdate: Cascade) + assignment Assignment @relation(fields: [assignmentName], references: [name], onDelete: NoAction, onUpdate: Cascade) members User[] results Result[] } model Result { - exerciceId String @db.Char(36) + exerciseId String @db.Char(36) dateTime DateTime @default(now()) success Boolean exitCode Int @@ -60,7 +60,7 @@ model Result { results Json @db.Json files Json @db.Json - exercice Exercice @relation(fields: [exerciceId], references: [id], onDelete: Cascade, onUpdate: Cascade) + exercise Exercise @relation(fields: [exerciseId], references: [id], onDelete: Cascade, onUpdate: Cascade) - @@id([exerciceId, dateTime]) + @@id([exerciseId, dateTime]) } diff --git a/ExpressAPI/src/config/Config.ts b/ExpressAPI/src/config/Config.ts index 83edf27abaa5442c746cdc427cccae875c2686c2..a88b015f2497e28edbacfc5f054c325c77c5bb1c 100644 --- a/ExpressAPI/src/config/Config.ts +++ b/ExpressAPI/src/config/Config.ts @@ -1,7 +1,7 @@ import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; -import { Exercice } from '../types/DatabaseTypes'; import path from 'path'; import fs from 'fs'; +import { Exercise } from '../types/DatabaseTypes'; class Config { @@ -18,14 +18,14 @@ class Config { }; public gitlab: { - apiURL: string; urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; enonces: number; exercices: number; }; + apiURL: string; urls: Array<string>; account: { id: number; username: string; token: string; }; group: { root: number; templates: number; assignments: number; exercises: number; }; }; - public enonce: { + public assignment: { default: { description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: string; wikiEnabled: boolean; template: string }; baseFiles: Array<string>; filename: string }; - public exercice: { + public exercise: { maxSameName: number; resultsFolder: string, pipelineResultsFolder: string; default: { description: string; visibility: string; }; }; @@ -38,8 +38,7 @@ class Config { }; this.jwtConfig = { - secret : process.env.JWT_SECRET_KEY || '', - expiresIn: Number(process.env.SESSION_TIMEOUT || 0) + secret: process.env.JWT_SECRET_KEY || '', expiresIn: Number(process.env.SESSION_TIMEOUT || 0) }; this.permissions = { @@ -47,41 +46,23 @@ class Config { }; this.gitlab = { - apiURL : process.env.GITLAB_API_URL || '', - urls : JSON.parse(process.env.GITLAB_URLS || '[]'), - account: { - id : Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0), - username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '', - token : process.env.GITLAB_DOJO_ACCOUNT_TOKEN || '' - }, - group : { - root : Number(process.env.GITLAB_GROUP_ROOT_ID || 0), - templates: Number(process.env.GITLAB_GROUP_TEMPLATES_ID || 0), - enonces : Number(process.env.GITLAB_GROUP_ENONCES_ID || 0), - exercices: Number(process.env.GITLAB_GROUP_EXERCICES_ID || 0) + apiURL : process.env.GITLAB_API_URL || '', urls: JSON.parse(process.env.GITLAB_URLS || '[]'), account: { + id: Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0), username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '', token: process.env.GITLAB_DOJO_ACCOUNT_TOKEN || '' + }, group: { + root: Number(process.env.GITLAB_GROUP_ROOT_ID || 0), templates: Number(process.env.GITLAB_GROUP_TEMPLATES_ID || 0), assignments: Number(process.env.GITLAB_GROUP_ASSIGNMENTS_ID || 0), exercises: Number(process.env.GITLAB_GROUP_EXERCISES_ID || 0) } }; - this.enonce = { - default : { - description : process.env.ENONCE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', - initReadme : process.env.ENONCE_DEFAULT_INIT_README?.toBoolean() ?? false, - sharedRunnersEnabled: process.env.ENONCE_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true, - visibility : process.env.ENONCE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE, - wikiEnabled : process.env.ENONCE_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false, - template : process.env.ENONCE_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? '' - }, - baseFiles: JSON.parse(process.env.ENONCE_BASE_FILES || '[]'), - filename : process.env.ENONCE_FILENAME || '' + this.assignment = { + default : { + description: process.env.ASSIGNMENT_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', initReadme: process.env.ASSIGNMENT_DEFAULT_INIT_README?.toBoolean() ?? false, sharedRunnersEnabled: process.env.ASSIGNMENT_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true, visibility: process.env.ASSIGNMENT_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE, wikiEnabled: process.env.ASSIGNMENT_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false, template: process.env.ASSIGNMENT_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? '' + }, baseFiles: JSON.parse(process.env.ASSIGNMENT_BASE_FILES || '[]'), filename: process.env.ASSIGNMENT_FILENAME || '' }; - this.exercice = { - maxSameName : Number(process.env.EXERCICE_MAX_SAME_NAME || 0), - resultsFolder : process.env.EXERCICE_RESULTS_FOLDER?.convertWithEnvVars() ?? '', - pipelineResultsFolder: process.env.EXERCICE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercice creation and muste be interpreted at exercice runtime - default : { - description: process.env.EXERCICE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', - visibility : process.env.EXERCICE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE + this.exercise = { + maxSameName: Number(process.env.EXERCISE_MAX_SAME_NAME || 0), resultsFolder: process.env.EXERCISE_RESULTS_FOLDER?.convertWithEnvVars() ?? '', pipelineResultsFolder: process.env.EXERCISE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercise creation and muste be interpreted at exercise runtime + default : { + description: process.env.EXERCISE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', visibility: process.env.EXERCISE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE } }; @@ -89,8 +70,8 @@ class Config { this.userPasswordSaltRounds = Number(process.env.USER_PASSWORD_SALT_ROUNDS || 10); } - public getResultsFolder(exercice: Exercice): string { - const folderPath = path.join(this.exercice.resultsFolder, exercice.enonceName, exercice.id); + public getResultsFolder(exercise: Exercise): string { + const folderPath = path.join(this.exercise.resultsFolder, exercise.assignmentName, exercise.id); fs.mkdirSync(folderPath, { recursive: true }); diff --git a/ExpressAPI/src/controllers/Session.ts b/ExpressAPI/src/controllers/Session.ts index 7f71824b3374e7a68660eb89e838b07b5e1d18a7..fd6b85ff39d9e240795a178a1da7bdc2ab84fcc9 100644 --- a/ExpressAPI/src/controllers/Session.ts +++ b/ExpressAPI/src/controllers/Session.ts @@ -4,8 +4,8 @@ import { JwtPayload } from 'jsonwebtoken'; import Config from '../config/Config'; import express from 'express'; import UserManager from '../managers/UserManager'; -import DojoResponse from '../shared/types/Dojo/DojoResponse'; import { User } from '../types/DatabaseTypes'; +import DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse'; class Session { @@ -46,7 +46,7 @@ class Session { return profileJson === null ? null : jwt.sign({ profile: profileJson }, Config.jwtConfig.secret, Config.jwtConfig.expiresIn > 0 ? { expiresIn: Config.jwtConfig.expiresIn } : {}); } - private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoResponse<T>> { + private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoBackendResponse<T>> { const profileJson = this.profile; let reasonPhrase = ''; diff --git a/ExpressAPI/src/helpers/DojoValidators.ts b/ExpressAPI/src/helpers/DojoValidators.ts index c4430360bb482496c6078ba4f8a12eff905e4cd1..ccb2b6575b637cfe2cb226498cd0b24d93e43dc3 100644 --- a/ExpressAPI/src/helpers/DojoValidators.ts +++ b/ExpressAPI/src/helpers/DojoValidators.ts @@ -4,7 +4,7 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres import { BailOptions, ValidationChain } from 'express-validator/src/chain'; import GitlabManager from '../managers/GitlabManager'; import express from 'express'; -import SharedExerciceHelper from '../shared/helpers/Dojo/SharedExerciceHelper'; +import SharedExerciseHelper from '../shared/helpers/Dojo/SharedExerciseHelper'; declare type DojoMeta = Meta & { @@ -75,7 +75,7 @@ class DojoValidators { if ( template ) { return `${ Config.gitlab.urls[0].replace(/^([a-z]{3,5}:\/{2})?(.*)/, `$1${ Config.gitlab.account.username }:${ Config.gitlab.account.token }@$2`) }${ template }.git`; } else { - return Config.enonce.default.template; + return Config.assignment.default.template; } } catch ( e ) { } @@ -83,7 +83,7 @@ class DojoValidators { } }); - readonly exerciceResultsValidator = this.toValidatorSchemaOptions({ + readonly exerciseResultsValidator = this.toValidatorSchemaOptions({ bail : true, errorMessage: 'Results: not provided or invalid format', options : (_value, { @@ -93,7 +93,7 @@ class DojoValidators { return new Promise((resolve, reject) => { const results = this.getParamValue(req, path); if ( results ) { - SharedExerciceHelper.validateResultFile(results, false).isValid ? resolve(true) : reject(); + SharedExerciseHelper.validateResultFile(results, false).isValid ? resolve(true) : reject(); } else { reject(); } diff --git a/ExpressAPI/src/managers/AssignmentManager.ts b/ExpressAPI/src/managers/AssignmentManager.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f2bc54dcf9c68fb267628d2dc32a1e201bb6284 --- /dev/null +++ b/ExpressAPI/src/managers/AssignmentManager.ts @@ -0,0 +1,39 @@ +import { Prisma } from '@prisma/client'; +import { Assignment, User } from '../types/DatabaseTypes'; +import db from '../helpers/DatabaseHelper'; + + +class AssignmentManager { + async isUserAllowedToAccessAssignment(assignment: Assignment, user: User): Promise<boolean> { + if ( !assignment.staff ) { + assignment.staff = await db.assignment.findUnique({ + where: { + name: assignment.name + } + }).staff() ?? []; + } + return assignment.staff.findIndex(staff => staff.id === user.id) !== -1; + } + + async getByName(name: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> { + return await db.assignment.findUnique({ + where : { + name: name + }, include: include + }) as unknown as Assignment ?? undefined; + } + + getByGitlabLink(gitlabLink: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> { + const name = gitlabLink.replace('.git', '').split('/').pop()!; + + return this.getByName(name, include); + } + + get(nameOrUrl: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> { + // We can use the same function for both name and url because the name is the last part of the url and the name extraction from the url doesn't corrupt the name + return this.getByGitlabLink(nameOrUrl, include); + } +} + + +export default new AssignmentManager(); diff --git a/ExpressAPI/src/managers/EnonceManager.ts b/ExpressAPI/src/managers/EnonceManager.ts deleted file mode 100644 index 7c8b612cae69bebb9de77125719c639e424054d5..0000000000000000000000000000000000000000 --- a/ExpressAPI/src/managers/EnonceManager.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Prisma } from '@prisma/client'; -import { Enonce, User } from '../types/DatabaseTypes'; -import db from '../helpers/DatabaseHelper'; - - -class EnonceManager { - async isUserAllowedToAccessEnonce(enonce: Enonce, user: User): Promise<boolean> { - if ( !enonce.staff ) { - enonce.staff = await db.enonce.findUnique({ - where: { - name: enonce.name - } - }).staff() ?? []; - } - return enonce.staff.findIndex(staff => staff.id === user.id) !== -1; - } - - async getByName(name: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> { - return await db.enonce.findUnique({ - where : { - name: name - }, - include: include - }) as unknown as Enonce ?? undefined; - } - - getByGitlabLink(gitlabLink: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> { - const name = gitlabLink.replace('.git', '').split('/').pop()!; - - return this.getByName(name, include); - } - - get(nameOrUrl: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> { - // We can use the same function for both name and url because the name is the last part of the url and the name extraction from the url doesn't corrupt the name - return this.getByGitlabLink(nameOrUrl, include); - } -} - - -export default new EnonceManager(); diff --git a/ExpressAPI/src/managers/ExerciceManager.ts b/ExpressAPI/src/managers/ExerciseManager.ts similarity index 57% rename from ExpressAPI/src/managers/ExerciceManager.ts rename to ExpressAPI/src/managers/ExerciseManager.ts index 698e16f2da86c1776b636af0b70a4faa9b00de38..95bc487f8fadb40b84e11cb4253556b3bcb4edb9 100644 --- a/ExpressAPI/src/managers/ExerciceManager.ts +++ b/ExpressAPI/src/managers/ExerciseManager.ts @@ -1,18 +1,18 @@ import { Prisma } from '@prisma/client'; -import { Exercice } from '../types/DatabaseTypes'; +import { Exercise } from '../types/DatabaseTypes'; import db from '../helpers/DatabaseHelper'; -class ExerciceManager { - async get(id: string, include: Prisma.ExerciceInclude | undefined = undefined): Promise<Exercice | undefined> { - return await db.exercice.findUnique({ +class ExerciseManager { + async get(id: string, include: Prisma.ExerciseInclude | undefined = undefined): Promise<Exercise | undefined> { + return await db.exercise.findUnique({ where : { id: id }, include: include - }) as unknown as Exercice ?? undefined; + }) as unknown as Exercise ?? undefined; } } -export default new ExerciceManager(); +export default new ExerciseManager(); diff --git a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts index 64a7bda72a9445ce4b755b4e48013fe5df57c3b9..e81e172e444e0ccaf8723fef8a31a658dbdf2eb1 100644 --- a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts +++ b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts @@ -1,8 +1,8 @@ -import { Express } from 'express-serve-static-core'; -import express from 'express'; -import { StatusCodes } from 'http-status-codes'; -import EnonceManager from '../managers/EnonceManager'; -import ExerciceManager from '../managers/ExerciceManager'; +import { Express } from 'express-serve-static-core'; +import express from 'express'; +import { StatusCodes } from 'http-status-codes'; +import ExerciseManager from '../managers/ExerciseManager'; +import AssignmentManager from '../managers/AssignmentManager'; type GetFunction = (id: string | number, ...args: Array<any>) => Promise<any> @@ -27,23 +27,23 @@ class ParamsCallbackManager { initBoundParams(req: express.Request) { if ( !req.boundParams ) { req.boundParams = { - enonce : undefined, - exercice: undefined + assignment: undefined, + exercise : undefined }; } } register(backend: Express) { - this.listenParam('enonceNameOrUrl', backend, (EnonceManager.get as GetFunction).bind(EnonceManager), [ { - exercices: true, + this.listenParam('assignmentNameOrUrl', backend, (AssignmentManager.get as GetFunction).bind(AssignmentManager), [ { + exercises: true, staff : true - } ], 'enonce'); + } ], 'assignment'); - this.listenParam('exerciceId', backend, (ExerciceManager.get as GetFunction).bind(ExerciceManager), [ { - enonce : true, - members: true, - results: true - } ], 'exercice'); + this.listenParam('exerciseId', backend, (ExerciseManager.get as GetFunction).bind(ExerciseManager), [ { + assignment: true, + members : true, + results : true + } ], 'exercise'); } } diff --git a/ExpressAPI/src/middlewares/SecurityMiddleware.ts b/ExpressAPI/src/middlewares/SecurityMiddleware.ts index 9ba9c2f41de95600760aac0dde6549236b5018b7..cc7e06799011d2fd0a7201f9efd3719f81499955 100644 --- a/ExpressAPI/src/middlewares/SecurityMiddleware.ts +++ b/ExpressAPI/src/middlewares/SecurityMiddleware.ts @@ -2,7 +2,7 @@ import express from 'express'; import { StatusCodes } from 'http-status-codes'; import SecurityCheckType from '../types/SecurityCheckType'; import logger from '../shared/logging/WinstonLogger'; -import EnonceManager from '../managers/EnonceManager'; +import AssignmentManager from '../managers/AssignmentManager'; class SecurityMiddleware { @@ -24,14 +24,14 @@ class SecurityMiddleware { case SecurityCheckType.TEACHING_STAFF: isAllowed = isAllowed || req.session.profile.isTeachingStaff; break; - case SecurityCheckType.ENONCE_STAFF: - isAllowed = isAllowed || await EnonceManager.isUserAllowedToAccessEnonce(req.boundParams.enonce!, req.session.profile); + case SecurityCheckType.ASSIGNMENT_STAFF: + isAllowed = isAllowed || await AssignmentManager.isUserAllowedToAccessAssignment(req.boundParams.assignment!, req.session.profile); break; - case SecurityCheckType.ENONCE_IS_PUBLISHED: - isAllowed = isAllowed || (req.boundParams.enonce?.published ?? false); + case SecurityCheckType.ASSIGNMENT_IS_PUBLISHED: + isAllowed = isAllowed || (req.boundParams.assignment?.published ?? false); break; - case SecurityCheckType.EXERCICE_SECRET: - isAllowed = isAllowed || req.headers.authorization?.replace('ExerciceSecret ', '') === req.boundParams.exercice!.secret; + case SecurityCheckType.EXERCISE_SECRET: + isAllowed = isAllowed || req.headers.authorization?.replace('ExerciseSecret ', '') === req.boundParams.exercise!.secret; break; default: break; diff --git a/ExpressAPI/src/routes/ApiRoutesManager.ts b/ExpressAPI/src/routes/ApiRoutesManager.ts index d7641bc9c66668f9983e8ac70379fe375e5b5f48..ec4eeb909e0580901c56fcf3fda13ce88c675149 100644 --- a/ExpressAPI/src/routes/ApiRoutesManager.ts +++ b/ExpressAPI/src/routes/ApiRoutesManager.ts @@ -1,10 +1,10 @@ -import { Express } from 'express-serve-static-core'; -import RoutesManager from '../express/RoutesManager'; -import BaseRoutes from './BaseRoutes'; -import SessionRoutes from './SessionRoutes'; -import EnonceRoutes from './EnonceRoutes'; -import GitlabRoutes from './GitlabRoutes'; -import ExerciceRoutes from './ExerciceRoutes'; +import { Express } from 'express-serve-static-core'; +import RoutesManager from '../express/RoutesManager'; +import BaseRoutes from './BaseRoutes'; +import SessionRoutes from './SessionRoutes'; +import AssignmentRoutes from './AssignmentRoutes'; +import GitlabRoutes from './GitlabRoutes'; +import ExerciseRoutes from './ExerciseRoutes'; class AdminRoutesManager implements RoutesManager { @@ -12,8 +12,8 @@ class AdminRoutesManager implements RoutesManager { BaseRoutes.registerOnBackend(backend); SessionRoutes.registerOnBackend(backend); GitlabRoutes.registerOnBackend(backend); - EnonceRoutes.registerOnBackend(backend); - ExerciceRoutes.registerOnBackend(backend); + AssignmentRoutes.registerOnBackend(backend); + ExerciseRoutes.registerOnBackend(backend); } } diff --git a/ExpressAPI/src/routes/EnonceRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts similarity index 54% rename from ExpressAPI/src/routes/EnonceRoutes.ts rename to ExpressAPI/src/routes/AssignmentRoutes.ts index ddff650df3e4330e579b4d086ba4b3a86d2bb35c..af42e42fe67513b9c926e4c9de601aaed55b2e6a 100644 --- a/ExpressAPI/src/routes/EnonceRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -17,62 +17,55 @@ import logger from '../shared/logging/WinstonLogger'; import DojoValidators from '../helpers/DojoValidators'; import { Prisma } from '@prisma/client'; import db from '../helpers/DatabaseHelper'; -import { Enonce } from '../types/DatabaseTypes'; -import EnonceManager from '../managers/EnonceManager'; +import { Assignment } from '../types/DatabaseTypes'; +import AssignmentManager from '../managers/AssignmentManager'; import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; -class EnonceRoutes implements RoutesManager { - private readonly enonceValidator: ExpressValidator.Schema = { - name : { - trim : true, - notEmpty: true - }, - members : { - trim : true, - notEmpty : true, - customSanitizer: DojoValidators.jsonSanitizer - }, - template: { - trim : true, - custom : DojoValidators.templateUrlValidator, - customSanitizer: DojoValidators.templateUrlSanitizer +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 } }; registerOnBackend(backend: Express) { - backend.get('/enonces/:enonceNameOrUrl', SecurityMiddleware.check(true), this.getEnonce); - backend.post('/enonces', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.enonceValidator), this.createEnonce); + backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment); + backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment); - backend.patch('/enonces/:enonceNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_STAFF), this.changeEnoncePublishedStatus(true)); - backend.patch('/enonces/:enonceNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_STAFF), this.changeEnoncePublishedStatus(false)); + backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true)); + backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false)); } - // Get an enonce by its name or gitlab url - private async getEnonce(req: express.Request, res: express.Response) { - const enonce: Enonce | undefined = req.boundParams.enonce; + // Get an assignment by its name or gitlab url + private async getAssignment(req: express.Request, res: express.Response) { + const assignment: Assignment | undefined = req.boundParams.assignment; - if ( enonce && !enonce.published && !await EnonceManager.isUserAllowedToAccessEnonce(enonce, req.session.profile) ) { + if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment, req.session.profile) ) { // @ts-ignore - delete enonce.gitlabId; + delete assignment.gitlabId; // @ts-ignore - delete enonce.gitlabLink; + delete assignment.gitlabLink; // @ts-ignore - delete enonce.gitlabCreationInfo; + delete assignment.gitlabCreationInfo; // @ts-ignore - delete enonce.gitlabLastInfo; + delete assignment.gitlabLastInfo; // @ts-ignore - delete enonce.gitlabLastInfoDate; + delete assignment.gitlabLastInfoDate; // @ts-ignore - delete enonce.staff; + delete assignment.staff; // @ts-ignore - delete enonce.exercices; + delete assignment.exercises; } - return enonce ? req.session.sendResponse(res, StatusCodes.OK, enonce) : res.status(StatusCodes.NOT_FOUND).send(); + return assignment ? req.session.sendResponse(res, StatusCodes.OK, assignment) : res.status(StatusCodes.NOT_FOUND).send(); } - private async createEnonce(req: express.Request, res: express.Response) { + private async createAssignment(req: express.Request, res: express.Response) { const params: { name: string, members: Array<GitlabUser>, template: string } = req.body; @@ -82,7 +75,7 @@ class EnonceRoutes implements RoutesManager { let repository: GitlabRepository; try { - repository = await GitlabManager.createRepository(params.name, Config.enonce.default.description.replace('{{ENONCE_NAME}}', params.name), Config.enonce.default.visibility, Config.enonce.default.initReadme, Config.gitlab.group.enonces, Config.enonce.default.sharedRunnersEnabled, Config.enonce.default.wikiEnabled, params.template); + 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); } catch ( error ) { @@ -107,31 +100,23 @@ class EnonceRoutes implements RoutesManager { } })); - const enonce: Enonce = await db.enonce.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: { - gitlabId : gitlabUser.id, - firstname: gitlabUser.name - }, - where : { - gitlabId: gitlabUser.id + 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: { + connectOrCreate: [ ...params.members.map(gitlabUser => { + return { + create : { + gitlabId: gitlabUser.id, firstname: gitlabUser.name + }, where: { + gitlabId: gitlabUser.id + } + }; + }) ] } - }; - }) ] - } - } - }) as unknown as Enonce; + } + }) as unknown as Assignment; - return req.session.sendResponse(res, StatusCodes.OK, enonce); + return req.session.sendResponse(res, StatusCodes.OK, assignment); } catch ( error ) { if ( error instanceof AxiosError ) { return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send(); @@ -142,19 +127,18 @@ class EnonceRoutes implements RoutesManager { } } - private changeEnoncePublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> { + private changeAssignmentPublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> { return async (req: express.Request, res: express.Response): Promise<void> => { try { - await GitlabManager.changeRepositoryVisibility(req.boundParams.enonce!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE); + await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE); - await db.enonce.update({ - where: { - name: req.boundParams.enonce!.name - }, - data : { - published: publish - } - }); + await db.assignment.update({ + where : { + name: req.boundParams.assignment!.name + }, data: { + published: publish + } + }); req.session.sendResponse(res, StatusCodes.OK); } catch ( error ) { @@ -172,4 +156,4 @@ class EnonceRoutes implements RoutesManager { } -export default new EnonceRoutes(); +export default new AssignmentRoutes(); diff --git a/ExpressAPI/src/routes/ExerciceRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts similarity index 53% rename from ExpressAPI/src/routes/ExerciceRoutes.ts rename to ExpressAPI/src/routes/ExerciseRoutes.ts index f4a2e389fb392538f0f0dd315c33f4ad5f1d88b6..e6f60a5663bee5bff7e4c367b09621eff23bf8b7 100644 --- a/ExpressAPI/src/routes/ExerciceRoutes.ts +++ b/ExpressAPI/src/routes/ExerciseRoutes.ts @@ -16,91 +16,76 @@ 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 { Assignment, Exercise } 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 EnonceFile from '../shared/types/Dojo/EnonceFile'; import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType'; import JSON5 from 'json5'; -import ExerciceResultsFile from '../shared/types/Dojo/ExerciceResultsFile'; import fs from 'fs'; import path from 'path'; +import AssignmentFile from '../shared/types/Dojo/AssignmentFile'; +import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile'; -class ExerciceRoutes implements RoutesManager { - private readonly exerciceValidator: ExpressValidator.Schema = { +class ExerciseRoutes implements RoutesManager { + private readonly exerciseValidator: ExpressValidator.Schema = { members: { - trim : true, - notEmpty : true, - customSanitizer: DojoValidators.jsonSanitizer + trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer } }; private readonly resultValidator: ExpressValidator.Schema = { - exitCode : { - isInt: true, - toInt: true - }, - commit : { - trim : true, - notEmpty : true, - customSanitizer: DojoValidators.jsonSanitizer - }, - results : { - trim : true, - notEmpty : true, - custom : DojoValidators.exerciceResultsValidator, - customSanitizer: DojoValidators.jsonSanitizer - }, - files : { - trim : true, - notEmpty : true, - customSanitizer: DojoValidators.jsonSanitizer - }, - archiveBase64: { - isBase64: true, - notEmpty: true + exitCode : { + isInt: true, toInt: true + }, commit : { + trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer + }, results : { + trim: true, notEmpty: true, custom: DojoValidators.exerciseResultsValidator, customSanitizer: DojoValidators.jsonSanitizer + }, files : { + trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer + }, archiveBase64: { + isBase64: true, notEmpty: true } }; registerOnBackend(backend: Express) { - backend.post('/enonces/:enonceNameOrUrl/exercices', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciceValidator), this.createExercice.bind(this)); + backend.post('/assignments/:assignmentNameOrUrl/exercises', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciseValidator), this.createExercise.bind(this)); - backend.get('/exercices/:exerciceId/enonce', SecurityMiddleware.check(false, SecurityCheckType.EXERCICE_SECRET), this.getEnonce.bind(this)); + backend.get('/exercises/:exerciseId/assignment', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), this.getAssignment.bind(this)); - backend.post('/exercices/:exerciceId/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCICE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this)); + backend.post('/exercises/:exerciseId/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.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 getExerciseName(assignment: Assignment, members: Array<GitlabUser>, suffix: number): string { + return `DojoEx - ${ assignment.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 getExercisePath(assignment: Assignment, exerciseId: string): string { + return `dojo-ex_${ (assignment.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciseId }`; } - private async createExercice(req: express.Request, res: express.Response) { + private async createExercise(req: express.Request, 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 assignment: Assignment = req.boundParams.assignment!; - const exerciceId: string = uuidv4(); + const exerciseId: 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); + repository = await GitlabManager.forkRepository((assignment.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciseName(assignment, params.members, suffix), this.getExercisePath(req.boundParams.assignment!, exerciseId), Config.exercise.default.description.replace('{{ASSIGNMENT_NAME}}', assignment.name), Config.exercise.default.visibility, Config.gitlab.group.exercises); 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_EXERCISE_ID', exerciseId, false, true); await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_SECRET', secret, false, true); - await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_RESULTS_FOLDER', Config.exercice.pipelineResultsFolder, false, false); + await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_RESULTS_FOLDER', Config.exercise.pipelineResultsFolder, false, false); break; } catch ( error ) { @@ -114,14 +99,14 @@ class ExerciceRoutes implements RoutesManager { return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send(); } } - } while ( suffix < Config.exercice.maxSameName ); + } while ( suffix < Config.exercise.maxSameName ); - if ( suffix >= Config.exercice.maxSameName ) { + if ( suffix >= Config.exercise.maxSameName ) { return res.status(StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE).send(); } try { - await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/exercice_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)'); + await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/exercise_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)'); } catch ( error ) { logger.error(error); @@ -133,7 +118,7 @@ class ExerciceRoutes implements RoutesManager { } 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> => { + await Promise.all([ ...new Set([ ...assignment.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 ) { @@ -141,37 +126,26 @@ class ExerciceRoutes implements RoutesManager { } })); - const exercice: Exercice = await db.exercice.create({ + const exercise: Exercise = await db.exercise.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 : { + id: exerciseId, assignmentName: assignment.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 : { + create : { + gitlabId: gitlabUser.id, firstname: gitlabUser.name + }, where: { gitlabId: gitlabUser.id } }; }) ] } } - }) as unknown as Exercice; + }) as unknown as Exercise; - return req.session.sendResponse(res, StatusCodes.OK, exercice); + return req.session.sendResponse(res, StatusCodes.OK, exercise); } catch ( error ) { logger.error(error); - + if ( error instanceof AxiosError ) { return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send(); } @@ -180,29 +154,29 @@ class ExerciceRoutes implements RoutesManager { } } - private async getEnonce(req: express.Request, res: express.Response) { - const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercice!.enonce.gitlabId); + private async getAssignment(req: express.Request, res: express.Response) { + const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId); - let enonceHjsonFile!: GitlabFile; - let immutableFiles: Array<GitlabFile> = await Promise.all(Config.enonce.baseFiles.map(async (baseFile: string) => { - let file = await GitlabManager.getFile(req.boundParams.exercice!.enonce.gitlabId, baseFile); + let assignmentHjsonFile!: GitlabFile; + let immutableFiles: Array<GitlabFile> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => { + let file = await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, baseFile); - if ( baseFile === Config.enonce.filename ) { - enonceHjsonFile = file; + if ( baseFile === Config.assignment.filename ) { + assignmentHjsonFile = file; } return file; })); - const dojoEnonceFile: EnonceFile = JSON5.parse(atob(enonceHjsonFile.content)) as EnonceFile; + const dojoAssignmentFile: AssignmentFile = JSON5.parse(atob(assignmentHjsonFile.content)) as AssignmentFile; - const immutablePaths = dojoEnonceFile.immutable.map(fileDescriptor => fileDescriptor.path); + const immutablePaths = dojoAssignmentFile.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!.enonce.gitlabId, gitlabTreeFile.path)); + immutableFiles.push(await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, gitlabTreeFile.path)); break; } } @@ -210,32 +184,25 @@ class ExerciceRoutes implements RoutesManager { })); return req.session.sendResponse(res, StatusCodes.OK, { - enonce : (req.boundParams.exercice as Exercice).enonce, - enonceFile: dojoEnonceFile, - immutable : immutableFiles + assignment: (req.boundParams.exercise as Exercise).assignment, assignmentFile: dojoAssignmentFile, immutable: immutableFiles }); } private async createResult(req: express.Request, res: express.Response) { - const params: { exitCode: number, commit: any, results: ExerciceResultsFile, files: any, archiveBase64: string } = req.body; - const exercice: Exercice = req.boundParams.exercice!; + const params: { exitCode: number, commit: any, results: ExerciseResultsFile, files: any, archiveBase64: string } = req.body; + const exercise: Exercise = req.boundParams.exercise!; const result = await db.result.create({ data: { - exerciceId: exercice.id, - exitCode : params.exitCode, - success : params.results.success, - commit : params.commit, - results : params.results as unknown as Prisma.JsonObject, - files : params.files + exerciseId: exercise.id, exitCode: params.exitCode, success: params.results.success, commit: params.commit, results: params.results as unknown as Prisma.JsonObject, files: params.files } }); - fs.writeFileSync(path.join(Config.getResultsFolder(exercice), `${ result.dateTime.toISOString().replace(/:/g, '_') }.tar.gz`), params.archiveBase64, 'base64'); + fs.writeFileSync(path.join(Config.getResultsFolder(exercise), `${ result.dateTime.toISOString().replace(/:/g, '_') }.tar.gz`), params.archiveBase64, 'base64'); req.session.sendResponse(res, StatusCodes.OK); } } -export default new ExerciceRoutes(); +export default new ExerciseRoutes(); diff --git a/ExpressAPI/src/routes/GitlabRoutes.ts b/ExpressAPI/src/routes/GitlabRoutes.ts index ad03cfca87eb268d9cac241ec744de8df7779d81..40ad2d9e39e28303a69aa65d60671bdcecd6326b 100644 --- a/ExpressAPI/src/routes/GitlabRoutes.ts +++ b/ExpressAPI/src/routes/GitlabRoutes.ts @@ -6,7 +6,7 @@ import SecurityCheckType from '../types/SecurityCheckType'; import GitlabManager from '../managers/GitlabManager'; -class EnonceRoutes implements RoutesManager { +class GitlabRoutes implements RoutesManager { registerOnBackend(backend: Express) { backend.get('/gitlab/project/:idOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess); } @@ -19,4 +19,4 @@ class EnonceRoutes implements RoutesManager { } -export default new EnonceRoutes(); +export default new GitlabRoutes(); diff --git a/ExpressAPI/src/shared b/ExpressAPI/src/shared index f33e4e0c7b34f9060e8995550920d25cd3e73c40..4cbcc7398459d7a2de027292fa0522c7fa592935 160000 --- a/ExpressAPI/src/shared +++ b/ExpressAPI/src/shared @@ -1 +1 @@ -Subproject commit f33e4e0c7b34f9060e8995550920d25cd3e73c40 +Subproject commit 4cbcc7398459d7a2de027292fa0522c7fa592935 diff --git a/ExpressAPI/src/types/DatabaseTypes.ts b/ExpressAPI/src/types/DatabaseTypes.ts index 4627dcb73f3d36015985266c0c57ff7fbcb68453..bdb25f8c14088d4dcc8524f309a52d5380556a1c 100644 --- a/ExpressAPI/src/types/DatabaseTypes.ts +++ b/ExpressAPI/src/types/DatabaseTypes.ts @@ -3,27 +3,27 @@ import LazyVal from '../shared/helpers/LazyVal'; import GitlabUser from '../shared/types/Gitlab/GitlabUser'; -const userBase = Prisma.validator<Prisma.UserArgs>()({ - include: { exercices: true } - }); -const enonceBase = Prisma.validator<Prisma.EnonceArgs>()({ - include: { - exercices: true, - staff : true - } - }); -const exerciceBase = Prisma.validator<Prisma.ExerciceArgs>()({ - include: { - enonce : true, - members: true, - results: true - } - }); -const resultBase = Prisma.validator<Prisma.ResultArgs>()({ - include: { - exercice: true - } - }); +const userBase = Prisma.validator<Prisma.UserDefaultArgs>()({ + include: { exercises: true } + }); +const assignmentBase = Prisma.validator<Prisma.AssignmentDefaultArgs>()({ + include: { + exercises: true, + staff : true + } + }); +const exerciseBase = Prisma.validator<Prisma.ExerciseDefaultArgs>()({ + include: { + assignment: true, + members : true, + results : true + } + }); +const resultBase = Prisma.validator<Prisma.ResultDefaultArgs>()({ + include: { + exercise: true + } + }); export type User = Omit<Prisma.UserGetPayload<typeof userBase>, 'password'> & { @@ -31,6 +31,6 @@ export type User = Omit<Prisma.UserGetPayload<typeof userBase>, 'password'> & { isTeachingStaff: boolean gitlabProfile: LazyVal<GitlabUser> } -export type Enonce = Prisma.EnonceGetPayload<typeof enonceBase> -export type Exercice = Prisma.ExerciceGetPayload<typeof exerciceBase> +export type Assignment = Prisma.AssignmentGetPayload<typeof assignmentBase> +export type Exercise = Prisma.ExerciseGetPayload<typeof exerciseBase> export type Result = Prisma.ResultGetPayload<typeof resultBase> \ No newline at end of file diff --git a/ExpressAPI/src/types/SecurityCheckType.ts b/ExpressAPI/src/types/SecurityCheckType.ts index b063ff961c724ecef22de1e92718d0b7e3dc1a31..3a0b733103af4604e10f917ec3edc4c7f56b3b66 100644 --- a/ExpressAPI/src/types/SecurityCheckType.ts +++ b/ExpressAPI/src/types/SecurityCheckType.ts @@ -1,8 +1,8 @@ enum SecurityCheckType { - TEACHING_STAFF = 'teachingStaff', - ENONCE_STAFF = 'enonceStaff', - ENONCE_IS_PUBLISHED = 'enonceIsPublished', - EXERCICE_SECRET = 'exerciceSecret', + TEACHING_STAFF = 'teachingStaff', + ASSIGNMENT_STAFF = 'assignmentStaff', + ASSIGNMENT_IS_PUBLISHED = 'assignmentIsPublished', + EXERCISE_SECRET = 'exerciseSecret', } diff --git a/ExpressAPI/src/types/express/index.d.ts b/ExpressAPI/src/types/express/index.d.ts index c33801d8513fa8dde55cd0cd685a3c3918c3e22b..e7ac814574fc914961ed95329b1954eb60a74997 100644 --- a/ExpressAPI/src/types/express/index.d.ts +++ b/ExpressAPI/src/types/express/index.d.ts @@ -1,5 +1,5 @@ -import Session from '../../controllers/Session'; -import { Enonce, Exercice } from '../DatabaseTypes'; +import Session from '../../controllers/Session'; +import { Assignment, Exercise } from '../DatabaseTypes'; // to make the file a module and avoid the TypeScript error export {}; @@ -9,7 +9,7 @@ declare global { export interface Request { session: Session, boundParams: { - enonce: Enonce | undefined, exercice: Exercice | undefined + assignment: Assignment | undefined, exercise: Exercise | undefined } } }