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

Merge tag 'v4.0'

parents 4efff1c5 788d286e
Branches
Tags
No related merge requests found
...@@ -21,28 +21,28 @@ class ClientsSharedConfig { ...@@ -21,28 +21,28 @@ class ClientsSharedConfig {
constructor() { constructor() {
this.apiURL = process.env.API_URL || ''; this.apiURL = process.env.API_URL ?? '';
this.assignment = { this.assignment = {
filename : process.env.ASSIGNMENT_FILENAME || '', filename : process.env.ASSIGNMENT_FILENAME ?? '',
neededFiles: JSON.parse(process.env.EXERCISE_NEEDED_FILES || '[]') neededFiles: JSON.parse(process.env.EXERCISE_NEEDED_FILES ?? '[]')
}; };
this.gitlab = { this.gitlab = {
dojoAccount: { dojoAccount: {
id : Number(process.env.GITLAB_DOJO_ACCOUNT_ID) || -1, id : Number(process.env.GITLAB_DOJO_ACCOUNT_ID ?? -1),
username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || '' username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME ?? ''
} }
}; };
this.dockerCompose = { this.dockerCompose = {
projectName: process.env.DOCKER_COMPOSE_PROJECT_NAME || '' projectName: process.env.DOCKER_COMPOSE_PROJECT_NAME ?? ''
}; };
this.exerciseResultsFolderMaxSizeInBytes = Number(process.env.EXERCISE_RESULTS_FOLDER_MAX_SIZE_IN_BYTES || 0); this.exerciseResultsFolderMaxSizeInBytes = Number(process.env.EXERCISE_RESULTS_FOLDER_MAX_SIZE_IN_BYTES ?? 0);
this.filenames = { this.filenames = {
results: process.env.EXERCISE_RESULTS_FILENAME || '' results: process.env.EXERCISE_RESULTS_FILENAME ?? ''
}; };
} }
} }
......
This diff is collapsed.
import chalk from 'chalk'; import chalk from 'chalk';
import boxen from 'boxen'; import boxen from 'boxen';
import Icon from '../../../shared/types/Icon'; import Icon from '../../../shared/types/Icon.js';
import AssignmentValidator from './AssignmentValidator'; import AssignmentValidator from './AssignmentValidator.js';
class ClientsSharedAssignmentHelper { class ClientsSharedAssignmentHelper {
displayExecutionResults(validator: AssignmentValidator, successMessage: string, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }) { displayExecutionResults(validator: AssignmentValidator, successMessage: string, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }) {
const finalLogGlobalResult = `${ Style.INFO('Global result') } : ${ validator.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`) }`; const globalResult = validator.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`);
const finalLogGlobalResult = `${ Style.INFO('Global result') } : ${ globalResult }`;
const finalLogSuccessMessage = validator.success ? `${ successMessage }` : ''; const finalLogSuccessMessage = validator.success ? `${ successMessage }` : '';
const finalLogErrorMessage = !validator.success ? `${ Style.INFO('Error message') } :\n${ Style.FAILURE(validator.fatalErrorMessage) }` : ''; const finalLogErrorMessage = !validator.success ? `${ Style.INFO('Error message') } :\n${ Style.FAILURE(validator.fatalErrorMessage) }` : '';
......
import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile'; import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile.js';
import chalk from 'chalk'; import chalk from 'chalk';
import boxen from 'boxen'; import boxen from 'boxen';
import Icon from '../../../shared/types/Icon'; import Icon from '../../../shared/types/Icon.js';
class ClientsSharedExerciseHelper { class ClientsSharedExerciseHelper {
displayExecutionResults(exerciseResults: ExerciseResultsFile, containerExitCode: number, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }, additionalText: string = '') {
const finalLogGlobalResult = `${ Style.INFO('Global result: ') }${ exerciseResults.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`) }`;
const finalLogExecutionExitCode = `${ Style.INFO('Execution exit code: ') }${ (containerExitCode == 0 ? Style.SUCCESS : Style.FAILURE)(containerExitCode) }`; private getOtherInformations(exerciseResults: ExerciseResultsFile, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }) {
return exerciseResults.otherInformations ? [ '', ...exerciseResults.otherInformations.map(information => {
const informationTitle = Style.INFO(`${ information.icon && information.icon !== '' ? Icon[information.icon] + ' ' : '' }${ information.name }: `);
const informationItems = typeof information.itemsOrInformations == 'string' ? information.itemsOrInformations : information.itemsOrInformations.map(item => `- ${ item }`).join('\n');
const finalLogResultNumbers = exerciseResults.successfulTests || exerciseResults.failedTests ? `\n\n${ Style.INFO(Style.SUCCESS('Tests passed: ')) }${ exerciseResults.successfulTests ?? '--' }\n${ Style.INFO(Style.FAILURE('Tests failed: ')) }${ exerciseResults.failedTests ?? '--' }` : ''; return `${ informationTitle }\n${ informationItems }`;
}) ].join('\n\n') : '';
}
const finalLogSuccessResultDetails = (exerciseResults.successfulTestsList ?? []).map(testName => `- ${ Icon.SUCCESS } ${ testName }`).join('\n'); displayExecutionResults(exerciseResults: ExerciseResultsFile, containerExitCode: number, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }, additionalText: string = '') {
const finalLogFailedResultDetails = (exerciseResults.failedTestsList ?? []).map(testName => `- ${ Icon.FAILURE } ${ testName }`).join('\n'); const globalResult = exerciseResults.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`);
const finalLogResultDetails = exerciseResults.successfulTestsList || exerciseResults.failedTestsList ? `\n\n${ Style.INFO('Tests: ') }${ finalLogSuccessResultDetails != '' ? '\n' + finalLogSuccessResultDetails : '' }${ finalLogFailedResultDetails != '' ? '\n' + finalLogFailedResultDetails : '' }` : ''; const finalLogGlobalResult = `${ Style.INFO('Global result: ') }${ globalResult }`;
const finalLogExecutionExitCode = `${ Style.INFO('Execution exit code: ') }${ (containerExitCode === 0 ? Style.SUCCESS : Style.FAILURE)(containerExitCode) }`;
let finalLogInformations = ''; const finalLogResultNumbers = exerciseResults.successfulTests || exerciseResults.failedTests ? `\n\n${ Style.INFO(Style.SUCCESS('Tests passed: ')) }${ exerciseResults.successfulTests ?? '--' }\n${ Style.INFO(Style.FAILURE('Tests failed: ')) }${ exerciseResults.failedTests ?? '--' }` : '';
if ( exerciseResults.otherInformations ) {
finalLogInformations = [ '', ...exerciseResults.otherInformations.map(information => {
const informationTitle = Style.INFO(`${ information.icon && information.icon != '' ? Icon[information.icon] + ' ' : '' }${ information.name }: `);
const informationItems = typeof information.itemsOrInformations == 'string' ? information.itemsOrInformations : information.itemsOrInformations.map(item => `- ${ item }`).join('\n');
return `${ informationTitle }\n${ informationItems }`; let finalLogSuccessResultDetails = (exerciseResults.successfulTestsList ?? []).map(testName => `- ${ Icon.SUCCESS } ${ testName }`).join('\n');
}) ].join('\n\n'); finalLogSuccessResultDetails = finalLogSuccessResultDetails !== '' ? '\n' + finalLogSuccessResultDetails : '';
} let finalLogFailedResultDetails = (exerciseResults.failedTestsList ?? []).map(testName => `- ${ Icon.FAILURE } ${ testName }`).join('\n');
finalLogFailedResultDetails = finalLogFailedResultDetails !== '' ? '\n' + finalLogFailedResultDetails : '';
const finalLogResultDetails = exerciseResults.successfulTestsList || exerciseResults.failedTestsList ? `\n\n${ Style.INFO('Tests: ') }${ finalLogSuccessResultDetails }${ finalLogFailedResultDetails }` : '';
const finalLogInformations = this.getOtherInformations(exerciseResults, Style);
console.log(boxen(`${ finalLogGlobalResult }\n\n${ finalLogExecutionExitCode }${ finalLogResultNumbers }${ finalLogResultDetails }${ finalLogInformations }${ additionalText }`, { console.log(boxen(`${ finalLogGlobalResult }\n\n${ finalLogExecutionExitCode }${ finalLogResultNumbers }${ finalLogResultDetails }${ finalLogInformations }${ additionalText }`, {
title : 'Results', title : 'Results',
......
import ApiRoute from '../../types/Dojo/ApiRoute.js';
import ClientsSharedConfig from '../../config/ClientsSharedConfig.js';
class DojoBackendHelper {
public getApiUrl(route: ApiRoute, options?: Partial<{ assignmentNameOrUrl: string, exerciseIdOrUrl: string, gitlabProjectId: string }>): string {
const url = `${ ClientsSharedConfig.apiURL }${ route }`;
if ( options ) {
if ( options.assignmentNameOrUrl ) {
return url.replace('{{assignmentNameOrUrl}}', encodeURIComponent(options.assignmentNameOrUrl));
}
if ( options.exerciseIdOrUrl ) {
return url.replace('{{exerciseIdOrUrl}}', encodeURIComponent(options.exerciseIdOrUrl));
}
if ( options.gitlabProjectId ) {
return url.replace('{{gitlabProjectId}}', encodeURIComponent(options.gitlabProjectId));
}
}
return url;
}
}
export default new DojoBackendHelper();
\ No newline at end of file
import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile'; import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile.js';
import { TypedEmitter } from 'tiny-typed-emitter'; import { TypedEmitter } from 'tiny-typed-emitter';
import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents'; import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents.js';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError'; import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError.js';
import { ChildProcessWithoutNullStreams } from 'node:child_process'; import { ChildProcessWithoutNullStreams } from 'node:child_process';
...@@ -18,7 +18,17 @@ class ExerciseDockerCompose { ...@@ -18,7 +18,17 @@ class ExerciseDockerCompose {
private currentStep: string = 'NOT_RUNNING'; private currentStep: string = 'NOT_RUNNING';
constructor(private projectName: string, private assignmentFile: AssignmentFile, private executionFolder: string, private composeFileOverride: Array<string> = []) { private readonly projectName: string;
private readonly assignmentFile: AssignmentFile;
private readonly executionFolder: string;
private readonly composeFileOverride: Array<string> = [];
constructor(projectName: string, assignmentFile: AssignmentFile, executionFolder: string, composeFileOverride: Array<string> = []) {
this.projectName = projectName;
this.assignmentFile = assignmentFile;
this.executionFolder = executionFolder;
this.composeFileOverride = composeFileOverride;
this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => { this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => {
this.allLogs += log; this.allLogs += log;
this.displayableLogs += displayable ? log : ''; this.displayableLogs += displayable ? log : '';
...@@ -49,15 +59,17 @@ class ExerciseDockerCompose { ...@@ -49,15 +59,17 @@ class ExerciseDockerCompose {
} }
private registerChildProcess(childProcess: ChildProcessWithoutNullStreams, resolve: (value: (number | PromiseLike<number>)) => void, reject: (reason?: unknown) => void, displayable: boolean, rejectIfCodeIsNotZero: boolean) { private registerChildProcess(childProcess: ChildProcessWithoutNullStreams, resolve: (value: (number | PromiseLike<number>)) => void, reject: (reason?: unknown) => void, displayable: boolean, rejectIfCodeIsNotZero: boolean) {
childProcess.stdout.on('data', (data) => { childProcess.stdout.on('data', data => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call
this.log(data.toString(), false, displayable); this.log(data.toString(), false, displayable);
}); });
childProcess.stderr.on('data', (data) => { childProcess.stderr.on('data', data => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call
this.log(data.toString(), true, displayable); this.log(data.toString(), true, displayable);
}); });
childProcess.on('exit', (code) => { childProcess.on('exit', code => {
code === null || (rejectIfCodeIsNotZero && code !== 0) ? reject(code) : resolve(code); code === null || (rejectIfCodeIsNotZero && code !== 0) ? reject(code) : resolve(code);
}); });
} }
...@@ -66,7 +78,8 @@ class ExerciseDockerCompose { ...@@ -66,7 +78,8 @@ class ExerciseDockerCompose {
(async () => { (async () => {
let containerExitCode: number = -1; let containerExitCode: number = -1;
const dockerComposeCommand = `docker compose --project-name ${ this.projectName } --progress plain --file docker-compose.yml ${ this.composeFileOverride.map((file) => `--file "${ file }"`).join(' ') }`; const filesOverrideArguments = this.composeFileOverride.map(file => `--file "${ file }"`).join(' ');
const dockerComposeCommand = `docker compose --project-name ${ this.projectName } --progress plain --file docker-compose.yml ${ filesOverrideArguments }`;
// Run the service // Run the service
{ {
......
import { TypedEmitter } from 'tiny-typed-emitter'; import { TypedEmitter } from 'tiny-typed-emitter';
import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents'; import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents.js';
import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError'; import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError.js';
import path from 'node:path'; import path from 'node:path';
import ClientsSharedConfig from '../../config/ClientsSharedConfig'; import ClientsSharedConfig from '../../config/ClientsSharedConfig.js';
import Toolbox from '../../../shared/helpers/Toolbox'; import Toolbox from '../../../shared/helpers/Toolbox.js';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile'; import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile.js';
import JSON5 from 'json5'; import JSON5 from 'json5';
import Json5FileValidator from '../../../shared/helpers/Json5FileValidator'; import Json5FileValidator from '../../../shared/helpers/Json5FileValidator.js';
class ExerciseResultsSanitizerAndValidator { class ExerciseResultsSanitizerAndValidator {
...@@ -17,9 +17,17 @@ class ExerciseResultsSanitizerAndValidator { ...@@ -17,9 +17,17 @@ class ExerciseResultsSanitizerAndValidator {
private resultsFilePath: string = ''; private resultsFilePath: string = '';
constructor(private folderResultsDojo: string, private folderResultsExercise: string, private containerExitCode: number) { } private readonly folderResultsDojo: string;
private readonly folderResultsExercise: string;
private readonly containerExitCode: number;
private async resultsFileSanitization() { constructor(folderResultsDojo: string, folderResultsExercise: string, containerExitCode: number) {
this.folderResultsDojo = folderResultsDojo;
this.folderResultsExercise = folderResultsExercise;
this.containerExitCode = containerExitCode;
}
private resultsFileSanitization() {
this.events.emit('step', 'RESULTS_FILE_SANITIZATION', 'Sanitizing results file'); this.events.emit('step', 'RESULTS_FILE_SANITIZATION', 'Sanitizing results file');
if ( this.exerciseResults.success === undefined ) { if ( this.exerciseResults.success === undefined ) {
...@@ -33,11 +41,11 @@ class ExerciseResultsSanitizerAndValidator { ...@@ -33,11 +41,11 @@ class ExerciseResultsSanitizerAndValidator {
this.events.emit('endStep', 'RESULTS_FILE_SANITIZATION', 'Results file sanitized', false); this.events.emit('endStep', 'RESULTS_FILE_SANITIZATION', 'Results file sanitized', false);
} }
private async resultsFileProvided(path: string): Promise<boolean> { private async resultsFileProvided(resultFilePath: string): Promise<boolean> {
// Results file schema validation // Results file schema validation
{ {
this.events.emit('step', 'VALIDATE_RESULTS_FILE', 'Validating results file schema'); this.events.emit('step', 'VALIDATE_RESULTS_FILE', 'Validating results file schema');
const validationResults = Json5FileValidator.validateFile(ExerciseResultsFile, path); const validationResults = Json5FileValidator.validateFile(ExerciseResultsFile, resultFilePath);
if ( !validationResults.isValid ) { if ( !validationResults.isValid ) {
this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', `Results file is not valid. Here are the errors :\n${ validationResults.error }`, true); this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', `Results file is not valid. Here are the errors :\n${ validationResults.error }`, true);
this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID); this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID);
...@@ -49,7 +57,7 @@ class ExerciseResultsSanitizerAndValidator { ...@@ -49,7 +57,7 @@ class ExerciseResultsSanitizerAndValidator {
// Results file content sanitization // Results file content sanitization
await this.resultsFileSanitization(); this.resultsFileSanitization();
// Results folder size // Results folder size
...@@ -68,11 +76,6 @@ class ExerciseResultsSanitizerAndValidator { ...@@ -68,11 +76,6 @@ class ExerciseResultsSanitizerAndValidator {
return true; return true;
} }
private async resultsFileNotProvided(): Promise<boolean> {
await this.resultsFileSanitization();
return true;
}
run() { run() {
(async () => { (async () => {
// Results file existence // Results file existence
...@@ -80,7 +83,7 @@ class ExerciseResultsSanitizerAndValidator { ...@@ -80,7 +83,7 @@ class ExerciseResultsSanitizerAndValidator {
const resultsFileOriginPath = path.join(this.folderResultsExercise, ClientsSharedConfig.filenames.results); const resultsFileOriginPath = path.join(this.folderResultsExercise, ClientsSharedConfig.filenames.results);
this.resultsFilePath = path.join(this.folderResultsDojo, ClientsSharedConfig.filenames.results); this.resultsFilePath = path.join(this.folderResultsDojo, ClientsSharedConfig.filenames.results);
let result: boolean; let result: boolean = true;
if ( fs.existsSync(resultsFileOriginPath) ) { if ( fs.existsSync(resultsFileOriginPath) ) {
this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file found', false); this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file found', false);
...@@ -94,7 +97,7 @@ class ExerciseResultsSanitizerAndValidator { ...@@ -94,7 +97,7 @@ class ExerciseResultsSanitizerAndValidator {
} else { } else {
this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file not found', false); this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file not found', false);
result = await this.resultsFileNotProvided(); this.resultsFileSanitization();
} }
if ( result ) { if ( result ) {
......
import GitlabRepository from '../../shared/types/Gitlab/GitlabRepository'; import User from './User.js';
import User from './User'; import Exercise from './Exercise.js';
import Exercise from './Exercise'; import * as Gitlab from '@gitbeaker/rest';
interface Assignment { interface Assignment {
name: string; name: string;
gitlabId: number; gitlabId: number;
gitlabLink: string; gitlabLink: string;
gitlabCreationInfo: GitlabRepository; gitlabCreationInfo: Gitlab.ProjectSchema;
gitlabLastInfo: GitlabRepository; gitlabLastInfo: Gitlab.ProjectSchema;
gitlabLastInfoDate: string; gitlabLastInfoDate: string;
published: boolean; published: boolean;
......
import GitlabRepository from '../../shared/types/Gitlab/GitlabRepository'; import User from './User.js';
import { CommitSchema } from '@gitbeaker/rest'; import Assignment from './Assignment.js';
import User from './User'; import * as Gitlab from '@gitbeaker/rest';
import Assignment from './Assignment';
interface Exercise { interface Exercise {
...@@ -10,15 +9,15 @@ interface Exercise { ...@@ -10,15 +9,15 @@ interface Exercise {
name: string; name: string;
gitlabId: number; gitlabId: number;
gitlabLink: string; gitlabLink: string;
gitlabCreationInfo: GitlabRepository; gitlabCreationInfo: Gitlab.ProjectSchema;
gitlabLastInfo: GitlabRepository; gitlabLastInfo: Gitlab.ProjectSchema;
gitlabLastInfoDate: string; gitlabLastInfoDate: string;
members: Array<User> | undefined; members: Array<User> | undefined;
assignment: Assignment | undefined; assignment: Assignment | undefined;
isCorrection: boolean; isCorrection: boolean;
correctionCommit: CommitSchema | undefined; correctionCommit: Gitlab.CommitSchema | undefined;
} }
......
import AssignmentFile from '../../shared/types/Dojo/AssignmentFile'; import AssignmentFile from '../../shared/types/Dojo/AssignmentFile.js';
import Assignment from './Assignment'; import Assignment from './Assignment.js';
import GitlabFile from '../../shared/types/Gitlab/GitlabFile'; import * as Gitlab from '@gitbeaker/rest';
interface ExerciseAssignment { interface ExerciseAssignment {
assignment: Assignment; assignment: Assignment;
assignmentFile: AssignmentFile; assignmentFile: AssignmentFile;
immutable: Array<GitlabFile>; immutable: Array<Gitlab.RepositoryFileSchema>;
} }
......
import UserRole from './UserRole'; import UserRole from './UserRole.js';
import Exercise from './Exercise'; import Exercise from './Exercise.js';
import Assignment from './Assignment'; import Assignment from './Assignment.js';
import GitlabProfile from '../../shared/types/Gitlab/GitlabProfile'; import * as Gitlab from '@gitbeaker/rest';
interface User { interface User {
...@@ -10,7 +10,7 @@ interface User { ...@@ -10,7 +10,7 @@ interface User {
mail: string; mail: string;
role: UserRole; role: UserRole;
gitlabUsername: string; gitlabUsername: string;
gitlabLastInfo: GitlabProfile; gitlabLastInfo: Gitlab.ExpandedUserSchema;
isTeachingStaff: boolean; isTeachingStaff: boolean;
isAdmin: boolean; isAdmin: boolean;
deleted: boolean; deleted: boolean;
......
...@@ -2,16 +2,16 @@ enum ApiRoute { ...@@ -2,16 +2,16 @@ enum ApiRoute {
LOGIN = '/login', LOGIN = '/login',
REFRESH_TOKENS = '/refresh_tokens', REFRESH_TOKENS = '/refresh_tokens',
TEST_SESSION = '/test_session', TEST_SESSION = '/test_session',
GITLAB_CHECK_TEMPLATE_ACCESS = '/gitlab/project/{{id}}/checkTemplateAccess', GITLAB_CHECK_TEMPLATE_ACCESS = '/gitlab/project/{{gitlabProjectId}}/checkTemplateAccess',
ASSIGNMENT_GET = '/assignments/{{nameOrUrl}}', ASSIGNMENT_GET = '/assignments/{{assignmentNameOrUrl}}',
ASSIGNMENT_CREATE = '/assignments', ASSIGNMENT_CREATE = '/assignments',
ASSIGNMENT_PUBLISH = '/assignments/{{nameOrUrl}}/publish', ASSIGNMENT_PUBLISH = '/assignments/{{assignmentNameOrUrl}}/publish',
ASSIGNMENT_UNPUBLISH = '/assignments/{{nameOrUrl}}/unpublish', ASSIGNMENT_UNPUBLISH = '/assignments/{{assignmentNameOrUrl}}/unpublish',
ASSIGNMENT_CORRECTION_LINK = '/assignments/{{assignmentNameOrUrl}}/corrections', ASSIGNMENT_CORRECTION_LINK = '/assignments/{{assignmentNameOrUrl}}/corrections',
ASSIGNMENT_CORRECTION_UPDATE = '/assignments/{{assignmentNameOrUrl}}/corrections/{{exerciseIdOrUrl}}', ASSIGNMENT_CORRECTION_UPDATE = '/assignments/{{assignmentNameOrUrl}}/corrections/{{exerciseIdOrUrl}}',
EXERCISE_CREATE = '/assignments/{{nameOrUrl}}/exercises', EXERCISE_CREATE = '/assignments/{{assignmentNameOrUrl}}/exercises',
EXERCISE_ASSIGNMENT = '/exercises/{{id}}/assignment', EXERCISE_ASSIGNMENT = '/exercises/{{exerciseIdOrUrl}}/assignment',
EXERCISE_RESULTS = '/exercises/{{id}}/results' EXERCISE_RESULTS = '/exercises/{{exerciseIdOrUrl}}/results'
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment