Skip to content
Snippets Groups Projects
Commit 7884e53c authored by michael.minelli's avatar michael.minelli
Browse files

Merge branch 'v2.0.0' into main

parents 390c277c 67d5298b
Branches
Tags 2.0.0
No related merge requests found
Pipeline #26338 passed
Showing
with 423 additions and 299 deletions
variables:
GIT_SUBMODULE_STRATEGY: recursive
GIT_SUBMODULE_FORCE_HTTPS: "true"
SECURE_FILES_DOWNLOAD_PATH: './'
VERSION_DEV_SUFFIX: '-dev'
GITLAB_API_PROJECT_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}
PROJECT_FOLDER: ExpressAPI
.get_version:
script:
- IS_DEV=$([[ $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ]] && echo false || echo true)
- VERSION=$(jq -r .version $PROJECT_FOLDER/package.json)$([[ $IS_DEV == true ]] && echo $VERSION_DEV_SUFFIX || echo '')
.clean_release:
script:
# Delete release if it already exists
- 'curl --request DELETE --header "JOB-TOKEN: $CI_JOB_TOKEN" "${GITLAB_API_PROJECT_URL}/releases/${VERSION}"'
# Delete tag if it already exists (use private-token because job-token don't have permission to delete tags)
- 'curl --request DELETE --header "PRIVATE-TOKEN: $GITLAB_PROJECT_ACCESS_TOKEN" "${GITLAB_API_PROJECT_URL}/repository/tags/${VERSION}"'
stages:
- test
- clean
- release
test:build:
stage: test
image: node:latest
tags:
- build
script:
- cd ExpressAPI
- npm install
- npm run build
rules:
- if: '$CI_COMMIT_TAG =~ "/^$/"'
clean:release:
stage: clean
tags:
- gitlab_clean
image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
script:
- !reference [.get_version, script]
- !reference [.clean_release, script]
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH =~ /^v[0-9]+(\.[0-9]+)*$/'
clean:dev:release:
stage: clean
tags:
- gitlab_clean
image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
script:
- !reference [.get_version, script]
- VERSION="${VERSION}${VERSION_DEV_SUFFIX}"
- !reference [.clean_release, script]
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
release:gitlab:
stage: release
tags:
- release
image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest
script:
- !reference [.get_version, script]
- echo 'Running release_job'
# Extract description from CHANGELOG.md
- CHANGELOG_LINE_START=`awk '/##\ [0-9]+\.[0-9]+\.[0-9]+/{print NR; exit;}' CHANGELOG.md`
- CHANGELOG_LINE_END=`awk '/##\ [0-9]+\.[0-9]+\.[0-9]+/{ count++; if(count>1) {print NR; exit;}}' CHANGELOG.md`
- DESCRIPTION=`awk 'NR > '$CHANGELOG_LINE_START' && NR < '$CHANGELOG_LINE_END'' CHANGELOG.md`
# Create Release (can't be done by release_step of gitlab image because it don't have access to env var defined in script_step)
- >
RELEASE_DATA=$(jq --null-input --arg version "$VERSION" --arg description "# Changelog (version $VERSION) $DESCRIPTION" --arg tag_name "$VERSION" --arg ref "$CI_COMMIT_SHORT_SHA" '{
"name": $version,
"description": $description,
"tag_name": $tag_name,
"ref": $ref
}')
- >
curl --data "${RELEASE_DATA}" \
--header "Content-Type: application/json" \
--header "JOB-TOKEN: $CI_JOB_TOKEN" \
--request POST "${GITLAB_API_PROJECT_URL}/releases"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH =~ /^v[0-9]+(\.[0-9]+)*$/'
# Changelog
<!--
### ✨ Feature
### 🤏 Minor change
### 🎨 Interface
### 🐛 Bugfix
### 🔒 Security
### 🚀‍️ CI / CD
### 🔨 Internal / Developers
### 📚 Documentation
**💥 Breaking:**
**⚠️ Deprecation:**
-->
## 2.0.0 (2023-09-15)
### ✨ Feature
- Added license: AGPLv3
### 🎨 Interface
- **💥 Breaking:** Renamed `enonce` to `assignment`
- **💥 Breaking:** Renamed `exercice` to `exercise`
### 🔨 Internal / Developers
- Auto release by pipeline
- For vX.Y.Z branch create release and tag with `-dev` suffix
- For main branch create definitive release and remove `-dev` releases
## 1.0.1 (2023-08-12)
### ✨ Feature
- 🎉 Initial release of the project
\ No newline at end of file
Subproject commit 57997f6ff4ad2d2e23e03f86d997f64463cc898d
Subproject commit 4d703a2dd39ec0c2b71bbbbda8900588c4e360bd
###################################################################################################################
# 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:
......
{
"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",
......
-- 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 {
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])
}
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 });
......
......@@ -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 = '';
......
......@@ -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();
}
......
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 { 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();
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');
}
}
......
......@@ -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;
......
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);
}
}
......
......@@ -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();
......@@ -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();
......@@ -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();
Subproject commit f33e4e0c7b34f9060e8995550920d25cd3e73c40
Subproject commit 8d7e3ca0cca10e874ac48e19e47da8a1491ccba7
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment