Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • Dojo_Project_Nguyen/backend/dojobackendapi
  • dojo_project/projects/backend/dojobackendapi
2 results
Select Git revision
Show changes
Commits on Source (2)
Showing
with 338 additions and 325 deletions
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
################################################################################################################### ###################################################################################################################
# DO NOT MODIFY THIS FILE # 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: variables:
...@@ -16,13 +16,13 @@ stages: ...@@ -16,13 +16,13 @@ stages:
dojo: dojo:
stage: dojo stage: dojo
tags: tags:
- dojo_exercice - dojo_exercise
services: services:
- docker:dind - docker:dind
image: image:
name: dojohesso/dojo_exercice_checker:latest name: dojohesso/dojo_exercise_checker:latest
script: script:
- dojo_exercice_checker - dojo_exercise_checker
artifacts: artifacts:
when: always when: always
paths: paths:
......
{ {
"name" : "dojo_backend_api", "name" : "dojo_backend_api",
"description" : "Backend API for the Dojo Project", "description" : "Backend API for the Dojo Project",
"version" : "1.0.1", "version" : "2.0.0",
"license" : "", "license" : "",
"author" : "Michaël Minelli <michael-jean.minelli@hesge.ch>", "author" : "Michaël Minelli <michael-jean.minelli@hesge.ch>",
"main" : "app.js", "main" : "app.js",
......
-- 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
...@@ -17,11 +17,11 @@ model User { ...@@ -17,11 +17,11 @@ model User {
role String? role String?
deleted Boolean @default(false) deleted Boolean @default(false)
enonces Enonce[] assignments Assignment[]
exercices Exercice[] exercises Exercise[]
} }
model Enonce { model Assignment {
name String @id name String @id
gitlabId Int gitlabId Int
gitlabLink String gitlabLink String
...@@ -30,13 +30,13 @@ model Enonce { ...@@ -30,13 +30,13 @@ model Enonce {
gitlabLastInfoDate DateTime gitlabLastInfoDate DateTime
published Boolean @default(false) published Boolean @default(false)
exercices Exercice[] exercises Exercise[]
staff User[] staff User[]
} }
model Exercice { model Exercise {
id String @id @db.Char(36) id String @id @db.Char(36)
enonceName String assignmentName String
name String name String
secret String @unique @db.Char(36) secret String @unique @db.Char(36)
gitlabId Int gitlabId Int
...@@ -45,14 +45,14 @@ model Exercice { ...@@ -45,14 +45,14 @@ model Exercice {
gitlabLastInfo Json @db.Json gitlabLastInfo Json @db.Json
gitlabLastInfoDate DateTime 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[] members User[]
results Result[] results Result[]
} }
model Result { model Result {
exerciceId String @db.Char(36) exerciseId String @db.Char(36)
dateTime DateTime @default(now()) dateTime DateTime @default(now())
success Boolean success Boolean
exitCode Int exitCode Int
...@@ -60,7 +60,7 @@ model Result { ...@@ -60,7 +60,7 @@ model Result {
results Json @db.Json results Json @db.Json
files 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])
} }
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
import { Exercice } from '../types/DatabaseTypes';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { Exercise } from '../types/DatabaseTypes';
class Config { class Config {
...@@ -18,14 +18,14 @@ class Config { ...@@ -18,14 +18,14 @@ class Config {
}; };
public gitlab: { 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 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; }; maxSameName: number; resultsFolder: string, pipelineResultsFolder: string; default: { description: string; visibility: string; };
}; };
...@@ -38,8 +38,7 @@ class Config { ...@@ -38,8 +38,7 @@ class Config {
}; };
this.jwtConfig = { this.jwtConfig = {
secret : process.env.JWT_SECRET_KEY || '', secret: process.env.JWT_SECRET_KEY || '', expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
}; };
this.permissions = { this.permissions = {
...@@ -47,41 +46,23 @@ class Config { ...@@ -47,41 +46,23 @@ class Config {
}; };
this.gitlab = { this.gitlab = {
apiURL : process.env.GITLAB_API_URL || '', apiURL : process.env.GITLAB_API_URL || '', urls: JSON.parse(process.env.GITLAB_URLS || '[]'), account: {
urls : JSON.parse(process.env.GITLAB_URLS || '[]'), id: Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0), username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '', token: process.env.GITLAB_DOJO_ACCOUNT_TOKEN || ''
account: { }, group: {
id : Number(process.env.GITLAB_DOJO_ACCOUNT_ID || 0), 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)
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)
} }
}; };
this.enonce = { this.assignment = {
default : { default : {
description : process.env.ENONCE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', 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) ?? ''
initReadme : process.env.ENONCE_DEFAULT_INIT_README?.toBoolean() ?? false, }, baseFiles: JSON.parse(process.env.ASSIGNMENT_BASE_FILES || '[]'), filename: process.env.ASSIGNMENT_FILENAME || ''
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.exercice = { this.exercise = {
maxSameName : Number(process.env.EXERCICE_MAX_SAME_NAME || 0), 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
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 : { default : {
description: process.env.EXERCICE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', description: process.env.EXERCISE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', visibility: process.env.EXERCISE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
visibility : process.env.EXERCICE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
} }
}; };
...@@ -89,8 +70,8 @@ class Config { ...@@ -89,8 +70,8 @@ class Config {
this.userPasswordSaltRounds = Number(process.env.USER_PASSWORD_SALT_ROUNDS || 10); this.userPasswordSaltRounds = Number(process.env.USER_PASSWORD_SALT_ROUNDS || 10);
} }
public getResultsFolder(exercice: Exercice): string { public getResultsFolder(exercise: Exercise): string {
const folderPath = path.join(this.exercice.resultsFolder, exercice.enonceName, exercice.id); const folderPath = path.join(this.exercise.resultsFolder, exercise.assignmentName, exercise.id);
fs.mkdirSync(folderPath, { recursive: true }); fs.mkdirSync(folderPath, { recursive: true });
......
...@@ -4,8 +4,8 @@ import { JwtPayload } from 'jsonwebtoken'; ...@@ -4,8 +4,8 @@ import { JwtPayload } from 'jsonwebtoken';
import Config from '../config/Config'; import Config from '../config/Config';
import express from 'express'; import express from 'express';
import UserManager from '../managers/UserManager'; import UserManager from '../managers/UserManager';
import DojoResponse from '../shared/types/Dojo/DojoResponse';
import { User } from '../types/DatabaseTypes'; import { User } from '../types/DatabaseTypes';
import DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse';
class Session { class Session {
...@@ -46,7 +46,7 @@ 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 } : {}); 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; const profileJson = this.profile;
let reasonPhrase = ''; let reasonPhrase = '';
......
...@@ -4,7 +4,7 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres ...@@ -4,7 +4,7 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres
import { BailOptions, ValidationChain } from 'express-validator/src/chain'; import { BailOptions, ValidationChain } from 'express-validator/src/chain';
import GitlabManager from '../managers/GitlabManager'; import GitlabManager from '../managers/GitlabManager';
import express from 'express'; import express from 'express';
import SharedExerciceHelper from '../shared/helpers/Dojo/SharedExerciceHelper'; import SharedExerciseHelper from '../shared/helpers/Dojo/SharedExerciseHelper';
declare type DojoMeta = Meta & { declare type DojoMeta = Meta & {
...@@ -75,7 +75,7 @@ class DojoValidators { ...@@ -75,7 +75,7 @@ class DojoValidators {
if ( template ) { 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`; return `${ Config.gitlab.urls[0].replace(/^([a-z]{3,5}:\/{2})?(.*)/, `$1${ Config.gitlab.account.username }:${ Config.gitlab.account.token }@$2`) }${ template }.git`;
} else { } else {
return Config.enonce.default.template; return Config.assignment.default.template;
} }
} catch ( e ) { } } catch ( e ) { }
...@@ -83,7 +83,7 @@ class DojoValidators { ...@@ -83,7 +83,7 @@ class DojoValidators {
} }
}); });
readonly exerciceResultsValidator = this.toValidatorSchemaOptions({ readonly exerciseResultsValidator = this.toValidatorSchemaOptions({
bail : true, bail : true,
errorMessage: 'Results: not provided or invalid format', errorMessage: 'Results: not provided or invalid format',
options : (_value, { options : (_value, {
...@@ -93,7 +93,7 @@ class DojoValidators { ...@@ -93,7 +93,7 @@ class DojoValidators {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const results = this.getParamValue(req, path); const results = this.getParamValue(req, path);
if ( results ) { if ( results ) {
SharedExerciceHelper.validateResultFile(results, false).isValid ? resolve(true) : reject(); SharedExerciseHelper.validateResultFile(results, false).isValid ? resolve(true) : reject();
} else { } else {
reject(); reject();
} }
......
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();
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();
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { Exercice } from '../types/DatabaseTypes'; import { Exercise } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper'; import db from '../helpers/DatabaseHelper';
class ExerciceManager { class ExerciseManager {
async get(id: string, include: Prisma.ExerciceInclude | undefined = undefined): Promise<Exercice | undefined> { async get(id: string, include: Prisma.ExerciseInclude | undefined = undefined): Promise<Exercise | undefined> {
return await db.exercice.findUnique({ return await db.exercise.findUnique({
where : { where : {
id: id id: id
}, },
include: include include: include
}) as unknown as Exercice ?? undefined; }) as unknown as Exercise ?? undefined;
} }
} }
export default new ExerciceManager(); export default new ExerciseManager();
import { Express } from 'express-serve-static-core'; import { Express } from 'express-serve-static-core';
import express from 'express'; import express from 'express';
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
import EnonceManager from '../managers/EnonceManager'; import ExerciseManager from '../managers/ExerciseManager';
import ExerciceManager from '../managers/ExerciceManager'; import AssignmentManager from '../managers/AssignmentManager';
type GetFunction = (id: string | number, ...args: Array<any>) => Promise<any> type GetFunction = (id: string | number, ...args: Array<any>) => Promise<any>
...@@ -27,23 +27,23 @@ class ParamsCallbackManager { ...@@ -27,23 +27,23 @@ class ParamsCallbackManager {
initBoundParams(req: express.Request) { initBoundParams(req: express.Request) {
if ( !req.boundParams ) { if ( !req.boundParams ) {
req.boundParams = { req.boundParams = {
enonce : undefined, assignment: undefined,
exercice: undefined exercise : undefined
}; };
} }
} }
register(backend: Express) { register(backend: Express) {
this.listenParam('enonceNameOrUrl', backend, (EnonceManager.get as GetFunction).bind(EnonceManager), [ { this.listenParam('assignmentNameOrUrl', backend, (AssignmentManager.get as GetFunction).bind(AssignmentManager), [ {
exercices: true, exercises: true,
staff : true staff : true
} ], 'enonce'); } ], 'assignment');
this.listenParam('exerciceId', backend, (ExerciceManager.get as GetFunction).bind(ExerciceManager), [ { this.listenParam('exerciseId', backend, (ExerciseManager.get as GetFunction).bind(ExerciseManager), [ {
enonce : true, assignment: true,
members : true, members : true,
results : true results : true
} ], 'exercice'); } ], 'exercise');
} }
} }
......
...@@ -2,7 +2,7 @@ import express from 'express'; ...@@ -2,7 +2,7 @@ import express from 'express';
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
import SecurityCheckType from '../types/SecurityCheckType'; import SecurityCheckType from '../types/SecurityCheckType';
import logger from '../shared/logging/WinstonLogger'; import logger from '../shared/logging/WinstonLogger';
import EnonceManager from '../managers/EnonceManager'; import AssignmentManager from '../managers/AssignmentManager';
class SecurityMiddleware { class SecurityMiddleware {
...@@ -24,14 +24,14 @@ class SecurityMiddleware { ...@@ -24,14 +24,14 @@ class SecurityMiddleware {
case SecurityCheckType.TEACHING_STAFF: case SecurityCheckType.TEACHING_STAFF:
isAllowed = isAllowed || req.session.profile.isTeachingStaff; isAllowed = isAllowed || req.session.profile.isTeachingStaff;
break; break;
case SecurityCheckType.ENONCE_STAFF: case SecurityCheckType.ASSIGNMENT_STAFF:
isAllowed = isAllowed || await EnonceManager.isUserAllowedToAccessEnonce(req.boundParams.enonce!, req.session.profile); isAllowed = isAllowed || await AssignmentManager.isUserAllowedToAccessAssignment(req.boundParams.assignment!, req.session.profile);
break; break;
case SecurityCheckType.ENONCE_IS_PUBLISHED: case SecurityCheckType.ASSIGNMENT_IS_PUBLISHED:
isAllowed = isAllowed || (req.boundParams.enonce?.published ?? false); isAllowed = isAllowed || (req.boundParams.assignment?.published ?? false);
break; break;
case SecurityCheckType.EXERCICE_SECRET: case SecurityCheckType.EXERCISE_SECRET:
isAllowed = isAllowed || req.headers.authorization?.replace('ExerciceSecret ', '') === req.boundParams.exercice!.secret; isAllowed = isAllowed || req.headers.authorization?.replace('ExerciseSecret ', '') === req.boundParams.exercise!.secret;
break; break;
default: default:
break; break;
......
...@@ -2,9 +2,9 @@ import { Express } from 'express-serve-static-core'; ...@@ -2,9 +2,9 @@ import { Express } from 'express-serve-static-core';
import RoutesManager from '../express/RoutesManager'; import RoutesManager from '../express/RoutesManager';
import BaseRoutes from './BaseRoutes'; import BaseRoutes from './BaseRoutes';
import SessionRoutes from './SessionRoutes'; import SessionRoutes from './SessionRoutes';
import EnonceRoutes from './EnonceRoutes'; import AssignmentRoutes from './AssignmentRoutes';
import GitlabRoutes from './GitlabRoutes'; import GitlabRoutes from './GitlabRoutes';
import ExerciceRoutes from './ExerciceRoutes'; import ExerciseRoutes from './ExerciseRoutes';
class AdminRoutesManager implements RoutesManager { class AdminRoutesManager implements RoutesManager {
...@@ -12,8 +12,8 @@ class AdminRoutesManager implements RoutesManager { ...@@ -12,8 +12,8 @@ class AdminRoutesManager implements RoutesManager {
BaseRoutes.registerOnBackend(backend); BaseRoutes.registerOnBackend(backend);
SessionRoutes.registerOnBackend(backend); SessionRoutes.registerOnBackend(backend);
GitlabRoutes.registerOnBackend(backend); GitlabRoutes.registerOnBackend(backend);
EnonceRoutes.registerOnBackend(backend); AssignmentRoutes.registerOnBackend(backend);
ExerciceRoutes.registerOnBackend(backend); ExerciseRoutes.registerOnBackend(backend);
} }
} }
......
...@@ -17,62 +17,55 @@ import logger from '../shared/logging/WinstonLogger'; ...@@ -17,62 +17,55 @@ import logger from '../shared/logging/WinstonLogger';
import DojoValidators from '../helpers/DojoValidators'; import DojoValidators from '../helpers/DojoValidators';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import db from '../helpers/DatabaseHelper'; import db from '../helpers/DatabaseHelper';
import { Enonce } from '../types/DatabaseTypes'; import { Assignment } from '../types/DatabaseTypes';
import EnonceManager from '../managers/EnonceManager'; import AssignmentManager from '../managers/AssignmentManager';
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
class EnonceRoutes implements RoutesManager { class AssignmentRoutes implements RoutesManager {
private readonly enonceValidator: ExpressValidator.Schema = { private readonly assignmentValidator: ExpressValidator.Schema = {
name : { name : {
trim : true, trim: true, notEmpty: true
notEmpty: true }, members : {
}, trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
members : { }, template: {
trim : true, trim: true, custom: DojoValidators.templateUrlValidator, customSanitizer: DojoValidators.templateUrlSanitizer
notEmpty : true,
customSanitizer: DojoValidators.jsonSanitizer
},
template: {
trim : true,
custom : DojoValidators.templateUrlValidator,
customSanitizer: DojoValidators.templateUrlSanitizer
} }
}; };
registerOnBackend(backend: Express) { registerOnBackend(backend: Express) {
backend.get('/enonces/:enonceNameOrUrl', SecurityMiddleware.check(true), this.getEnonce); backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment);
backend.post('/enonces', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.enonceValidator), this.createEnonce); 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('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true));
backend.patch('/enonces/:enonceNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_STAFF), this.changeEnoncePublishedStatus(false)); backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false));
} }
// Get an enonce by its name or gitlab url // Get an assignment by its name or gitlab url
private async getEnonce(req: express.Request, res: express.Response) { private async getAssignment(req: express.Request, res: express.Response) {
const enonce: Enonce | undefined = req.boundParams.enonce; 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 // @ts-ignore
delete enonce.gitlabId; delete assignment.gitlabId;
// @ts-ignore // @ts-ignore
delete enonce.gitlabLink; delete assignment.gitlabLink;
// @ts-ignore // @ts-ignore
delete enonce.gitlabCreationInfo; delete assignment.gitlabCreationInfo;
// @ts-ignore // @ts-ignore
delete enonce.gitlabLastInfo; delete assignment.gitlabLastInfo;
// @ts-ignore // @ts-ignore
delete enonce.gitlabLastInfoDate; delete assignment.gitlabLastInfoDate;
// @ts-ignore // @ts-ignore
delete enonce.staff; delete assignment.staff;
// @ts-ignore // @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: { const params: {
name: string, members: Array<GitlabUser>, template: string name: string, members: Array<GitlabUser>, template: string
} = req.body; } = req.body;
...@@ -82,7 +75,7 @@ class EnonceRoutes implements RoutesManager { ...@@ -82,7 +75,7 @@ class EnonceRoutes implements RoutesManager {
let repository: GitlabRepository; let repository: GitlabRepository;
try { 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); await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
} catch ( error ) { } catch ( error ) {
...@@ -107,31 +100,23 @@ class EnonceRoutes implements RoutesManager { ...@@ -107,31 +100,23 @@ class EnonceRoutes implements RoutesManager {
} }
})); }));
const enonce: Enonce = await db.enonce.create({ const assignment: Assignment = await db.assignment.create({
data: { data: {
name : repository.name, 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: {
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 => { connectOrCreate: [ ...params.members.map(gitlabUser => {
return { return {
create : { create : {
gitlabId : gitlabUser.id, gitlabId: gitlabUser.id, firstname: gitlabUser.name
firstname: gitlabUser.name }, where: {
},
where : {
gitlabId: gitlabUser.id 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 ) { } catch ( error ) {
if ( error instanceof AxiosError ) { if ( error instanceof AxiosError ) {
return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send(); return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
...@@ -142,16 +127,15 @@ class EnonceRoutes implements RoutesManager { ...@@ -142,16 +127,15 @@ 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> => { return async (req: express.Request, res: express.Response): Promise<void> => {
try { 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({ await db.assignment.update({
where : { where : {
name: req.boundParams.enonce!.name name: req.boundParams.assignment!.name
}, }, data: {
data : {
published: publish published: publish
} }
}); });
...@@ -172,4 +156,4 @@ class EnonceRoutes implements RoutesManager { ...@@ -172,4 +156,4 @@ class EnonceRoutes implements RoutesManager {
} }
export default new EnonceRoutes(); export default new AssignmentRoutes();
...@@ -16,91 +16,76 @@ import { v4 as uuidv4 } from 'uuid'; ...@@ -16,91 +16,76 @@ import { v4 as uuidv4 } from 'uuid';
import GitlabMember from '../shared/types/Gitlab/GitlabMember'; import GitlabMember from '../shared/types/Gitlab/GitlabMember';
import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel'; import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { Enonce, Exercice } from '../types/DatabaseTypes'; import { Assignment, Exercise } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper'; import db from '../helpers/DatabaseHelper';
import SecurityCheckType from '../types/SecurityCheckType'; import SecurityCheckType from '../types/SecurityCheckType';
import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile'; import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile';
import GitlabFile from '../shared/types/Gitlab/GitlabFile'; import GitlabFile from '../shared/types/Gitlab/GitlabFile';
import EnonceFile from '../shared/types/Dojo/EnonceFile';
import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType'; import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType';
import JSON5 from 'json5'; import JSON5 from 'json5';
import ExerciceResultsFile from '../shared/types/Dojo/ExerciceResultsFile';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import AssignmentFile from '../shared/types/Dojo/AssignmentFile';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile';
class ExerciceRoutes implements RoutesManager { class ExerciseRoutes implements RoutesManager {
private readonly exerciceValidator: ExpressValidator.Schema = { private readonly exerciseValidator: ExpressValidator.Schema = {
members: { members: {
trim : true, trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
notEmpty : true,
customSanitizer: DojoValidators.jsonSanitizer
} }
}; };
private readonly resultValidator: ExpressValidator.Schema = { private readonly resultValidator: ExpressValidator.Schema = {
exitCode : { exitCode : {
isInt: true, isInt: true, toInt: true
toInt: true }, commit : {
}, trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
commit : { }, results : {
trim : true, trim: true, notEmpty: true, custom: DojoValidators.exerciseResultsValidator, customSanitizer: DojoValidators.jsonSanitizer
notEmpty : true, }, files : {
customSanitizer: DojoValidators.jsonSanitizer trim: true, notEmpty: true, customSanitizer: DojoValidators.jsonSanitizer
}, }, archiveBase64: {
results : { isBase64: true, notEmpty: true
trim : true,
notEmpty : true,
custom : DojoValidators.exerciceResultsValidator,
customSanitizer: DojoValidators.jsonSanitizer
},
files : {
trim : true,
notEmpty : true,
customSanitizer: DojoValidators.jsonSanitizer
},
archiveBase64: {
isBase64: true,
notEmpty: true
} }
}; };
registerOnBackend(backend: Express) { 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 { private getExerciseName(assignment: Assignment, members: Array<GitlabUser>, suffix: number): string {
return `DojoEx - ${ enonce.name } - ${ members.map(member => member.username).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`; return `DojoEx - ${ assignment.name } - ${ members.map(member => member.username).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`;
} }
private getExercicePath(enonce: Enonce, exerciceId: string): string { private getExercisePath(assignment: Assignment, exerciseId: string): string {
return `dojo-ex_${ (enonce.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciceId }`; 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; const params: { members: Array<GitlabUser> } = req.body;
params.members = [ await req.session.profile.gitlabProfile!.value, ...params.members ].removeObjectDuplicates(gitlabUser => gitlabUser.id); 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(); const secret: string = uuidv4();
let repository!: GitlabRepository; let repository!: GitlabRepository;
let suffix: number = 0; let suffix: number = 0;
do { do {
try { 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.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_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; break;
} catch ( error ) { } catch ( error ) {
...@@ -114,14 +99,14 @@ class ExerciceRoutes implements RoutesManager { ...@@ -114,14 +99,14 @@ class ExerciceRoutes implements RoutesManager {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send(); 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(); return res.status(StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE).send();
} }
try { 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 ) { } catch ( error ) {
logger.error(error); logger.error(error);
...@@ -133,7 +118,7 @@ class ExerciceRoutes implements RoutesManager { ...@@ -133,7 +118,7 @@ class ExerciceRoutes implements RoutesManager {
} }
try { 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 { try {
return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER); return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER);
} catch ( e ) { } catch ( e ) {
...@@ -141,34 +126,23 @@ class ExerciceRoutes implements RoutesManager { ...@@ -141,34 +126,23 @@ class ExerciceRoutes implements RoutesManager {
} }
})); }));
const exercice: Exercice = await db.exercice.create({ const exercise: Exercise = await db.exercise.create({
data: { data: {
id : exerciceId, 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: {
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 : {
connectOrCreate: [ ...params.members.map(gitlabUser => { connectOrCreate: [ ...params.members.map(gitlabUser => {
return { return {
create : { create : {
gitlabId : gitlabUser.id, gitlabId: gitlabUser.id, firstname: gitlabUser.name
firstname: gitlabUser.name }, where: {
},
where : {
gitlabId: gitlabUser.id 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 ) { } catch ( error ) {
logger.error(error); logger.error(error);
...@@ -180,29 +154,29 @@ class ExerciceRoutes implements RoutesManager { ...@@ -180,29 +154,29 @@ class ExerciceRoutes implements RoutesManager {
} }
} }
private async getEnonce(req: express.Request, res: express.Response) { private async getAssignment(req: express.Request, res: express.Response) {
const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercice!.enonce.gitlabId); const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId);
let enonceHjsonFile!: GitlabFile; let assignmentHjsonFile!: GitlabFile;
let immutableFiles: Array<GitlabFile> = await Promise.all(Config.enonce.baseFiles.map(async (baseFile: string) => { let immutableFiles: Array<GitlabFile> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => {
let file = await GitlabManager.getFile(req.boundParams.exercice!.enonce.gitlabId, baseFile); let file = await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, baseFile);
if ( baseFile === Config.enonce.filename ) { if ( baseFile === Config.assignment.filename ) {
enonceHjsonFile = file; assignmentHjsonFile = file;
} }
return 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 => { await Promise.all(repoTree.map(async gitlabTreeFile => {
if ( gitlabTreeFile.type == GitlabTreeFileType.BLOB ) { if ( gitlabTreeFile.type == GitlabTreeFileType.BLOB ) {
for ( const immutablePath of immutablePaths ) { for ( const immutablePath of immutablePaths ) {
if ( gitlabTreeFile.path.startsWith(immutablePath) ) { 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; break;
} }
} }
...@@ -210,32 +184,25 @@ class ExerciceRoutes implements RoutesManager { ...@@ -210,32 +184,25 @@ class ExerciceRoutes implements RoutesManager {
})); }));
return req.session.sendResponse(res, StatusCodes.OK, { return req.session.sendResponse(res, StatusCodes.OK, {
enonce : (req.boundParams.exercice as Exercice).enonce, assignment: (req.boundParams.exercise as Exercise).assignment, assignmentFile: dojoAssignmentFile, immutable: immutableFiles
enonceFile: dojoEnonceFile,
immutable : immutableFiles
}); });
} }
private async createResult(req: express.Request, res: express.Response) { private async createResult(req: express.Request, res: express.Response) {
const params: { exitCode: number, commit: any, results: ExerciceResultsFile, files: any, archiveBase64: string } = req.body; const params: { exitCode: number, commit: any, results: ExerciseResultsFile, files: any, archiveBase64: string } = req.body;
const exercice: Exercice = req.boundParams.exercice!; const exercise: Exercise = req.boundParams.exercise!;
const result = await db.result.create({ const result = await db.result.create({
data: { data: {
exerciceId: exercice.id, exerciseId: exercise.id, exitCode: params.exitCode, success: params.results.success, commit: params.commit, results: params.results as unknown as Prisma.JsonObject, files: params.files
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); req.session.sendResponse(res, StatusCodes.OK);
} }
} }
export default new ExerciceRoutes(); export default new ExerciseRoutes();
...@@ -6,7 +6,7 @@ import SecurityCheckType from '../types/SecurityCheckType'; ...@@ -6,7 +6,7 @@ import SecurityCheckType from '../types/SecurityCheckType';
import GitlabManager from '../managers/GitlabManager'; import GitlabManager from '../managers/GitlabManager';
class EnonceRoutes implements RoutesManager { class GitlabRoutes implements RoutesManager {
registerOnBackend(backend: Express) { registerOnBackend(backend: Express) {
backend.get('/gitlab/project/:idOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess); backend.get('/gitlab/project/:idOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess);
} }
...@@ -19,4 +19,4 @@ class EnonceRoutes implements RoutesManager { ...@@ -19,4 +19,4 @@ class EnonceRoutes implements RoutesManager {
} }
export default new EnonceRoutes(); export default new GitlabRoutes();
Subproject commit f33e4e0c7b34f9060e8995550920d25cd3e73c40 Subproject commit 4cbcc7398459d7a2de027292fa0522c7fa592935
...@@ -3,25 +3,25 @@ import LazyVal from '../shared/helpers/LazyVal'; ...@@ -3,25 +3,25 @@ import LazyVal from '../shared/helpers/LazyVal';
import GitlabUser from '../shared/types/Gitlab/GitlabUser'; import GitlabUser from '../shared/types/Gitlab/GitlabUser';
const userBase = Prisma.validator<Prisma.UserArgs>()({ const userBase = Prisma.validator<Prisma.UserDefaultArgs>()({
include: { exercices: true } include: { exercises: true }
}); });
const enonceBase = Prisma.validator<Prisma.EnonceArgs>()({ const assignmentBase = Prisma.validator<Prisma.AssignmentDefaultArgs>()({
include: { include: {
exercices: true, exercises: true,
staff : true staff : true
} }
}); });
const exerciceBase = Prisma.validator<Prisma.ExerciceArgs>()({ const exerciseBase = Prisma.validator<Prisma.ExerciseDefaultArgs>()({
include: { include: {
enonce : true, assignment: true,
members : true, members : true,
results : true results : true
} }
}); });
const resultBase = Prisma.validator<Prisma.ResultArgs>()({ const resultBase = Prisma.validator<Prisma.ResultDefaultArgs>()({
include: { include: {
exercice: true exercise: true
} }
}); });
...@@ -31,6 +31,6 @@ export type User = Omit<Prisma.UserGetPayload<typeof userBase>, 'password'> & { ...@@ -31,6 +31,6 @@ export type User = Omit<Prisma.UserGetPayload<typeof userBase>, 'password'> & {
isTeachingStaff: boolean isTeachingStaff: boolean
gitlabProfile: LazyVal<GitlabUser> gitlabProfile: LazyVal<GitlabUser>
} }
export type Enonce = Prisma.EnonceGetPayload<typeof enonceBase> export type Assignment = Prisma.AssignmentGetPayload<typeof assignmentBase>
export type Exercice = Prisma.ExerciceGetPayload<typeof exerciceBase> export type Exercise = Prisma.ExerciseGetPayload<typeof exerciseBase>
export type Result = Prisma.ResultGetPayload<typeof resultBase> export type Result = Prisma.ResultGetPayload<typeof resultBase>
\ No newline at end of file
enum SecurityCheckType { enum SecurityCheckType {
TEACHING_STAFF = 'teachingStaff', TEACHING_STAFF = 'teachingStaff',
ENONCE_STAFF = 'enonceStaff', ASSIGNMENT_STAFF = 'assignmentStaff',
ENONCE_IS_PUBLISHED = 'enonceIsPublished', ASSIGNMENT_IS_PUBLISHED = 'assignmentIsPublished',
EXERCICE_SECRET = 'exerciceSecret', EXERCISE_SECRET = 'exerciseSecret',
} }
......