Skip to content
Snippets Groups Projects
Commit 20bcd68f authored by vincent.steinman's avatar vincent.steinman Committed by michael.minelli
Browse files

fix copy issue

parent b5b61aa3
No related branches found
No related tags found
No related merge requests found
Showing
with 32 additions and 859 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
...@@ -12,6 +12,10 @@ enum ApiRoute { ...@@ -12,6 +12,10 @@ enum ApiRoute {
EXERCISE_CREATE = '/assignments/{{assignmentNameOrUrl}}/exercises', EXERCISE_CREATE = '/assignments/{{assignmentNameOrUrl}}/exercises',
EXERCISE_ASSIGNMENT = '/exercises/{{exerciseIdOrUrl}}/assignment', EXERCISE_ASSIGNMENT = '/exercises/{{exerciseIdOrUrl}}/assignment',
EXERCISE_RESULTS = '/exercises/{{exerciseIdOrUrl}}/results' EXERCISE_RESULTS = '/exercises/{{exerciseIdOrUrl}}/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