Skip to content
Snippets Groups Projects

Compare revisions

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

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • dojo_project/projects/shared/nodesharedcode
1 result
Select Git revision
Show changes
Commits on Source (14)
...@@ -6,10 +6,11 @@ This repo contains some code that can be shared across node projects of Dojo. ...@@ -6,10 +6,11 @@ This repo contains some code that can be shared across node projects of Dojo.
These packages are needed : These packages are needed :
- `ajv`
- `json5` - `json5`
- `tar-stream` - `tar-stream`
- `winston` - `winston`
- `zod`
- `zod-validation-error`
## How to use it ## How to use it
......
class SharedConfig { class SharedConfig {
public readonly production: boolean; public readonly production: boolean;
public debug: boolean = false;
public readonly logsFolder: string; public readonly logsFolder: string;
......
import Ajv, { ErrorObject, JTDSchemaType } from 'ajv/dist/jtd'; import AssignmentFile from '../../types/Dojo/AssignmentFile';
import fs from 'fs'; import GitlabPipelineStatus from '../../types/Gitlab/GitlabPipelineStatus';
import JSON5 from 'json5'; import DojoStatusCode from '../../types/Dojo/DojoStatusCode';
import AssignmentFile from '../../types/Dojo/AssignmentFile'; import GitlabPipeline from '../../types/Gitlab/GitlabPipeline';
import GitlabPipelineStatus from '../../types/Gitlab/GitlabPipelineStatus'; import SharedGitlabManager from '../../managers/SharedGitlabManager';
import DojoStatusCode from '../../types/Dojo/DojoStatusCode'; import Json5FileValidator from '../Json5FileValidator';
import GitlabPipeline from '../../types/Gitlab/GitlabPipeline';
import SharedGitlabManager from '../../managers/SharedGitlabManager';
class SharedAssignmentHelper { class SharedAssignmentHelper {
private validateDescriptionFileV1(resultsFilePathOrStr: string, isFile: boolean = true): { results: AssignmentFile | undefined, isValid: boolean, errors: Array<ErrorObject | string> | null | undefined } { validateDescriptionFile(filePathOrStr: string, isFile: boolean = true, version: number = 1): { content: AssignmentFile | undefined, isValid: boolean, error: string | null } {
const ajv = new Ajv();
const schema: JTDSchemaType<AssignmentFile> = {
properties : {
dojoAssignmentVersion: { type: 'uint32' },
version : { type: 'uint32' },
immutable: {
elements: {
properties : {
path: { type: 'string' }
},
optionalProperties: {
description: { type: 'string' },
isDirectory: { type: 'boolean' }
}
}
},
result: {
properties : {
container: { type: 'string' }
},
optionalProperties: {
volume: { type: 'string' }
}
}
},
additionalProperties: false
};
const validator = ajv.compile(schema);
try {
const results = JSON5.parse(isFile ? fs.readFileSync(resultsFilePathOrStr, 'utf8') : resultsFilePathOrStr);
const isValid = validator(results);
return {
results: isValid ? results : results as AssignmentFile,
isValid: isValid,
errors : validator.errors
};
} catch ( error ) {
return {
results: undefined,
isValid: false,
errors : [ `JSON5 invalid : ${ JSON.stringify(error) }` ]
};
}
}
validateDescriptionFile(resultsFilePathOrStr: string, isFile: boolean = true, version: number = 1): { results: AssignmentFile | undefined, isValid: boolean, errors: Array<ErrorObject | string> | null | undefined } {
switch ( version ) { switch ( version ) {
case 1: case 1:
return this.validateDescriptionFileV1(resultsFilePathOrStr, isFile); return Json5FileValidator.validateFile(AssignmentFile, filePathOrStr, isFile);
default: default:
return { return {
results: undefined, content: undefined,
isValid: false, isValid: false,
errors : [ `Version ${ version } not supported` ] error : `Version ${ version } not supported`
}; };
} }
} }
......
import Ajv, { ErrorObject, JTDSchemaType } from 'ajv/dist/jtd'; class SharedExerciseHelper {}
import fs from 'fs';
import ExerciseResultsFile from '../../types/Dojo/ExerciseResultsFile';
import JSON5 from 'json5';
class SharedExerciseHelper {
validateResultFile(resultsFilePathOrStr: string, isFile: boolean = true): { results: ExerciseResultsFile | undefined, isValid: boolean, errors: Array<ErrorObject | string> | null | undefined } {
const ajv = new Ajv();
const schema: JTDSchemaType<ExerciseResultsFile> = {
properties : {},
optionalProperties : {
success: { type: 'boolean' },
containerExitCode: { type: 'uint32' },
successfulTests: { type: 'uint32' },
failedTests : { type: 'uint32' },
successfulTestsList: {
elements: {
type: 'string'
}
},
failedTestsList : {
elements: {
type: 'string'
}
}
},
additionalProperties: false
};
const validator = ajv.compile(schema);
try {
const results = JSON5.parse(isFile ? fs.readFileSync(resultsFilePathOrStr, 'utf8') : resultsFilePathOrStr);
const isValid = validator(results);
if ( isValid ) {
if ( results.successfulTests === undefined && results.successfulTestsList !== undefined ) {
results.successfulTests = results.successfulTestsList.length;
}
if ( results.failedTests === undefined && results.failedTestsList !== undefined ) {
results.failedTests = results.failedTestsList.length;
}
}
return {
results: isValid ? results : results as ExerciseResultsFile,
isValid: isValid,
errors : validator.errors
};
} catch ( error ) {
return {
results: undefined,
isValid: false,
errors : [ `JSON5 invalid : ${ JSON.stringify(error) }` ]
};
}
}
}
export default new 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
import ImmutableFileDescriptor from './ImmutableFileDescriptor'; import ImmutableFileDescriptor from './ImmutableFileDescriptor';
import { z } from 'zod';
interface AssignmentFile { const AssignmentFile = z.object({
dojoAssignmentVersion: number, dojoAssignmentVersion: z.number(),
version: number, version : z.number(),
immutable : z.array(ImmutableFileDescriptor.transform(value => value as ImmutableFileDescriptor)),
result : z.object({
container: z.string(),
volume : z.string().optional()
})
}).strict();
immutable: Array<ImmutableFileDescriptor>
result: { type AssignmentFile = z.infer<typeof AssignmentFile>;
container: string, volume?: string
}
}
export default AssignmentFile; export default AssignmentFile;
\ No newline at end of file
...@@ -5,7 +5,8 @@ enum ExerciseCheckerError { ...@@ -5,7 +5,8 @@ enum ExerciseCheckerError {
DOCKER_COMPOSE_DOWN_ERROR = 203, DOCKER_COMPOSE_DOWN_ERROR = 203,
EXERCISE_RESULTS_FOLDER_TOO_BIG = 204, EXERCISE_RESULTS_FOLDER_TOO_BIG = 204,
EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID = 206, EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID = 206,
UPLOAD = 207 UPLOAD = 207,
DOCKER_COMPOSE_REMOVE_DANGLING_ERROR = 208
} }
......
interface ExerciseResultsFile { import Icon from '../Icon';
success?: boolean; import { z } from 'zod';
containerExitCode?: number;
successfulTests?: number; const ExerciseResultsFile = z.object({
failedTests?: number; success: z.boolean().optional(),
successfulTestsList?: Array<string>; containerExitCode: z.number().optional(),
failedTestsList?: Array<string>;
} successfulTests: z.number().optional(),
failedTests : z.number().optional(),
successfulTestsList: z.array(z.string()).optional(),
failedTestsList : z.array(z.string()).optional(),
otherInformations: z.array(z.object({
name : z.string(),
description : z.string().optional(),
icon : z.enum(Object.keys(Icon) as [ firstKey: string, ...otherKeys: Array<string> ]).optional(),
itemsOrInformations: z.union([ z.array(z.string()), z.string() ])
}))
.optional()
}).strict().transform(value => {
if ( value.successfulTests === undefined && value.successfulTestsList !== undefined ) {
value.successfulTests = value.successfulTestsList.length;
}
if ( value.failedTests === undefined && value.failedTestsList !== undefined ) {
value.failedTests = value.failedTestsList.length;
}
return value;
});
type ExerciseResultsFile = z.infer<typeof ExerciseResultsFile>;
export default ExerciseResultsFile; export default ExerciseResultsFile;
\ No newline at end of file
interface ImmutableFileDescriptor { import { z } from 'zod';
description?: string,
path: string,
isDirectory?: boolean, const ImmutableFileDescriptor = z.object({
} description: z.string().optional(),
path : z.string(),
isDirectory: z.boolean().optional()
});
type ImmutableFileDescriptor = z.infer<typeof ImmutableFileDescriptor>;
export default ImmutableFileDescriptor; export default ImmutableFileDescriptor;
\ No newline at end of file
interface GitlabCommit {
id: string;
short_id: string;
created_at: string;
parent_ids: Array<string>;
title: string;
message: string;
author_name: string;
author_email: string;
authored_date: string;
committer_name: string;
committer_email: string;
committed_date: string;
}
export default GitlabCommit;
\ No newline at end of file
interface GitlabMilestone {
id: number;
iid: number;
project_id: number;
title: string;
description: string;
state: string;
created_at: string;
updated_at: string;
due_date: string;
start_date: string;
web_url: string;
issue_stats: {
total: number; closed: number;
};
}
export default GitlabMilestone;
\ No newline at end of file
import GitlabUser from './GitlabUser';
import GitlabCommit from './GitlabCommit';
import GitlabMilestone from './GitlabMilestone';
interface GitlabRelease {
tag_name: string;
description: string;
created_at: string;
released_at: string;
author: GitlabUser;
commit: GitlabCommit;
milestones: Array<GitlabMilestone>;
commit_path: string;
tag_path: string;
assets: {
count: number; sources: Array<{
format: string; url: string;
}>; links: Array<{
id: number; name: string; url: string; link_type: string;
}>; evidence_file_path: string;
};
evidences: Array<{
sha: string; filepath: string; collected_at: string;
}>;
}
export default GitlabRelease;
\ No newline at end of file
...@@ -9,6 +9,7 @@ enum GitlabRoute { ...@@ -9,6 +9,7 @@ enum GitlabRoute {
REPOSITORY_FORK = '/projects/{{id}}/fork', REPOSITORY_FORK = '/projects/{{id}}/fork',
REPOSITORY_MEMBER_ADD = '/projects/{{id}}/members', REPOSITORY_MEMBER_ADD = '/projects/{{id}}/members',
REPOSITORY_MEMBERS_GET = '/projects/{{id}}/members/all', REPOSITORY_MEMBERS_GET = '/projects/{{id}}/members/all',
REPOSITORY_RELEASES_GET = '/projects/{{id}}/releases',
REPOSITORY_BADGES_ADD = '/projects/{{id}}/badges', REPOSITORY_BADGES_ADD = '/projects/{{id}}/badges',
REPOSITORY_VARIABLES_ADD = '/projects/{{id}}/variables', REPOSITORY_VARIABLES_ADD = '/projects/{{id}}/variables',
REPOSITORY_BRANCHES_PROTECT = '/projects/{{id}}/protected_branches', REPOSITORY_BRANCHES_PROTECT = '/projects/{{id}}/protected_branches',
......
const Icon = {
CAT_INFO : '▶️',
INFO : 'ℹ️',
ERROR : '⛔️',
SUCCESS : '',
FAILURE : '',
VOMIT : '🤮',
YUCK : '🤢',
WELL_DONE: '👍',
NULL : '',
NONE : '',
BADMINTON: '🏸'
} as { [index: string]: string };
export default Icon;
\ No newline at end of file