Skip to content
Snippets Groups Projects
Commit c2a4e515 authored by vincent.steinman's avatar vincent.steinman
Browse files

fix copy issue

parent 99205c14
No related branches found
No related tags found
1 merge request!2Tags
Showing
with 33 additions and 860 deletions
...@@ -629,7 +629,7 @@ to attach them to the start of each source file to most effectively ...@@ -629,7 +629,7 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
NodeSharedCode NodeClientSharedCode
Copyright (C) 2023 ISC / projects / Dojo / Projects / Shared Copyright (C) 2023 ISC / projects / Dojo / Projects / Shared
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
......
# NodeSharedCode # NodeSharedCode
This repo contains some code that can be shared across node projects of Dojo. This repo contains some code that can be shared across clients projects of Dojo.
## Prerequisites ## Prerequisites
These shared modules are needed :
- `NodeSharedCode` imported in parent directory with directory name `shared`
These packages are needed : These packages are needed :
- `json5` - `boxen@5.1`
- `tar-stream` - `chalk@4.1`
- `winston` - `fs-extra`
- `zod` - `yaml`
- `zod-validation-error`
## How to use it ## How to use it
By adding this repo as submodule By adding this repo as submodule
```bash ```bash
git submodule add ../../shared/nodesharedcode.git shared git submodule add ../../shared/nodeclientsharedcode.git sharedByClients
``` ```
\ No newline at end of file
class SharedConfig {
public readonly production: boolean;
public debug: boolean = false;
public readonly logsFolder: string;
public gitlab: {
URL: string, apiURL: string
};
public readonly login: {
gitlab: {
client: {
id: string
}, url: {
redirect: string, token: string
}
}
};
constructor() {
this.production = process.env.NODE_ENV === 'production';
this.logsFolder = process.env.LOGS_FOLDER || '';
this.gitlab = {
URL : process.env.GITLAB_URL || '',
apiURL: process.env.GITLAB_API_URL || ''
};
this.login = {
gitlab: {
client: {
id: process.env.LOGIN_GITLAB_CLIENT_ID || ''
},
url : {
redirect: process.env.LOGIN_GITLAB_URL_REDIRECT || '',
token : process.env.LOGIN_GITLAB_URL_TOKEN || ''
}
}
};
}
}
export default new SharedConfig();
import fs from 'node:fs';
import path from 'node:path';
import tar from 'tar-stream';
import stream from 'node:stream';
import { Writable } from 'stream';
import zlib from 'zlib';
class ArchiveHelper {
private async explore(absoluteBasePath: string, rootPath: string, pack: tar.Pack) {
for ( let file of await fs.promises.readdir(rootPath) ) {
if ( file === 'output.tar' ) {
continue;
}
file = path.join(rootPath, file);
const stat = await fs.promises.stat(file);
if ( stat.isDirectory() ) {
await this.explore(absoluteBasePath, file, pack);
continue;
}
const entry = pack.entry({
name: file.replace(absoluteBasePath, ''),
size: stat.size
}, (err) => {
if ( err ) {
throw err;
}
});
const stream = fs.createReadStream(file);
stream.pipe(entry);
}
}
private async compress(folderPath: string, tarDataStream: stream.Writable) {
const pack = tar.pack();
await this.explore(folderPath, folderPath, pack);
pack.pipe(zlib.createGzip()).pipe(tarDataStream);
pack.finalize();
}
public async getBase64(folderPath: string): Promise<string> {
let data: string;
const tarDataStream = new stream.Writable({
write(this: Writable, chunk: Buffer, _encoding: BufferEncoding, next: (error?: Error | null) => void) {
if ( data ) {
data += chunk.toString('hex');
} else {
data = chunk.toString('hex');
}
next();
}
});
await this.compress(folderPath, tarDataStream);
data = await (new Promise((resolve) => {
tarDataStream.on('close', () => {
resolve(data);
});
}));
return Buffer.from(data, 'hex').toString('base64');
}
}
export default new ArchiveHelper();
\ No newline at end of file
import AssignmentFile from '../../types/Dojo/AssignmentFile';
import GitlabPipelineStatus from '../../types/Gitlab/GitlabPipelineStatus';
import DojoStatusCode from '../../types/Dojo/DojoStatusCode';
import GitlabPipeline from '../../types/Gitlab/GitlabPipeline';
import SharedGitlabManager from '../../managers/SharedGitlabManager';
import Json5FileValidator from '../Json5FileValidator';
class SharedAssignmentHelper {
validateDescriptionFile(filePathOrStr: string, isFile: boolean = true, version: number = 1): { content: AssignmentFile | undefined, isValid: boolean, error: string | null } {
switch ( version ) {
case 1:
return Json5FileValidator.validateFile(AssignmentFile, filePathOrStr, isFile);
default:
return {
content: undefined,
isValid: false,
error : `Version ${ version } not supported`
};
}
}
async isPublishable(repositoryId: number): Promise<{ isPublishable: boolean, lastPipeline: GitlabPipeline | null, status?: { code: DojoStatusCode, message: string } }> {
const pipelines = await SharedGitlabManager.getRepositoryPipelines(repositoryId, 'main');
if ( pipelines.length > 0 ) {
const lastPipeline = pipelines[0];
if ( lastPipeline.status != GitlabPipelineStatus.SUCCESS ) {
return {
isPublishable: false,
lastPipeline : lastPipeline,
status : {
code : DojoStatusCode.ASSIGNMENT_PUBLISH_PIPELINE_FAILED,
message: `Last pipeline status is not "${ GitlabPipelineStatus.SUCCESS }" but "${ lastPipeline.status }".`
}
};
} else {
return {
isPublishable: true,
lastPipeline : lastPipeline
};
}
} else {
return {
isPublishable: false,
lastPipeline : null,
status : {
code : DojoStatusCode.ASSIGNMENT_PUBLISH_NO_PIPELINE,
message: 'No pipeline found for this assignment.'
}
};
}
}
}
export default new SharedAssignmentHelper();
\ No newline at end of file
class SharedExerciseHelper {}
export default new SharedExerciseHelper();
\ No newline at end of file
import JSON5 from 'json5';
import fs from 'fs';
import { z, ZodError } from 'zod';
import { fromZodError } from 'zod-validation-error';
class Json5FileValidator {
validateFile<T>(schema: z.ZodType<T>, filePathOrStr: string, isFile: boolean = true, resultSanitizer: (value: T) => T = value => value): { content: T | undefined, isValid: boolean, error: string | null } {
let parsedInput: T;
try {
parsedInput = JSON5.parse(isFile ? fs.readFileSync(filePathOrStr, 'utf8') : filePathOrStr);
} catch ( error ) {
return {
content: undefined,
isValid: false,
error : `JSON5 invalid : ${ JSON.stringify(error) }`
};
}
try {
return {
content: resultSanitizer(schema.parse(parsedInput) as unknown as T),
isValid: true,
error : null
};
} catch ( error ) {
if ( error instanceof ZodError ) {
return {
content: parsedInput,
isValid: false,
error : fromZodError(error).toString()
};
}
return {
content: parsedInput,
isValid: false,
error : `Unknown error : ${ JSON.stringify(error) }`
};
}
}
}
export default new Json5FileValidator();
\ No newline at end of file
class LazyVal<T> {
private val: T | undefined = undefined;
constructor(private valLoader: () => Promise<T> | T) {}
get value(): Promise<T> {
return new Promise<T>((resolve) => {
if ( this.val === undefined ) {
Promise.resolve(this.valLoader()).then((value: T) => {
this.val = value;
resolve(value);
});
} else {
resolve(this.val);
}
});
}
reset() {
this.val = undefined;
}
get isValueLoaded(): boolean {
return this.val !== undefined;
}
}
export default LazyVal;
import fs from 'fs/promises';
import path from 'path';
class Toolbox {
public urlToPath(url: string): string {
return url.replace(/^([a-z]{3,5}:\/{2})?[a-z.@]+(:[0-9]{1,5})?.(.*)/, '$3').replace('.git', '');
}
/*
Source of getAllFiles and getTotalSize (modified for this project): https://coderrocketfuel.com/article/get-the-total-size-of-all-files-in-a-directory-using-node-js
*/
private async getAllFiles(dirPath: string, arrayOfFiles: Array<string> = []): Promise<Array<string>> {
const files = await fs.readdir(dirPath);
await Promise.all(files.map(async file => {
if ( (await fs.stat(dirPath + '/' + file)).isDirectory() ) {
arrayOfFiles = await this.getAllFiles(dirPath + '/' + file, arrayOfFiles);
} else {
arrayOfFiles.push(path.join(dirPath, file));
}
}));
return arrayOfFiles;
}
private async getTotalSize(directoryPath: string): Promise<number> {
const arrayOfFiles = await this.getAllFiles(directoryPath);
let totalSize = 0;
for ( const filePath of arrayOfFiles ) {
totalSize += (await fs.stat(filePath)).size;
}
return totalSize;
}
get fs() {
return {
getAllFiles : this.getAllFiles.bind(this),
getTotalSize: this.getTotalSize.bind(this)
};
}
public snakeToCamel(str: string): string {
return str.toLowerCase().replace(/([-_][a-z])/g, (group: string) => group.toUpperCase().replace('-', '').replace('_', ''));
}
public getKeysWithPrefix(obj: object, prefix: string): Array<string> {
return Object.keys(obj).filter(key => key.startsWith(prefix));
}
}
export default new Toolbox();
declare global {
interface BigInt {
toJSON: () => string;
}
interface Array<T> {
removeObjectDuplicates: (getProperty: (item: T) => unknown) => Array<T>;
}
interface String {
toBoolean: () => boolean;
capitalizingFirstLetter: () => string;
capitalizeName: () => string;
convertWithEnvVars: () => string;
}
}
function registerAll() {
registerBigIntJson();
registerArrayRemoveObjectDuplicates();
registerStringToBoolean();
registerStringCapitalizingFirstLetter();
registerStringCapitalizeName();
registerStringConvertWithEnvVars();
}
function registerBigIntJson() {
BigInt.prototype.toJSON = function () {
return this.toString();
};
}
function registerArrayRemoveObjectDuplicates() {
Array.prototype.removeObjectDuplicates = function <T>(this: Array<T>, getProperty: (item: T) => unknown): Array<T> {
return this.reduce((accumulator: Array<T>, current: T) => {
if ( !accumulator.find((item: T) => getProperty(item) === getProperty(current)) ) {
accumulator.push(current);
}
return accumulator;
}, Array<T>());
};
}
function registerStringToBoolean() {
String.prototype.toBoolean = function (this: string): boolean {
const tmp = this.toLowerCase().trim();
return tmp === 'true' || tmp === '1';
};
}
function registerStringCapitalizingFirstLetter() {
String.prototype.capitalizingFirstLetter = function (this: string): string {
return this.charAt(0).toUpperCase() + this.slice(1);
};
}
function registerStringCapitalizeName() {
String.prototype.capitalizeName = function (this: string): string {
return this.trim().replace(/(?:^|\s|-)\S/g, s => s.toUpperCase());
};
}
function registerStringConvertWithEnvVars() {
String.prototype.convertWithEnvVars = function (this: string): string {
return this.replace(/\${?([a-zA-Z0-9_]+)}?/g, (_match: string, p1: string) => {
return process.env[p1] || '';
});
};
}
registerAll();
export default null;
\ No newline at end of file
Source: recursive-readdir-files
===
Modified for Dojo
===
## Usage
```js
import recursiveReaddirFiles from 'recursive-readdir-files';
const files = await recursiveReaddirFiles(process.cwd(), {
ignored: /\/(node_modules|\.git)/
});
// `files` is an array
console.log(files);
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// [
// {
// dev: 16777233,
// mode: 33188,
// nlink: 1,
// uid: 501,
// gid: 20,
// rdev: 0,
// blksize: 4096,
// ino: 145023089,
// size: 89,
// blocks: 8,
// atimeMs: 1649303678077.934,
// mtimeMs: 1649303676847.1777,
// ctimeMs: 1649303676847.1777,
// birthtimeMs: 1649301118132.6782,
// atime: 2022-04-07T03:54:38.078Z,
// mtime: 2022-04-07T03:54:36.847Z,
// ctime: 2022-04-07T03:54:36.847Z,
// birthtime: 2022-04-07T03:11:58.133Z,
// name: 'watch.ts',
// path: '/Users/xxx/watch.ts',
// ext: 'ts'
// },
// // ...
// ]
```
Or
```js
recursiveReaddirFiles(process.cwd(), {
ignored: /\/(node_modules|\.git)/
}, (filepath, state) => {
console.log(filepath);
// 👉 /Users/xxx/watch.ts
console.log(state.isFile()); // 👉 true
console.log(state.isDirectory()); // 👉 false
console.log(state);
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// {
// dev: 16777233,
// mode: 33188,
// nlink: 1,
// uid: 501,
// gid: 20,
// rdev: 0,
// blksize: 4096,
// ino: 145023089,
// size: 89,
// blocks: 8,
// atimeMs: 1649303678077.934,
// mtimeMs: 1649303676847.1777,
// ctimeMs: 1649303676847.1777,
// birthtimeMs: 1649301118132.6782,
// atime: 2022-04-07T03:54:38.078Z,
// mtime: 2022-04-07T03:54:36.847Z,
// ctime: 2022-04-07T03:54:36.847Z,
// birthtime: 2022-04-07T03:11:58.133Z,
// name: 'watch.ts',
// path: '/Users/xxx/watch.ts',
// ext: 'ts'
// }
})
```
## Options
```ts
export interface RecursiveReaddirFilesOptions {
/**
* Ignore files
* @example `/\/(node_modules|\.git)/`
*/
ignored?: RegExp;
/**
* Specifies a list of `glob` patterns that match files to be included in compilation.
* @example `/(\.json)$/`
*/
include?: RegExp;
/**
* Specifies a list of files to be excluded from compilation.
* @example `/(package\.json)$/`
*/
exclude?: RegExp;
/** Provide filtering methods to filter data. */
filter?: (item: IFileDirStat) => boolean;
/** Do not give the absolute path but the relative one from the root folder */
replacePathByRelativeOne?: boolean;
/** Remove stats that are not necessary for transfert */
liteStats?: boolean;
}
```
## Result
```ts
import fs from 'node:fs';
export interface IFileDirStat extends Partial<fs.Stats> {
/**
* @example `/a/sum.jpg` => `sum.jpg`
*/
name: string;
/**
* @example `/basic/src/utils/sum.ts`
*/
path: string;
/**
* @example `/a/b.jpg` => `jpg`
*/
ext?: string;
}
declare type Callback = (filepath: string, stat: IFileDirStat) => void;
export default function recursiveReaddirFiles(rootPath: string, options?: RecursiveReaddirFilesOptions, callback?: Callback): Promise<IFileDirStat[]>;
export { recursiveReaddirFiles };
export declare const getStat: (filepath: string) => Promise<IFileDirStat>;
/**
* Get ext
* @param {String} filePath `/a/b.jpg` => `jpg`
*/
export declare const getExt: (filePath: string) => string;
```
## License
Licensed under the MIT License.
import fs from 'node:fs';
import path from 'node:path';
export interface RecursiveReaddirFilesOptions {
/**
* Ignore files
* @example `/\/(node_modules|\.git)/`
*/
ignored?: RegExp;
/**
* Specifies a list of `glob` patterns that match files to be included in compilation.
* @example `/(\.json)$/`
*/
include?: RegExp;
/**
* Specifies a list of files to be excluded from compilation.
* @example `/(package\.json)$/`
*/
exclude?: RegExp;
/** Provide filtering methods to filter data. */
filter?: (item: IFileDirStat) => boolean;
/** Do not give the absolute path but the relative one from the root folder */
replacePathByRelativeOne?: boolean;
/** Remove stats that are not necessary for transfert */
liteStats?: boolean;
}
export interface IFileDirStat extends Partial<fs.Stats> {
/**
* @example `/a/sum.jpg` => `sum.jpg`
*/
name: string;
/**
* @example `/basic/src/utils/sum.ts`
*/
path: string;
/**
* @example `/a/b.jpg` => `jpg`
*/
ext?: string;
}
type Callback = (filepath: string, stat: IFileDirStat) => void;
class RecursiveFilesStats {
async explore(rootPath: string, options: RecursiveReaddirFilesOptions = {}, callback?: Callback): Promise<IFileDirStat[]> {
return this.getFiles(`${ path.resolve(rootPath) }/`, rootPath, options, [], callback);
}
private async getFiles(absoluteBasePath: string, rootPath: string, options: RecursiveReaddirFilesOptions = {}, files: IFileDirStat[] = [], callback?: Callback): Promise<IFileDirStat[]> {
const {
ignored, include, exclude, filter
} = options;
const filesData = await fs.promises.readdir(rootPath);
const fileDir: IFileDirStat[] = filesData.map((file) => ({
name: file, path: path.join(rootPath, file)
})).filter((item) => {
if ( include && include.test(item.path) ) {
return true;
}
if ( exclude && exclude.test(item.path) ) {
return false;
}
if ( ignored ) {
return !ignored.test(item.path);
}
return true;
});
if ( callback ) {
fileDir.map(async (item: IFileDirStat) => {
const stat = await this.getStat(item.path, absoluteBasePath, options);
if ( stat.isDirectory!() ) {
await this.getFiles(absoluteBasePath, item.path, options, [], callback);
}
callback(item.path, stat);
});
} else {
await Promise.all(fileDir.map(async (item: IFileDirStat) => {
const stat = await this.getStat(item.path, absoluteBasePath, options);
if ( stat.isDirectory!() ) {
const arr = await this.getFiles(absoluteBasePath, item.path, options, []);
files = files.concat(arr);
} else if ( stat.isFile!() ) {
files.push(stat);
}
}));
}
return files.filter((item) => {
if ( filter && typeof filter === 'function' ) {
return filter(item);
}
return true;
});
}
private async getStat(filepath: string, absoluteRootPath: string, options: RecursiveReaddirFilesOptions): Promise<IFileDirStat> {
const stat = (await fs.promises.stat(filepath)) as IFileDirStat;
stat.ext = '';
if ( stat.isFile!() ) {
stat.ext = this.getExt(filepath);
stat.name = path.basename(filepath);
stat.path = path.resolve(filepath);
}
if ( options.replacePathByRelativeOne && stat.path ) {
stat.path = stat.path.replace(absoluteRootPath, '');
}
if ( options.liteStats ) {
delete stat.dev;
delete stat.nlink;
delete stat.uid;
delete stat.gid;
delete stat.rdev;
delete stat.blksize;
delete stat.ino;
delete stat.blocks;
delete stat.atimeMs;
delete stat.mtimeMs;
delete stat.ctimeMs;
delete stat.birthtimeMs;
delete stat.atime;
//delete stat.mtime;
delete stat.ctime;
//delete stat.birthtime;
//delete stat.mode;
}
return stat;
}
/**
* Get ext
* @param {String} filePath `/a/b.jpg` => `jpg`
*/
private getExt(filePath: string): string {
return path.extname(filePath).replace(/^\./, '').toLowerCase();
}
}
export default new RecursiveFilesStats();
import winston from 'winston';
import SharedConfig from '../config/SharedConfig';
import * as Transport from 'winston-transport';
const levels = {
error: 0,
warn : 1,
info : 2,
http : 3,
debug: 4
};
const colors = {
error: 'red',
warn : 'orange',
info : 'green',
http : 'magenta',
debug: 'blue'
};
winston.addColors(colors);
const format = winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format(info => ({
...info,
level: info.level.toUpperCase()
}))(), SharedConfig.production ? winston.format.uncolorize() : winston.format.colorize({ all: true }), winston.format.prettyPrint(), winston.format.errors({ stack: true }), winston.format.align(), winston.format.printf((info) => `[${ info.timestamp }] (${ process.pid }) ${ info.level } ${ info.message } ${ info.metadata ? `\n${ JSON.stringify(info.metadata) }` : '' } ${ info.stack ? `\n${ info.stack }` : '' } `));
const commonTransportOptions = {
handleRejections: true,
handleExceptions: true
};
let transports: Array<Transport> = [ new winston.transports.Console({
...commonTransportOptions,
level: 'debug'
}) ];
if ( SharedConfig.production ) {
const commonFileOptions = {
...commonTransportOptions,
maxsize : 5242880, // 5MB
maxFiles: 100,
tailable: true
};
transports = transports.concat([ new winston.transports.File({
...commonFileOptions,
filename: `${ SharedConfig.logsFolder }/error.log`,
level : 'error'
}), new winston.transports.File({
...commonFileOptions,
filename: `${ SharedConfig.logsFolder }/all.log`,
level : 'debug'
}) ]);
}
const logger = winston.createLogger({
levels,
format,
transports,
exitOnError: false
});
export default logger;
import axios from 'axios';
import GitlabPipeline from '../types/Gitlab/GitlabPipeline';
import GitlabRoute from '../types/Gitlab/GitlabRoute';
import SharedConfig from '../config/SharedConfig';
import GitlabToken from '../types/Gitlab/GitlabToken';
class GitlabManager {
private getApiUrl(route: GitlabRoute): string {
return `${ SharedConfig.gitlab.apiURL }${ route }`;
}
async getTokens(codeOrRefresh: string, isRefresh: boolean = false, clientSecret: string = ''): Promise<GitlabToken> {
const response = await axios.post<GitlabToken>(SharedConfig.login.gitlab.url.token, {
client_id : SharedConfig.login.gitlab.client.id,
client_secret: clientSecret,
grant_type : isRefresh ? 'refresh_token' : 'authorization_code',
refresh_token: codeOrRefresh,
code : codeOrRefresh,
redirect_uri : SharedConfig.login.gitlab.url.redirect
});
return response.data;
}
async getRepositoryPipelines(repoId: number, branch: string = 'main'): Promise<Array<GitlabPipeline>> {
const response = await axios.get<Array<GitlabPipeline>>(this.getApiUrl(GitlabRoute.REPOSITORY_PIPELINES).replace('{{id}}', String(repoId)), {
params: {
ref: branch
}
});
return response.data;
}
}
export default new GitlabManager();
import { CommitSchema } from '@gitbeaker/rest';
import Assignment from './Assignment';
import Exercise from './Exercise';
interface Tag {
name: string;
type: 'Language' | 'Framework' | 'Theme' | 'UserDefined';
exercise: Exercise | undefined;
assignment: Assignment | undefined;
correctionCommit: CommitSchema | undefined;
}
export default Tag;
\ No newline at end of file
...@@ -11,7 +11,11 @@ enum ApiRoute { ...@@ -11,7 +11,11 @@ enum ApiRoute {
ASSIGNMENT_CORRECTION_UPDATE = '/assignments/{{assignmentNameOrUrl}}/corrections/{{exerciseIdOrUrl}}', ASSIGNMENT_CORRECTION_UPDATE = '/assignments/{{assignmentNameOrUrl}}/corrections/{{exerciseIdOrUrl}}',
EXERCISE_CREATE = '/assignments/{{nameOrUrl}}/exercises', EXERCISE_CREATE = '/assignments/{{nameOrUrl}}/exercises',
EXERCISE_ASSIGNMENT = '/exercises/{{id}}/assignment', EXERCISE_ASSIGNMENT = '/exercises/{{id}}/assignment',
EXERCISE_RESULTS = '/exercises/{{id}}/results' EXERCISE_RESULTS = '/exercises/{{id}}/results',
ADD_TAG = '/tags',
DELETE_TAG = '/tags/{tagid}',
PROPOSE_TAG = '/tags/proposals',
ANSWER_TAG_PROPOSAL = '/tags/proposals/{tagProposalName}'
} }
......
enum ExerciseCheckerError {
DOCKER_DAEMON_NOT_RUNNING = 200,
REQUIRED_FILES_MISSING = 201,
ASSIGNMENT_FILE_SCHEMA_ERROR = 202,
IMMUTABLE_PATH_NOT_FOUND = 203,
IMMUTABLE_PATH_IS_DIRECTORY = 204,
IMMUTABLE_PATH_IS_NOT_DIRECTORY = 205,
COMPOSE_FILE_YAML_ERROR = 206,
COMPOSE_FILE_SCHEMA_ERROR = 207,
COMPOSE_FILE_CONTAINER_MISSING = 208,
COMPOSE_FILE_VOLUME_MISSING = 209,
DOCKERFILE_NOT_FOUND = 210,
COMPOSE_RUN_SUCCESSFULLY = 211, // Yes, this is an error
}
/**
* Codes that are unusable for historic reasons:
* None
*/
export default ExerciseCheckerError;
\ No newline at end of file
import ImmutableFileDescriptor from './ImmutableFileDescriptor';
import { z } from 'zod';
const AssignmentFile = z.object({
dojoAssignmentVersion: z.number(),
version : z.number(),
immutable : z.array(ImmutableFileDescriptor.transform(value => value as ImmutableFileDescriptor)),
result : z.object({
container: z.string(),
volume : z.string().optional()
})
}).strict();
type AssignmentFile = z.infer<typeof AssignmentFile>;
export default AssignmentFile;
\ No newline at end of file
interface DojoBackendResponse<T> {
timestamp: string;
code: number;
description: string;
sessionToken: string | null;
data: T;
}
export default DojoBackendResponse;
\ No newline at end of file
enum DojoStatusCode {
LOGIN_FAILED = 1,
REFRESH_TOKENS_FAILED = 2,
CLIENT_NOT_SUPPORTED = 100,
CLIENT_VERSION_NOT_SUPPORTED = 110,
ASSIGNMENT_PUBLISH_NO_PIPELINE = 200,
ASSIGNMENT_PUBLISH_PIPELINE_FAILED = 201,
ASSIGNMENT_CREATION_GITLAB_ERROR = 202,
ASSIGNMENT_CREATION_INTERNAL_ERROR = 203,
ASSIGNMENT_EXERCISE_NOT_RELATED = 204,
ASSIGNMENT_NOT_PUBLISHED = 205,
EXERCISE_CORRECTION_NOT_EXIST = 206,
EXERCISE_CORRECTION_ALREADY_EXIST = 207,
EXERCISE_CREATION_GITLAB_ERROR = 302,
EXERCISE_CREATION_INTERNAL_ERROR = 303,
MAX_EXERCISE_PER_ASSIGNMENT_REACHED = 304
}
export default DojoStatusCode;
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment