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
  • jw_sonar
  • jw_sonar_backup
  • main
  • move-to-esm-only
  • open_tool_for_self_hosting
  • v5.0
  • v4.1
  • v4.2
8 results

Target

Select target project
  • dojo_project/projects/shared/nodesharedcode
1 result
Select Git revision
  • jw_sonar
  • jw_sonar_backup
  • main
  • move-to-esm-only
  • open_tool_for_self_hosting
  • v5.0
  • v4.1
  • v4.2
8 results
Show changes
Commits on Source (50)
Showing
with 260 additions and 204 deletions
import * as process from 'process';
class SharedConfig { class SharedConfig {
public readonly production: boolean; public readonly production: boolean;
public debug: boolean = false; public debug: boolean = false;
public readonly logsFolder: string; public readonly logsFolder: string;
public gitlab: { public sonar: {
URL: string, apiURL: string enabled: boolean
}; url: string
token: string
public readonly login: { }
gitlab: {
client: {
id: string
}, url: {
redirect: string, token: string
}
}
};
constructor() { constructor() {
this.production = process.env.NODE_ENV === 'production'; this.production = process.env.NODE_ENV === 'production';
this.sonar = {
this.logsFolder = process.env.LOGS_FOLDER || ''; enabled: ['yes', 'true', '1', 'on'].includes(process.env.SONAR_ENABLED?.trim()?.toLowerCase() ?? ''),
url: process.env.SONAR_URL || '',
this.gitlab = { token: process.env.SONAR_TOKEN || ''
URL : process.env.GITLAB_URL || '',
apiURL: process.env.GITLAB_API_URL || ''
}; };
this.login = { this.logsFolder = process.env.LOGS_FOLDER ?? '';
gitlab: {
client: {
id: process.env.LOGIN_GITLAB_CLIENT_ID || ''
},
url : {
redirect: process.env.LOGIN_GITLAB_URL_REDIRECT || '',
token : process.env.LOGIN_GITLAB_URL_TOKEN || ''
}
}
};
} }
} }
......
...@@ -8,26 +8,26 @@ import zlib from 'zlib'; ...@@ -8,26 +8,26 @@ import zlib from 'zlib';
class ArchiveHelper { class ArchiveHelper {
private async explore(absoluteBasePath: string, rootPath: string, pack: tar.Pack) { private async explore(absoluteBasePath: string, rootPath: string, pack: tar.Pack) {
for ( let file of await fs.promises.readdir(rootPath) ) { for ( const file of await fs.promises.readdir(rootPath) ) {
if ( file === 'output.tar' ) { let filename = file;
continue; if ( filename !== 'output.tar' ) {
} filename = path.join(rootPath, filename);
file = path.join(rootPath, file); const stat = await fs.promises.stat(filename);
const stat = await fs.promises.stat(file); if ( stat.isDirectory() ) {
if ( stat.isDirectory() ) { await this.explore(absoluteBasePath, filename, pack);
await this.explore(absoluteBasePath, file, pack); } else {
continue; const entry = pack.entry({
} name: filename.replace(absoluteBasePath, ''),
const entry = pack.entry({ size: stat.size
name: file.replace(absoluteBasePath, ''), }, err => {
size: stat.size if ( err ) {
}, (err) => { throw err;
if ( err ) { }
throw err; });
const readStream = fs.createReadStream(filename);
readStream.pipe(entry);
} }
}); }
const stream = fs.createReadStream(file);
stream.pipe(entry);
} }
} }
...@@ -55,7 +55,7 @@ class ArchiveHelper { ...@@ -55,7 +55,7 @@ class ArchiveHelper {
await this.compress(folderPath, tarDataStream); await this.compress(folderPath, tarDataStream);
data = await (new Promise((resolve) => { data = await (new Promise(resolve => {
tarDataStream.on('close', () => { tarDataStream.on('close', () => {
resolve(data); resolve(data);
}); });
......
import AssignmentFile from '../../types/Dojo/AssignmentFile'; import AssignmentFile from '../../types/Dojo/AssignmentFile.js';
import GitlabPipelineStatus from '../../types/Gitlab/GitlabPipelineStatus'; import DojoStatusCode from '../../types/Dojo/DojoStatusCode.js';
import DojoStatusCode from '../../types/Dojo/DojoStatusCode'; import Json5FileValidator from '../Json5FileValidator.js';
import GitlabPipeline from '../../types/Gitlab/GitlabPipeline'; import * as Gitlab from '@gitbeaker/rest';
import GitlabPipelineStatus from '../../types/Gitlab/GitlabPipelineStatus.js';
import SharedGitlabManager from '../../managers/SharedGitlabManager'; import SharedGitlabManager from '../../managers/SharedGitlabManager';
import Json5FileValidator from '../Json5FileValidator';
class SharedAssignmentHelper { class SharedAssignmentHelper {
private gitlabManager!: SharedGitlabManager;
init(gitlabManager: SharedGitlabManager) {
this.gitlabManager = gitlabManager;
}
validateDescriptionFile(filePathOrStr: string, isFile: boolean = true, version: number = 1): { content: AssignmentFile | undefined, isValid: boolean, error: string | null } { validateDescriptionFile(filePathOrStr: string, isFile: boolean = true, version: number = 1): { content: AssignmentFile | undefined, isValid: boolean, error: string | null } {
switch ( version ) { if ( version === 1 ) {
case 1: return Json5FileValidator.validateFile(AssignmentFile, filePathOrStr, isFile);
return Json5FileValidator.validateFile(AssignmentFile, filePathOrStr, isFile); } else {
default: return {
return { content: undefined,
content: undefined, isValid: false,
isValid: false, error : `Version ${ version } not supported`
error : `Version ${ version } not supported` };
};
} }
} }
async isPublishable(repositoryId: number): Promise<{ isPublishable: boolean, lastPipeline: GitlabPipeline | null, status?: { code: DojoStatusCode, message: string } }> { async isPublishable(repositoryId: number): Promise<{ isPublishable: boolean, lastPipeline: Gitlab.PipelineSchema | null, status?: { code: DojoStatusCode, message: string } }> {
const pipelines = await SharedGitlabManager.getRepositoryPipelines(repositoryId, 'main'); const pipelines = await this.gitlabManager.getRepositoryPipelines(repositoryId, 'main');
if ( pipelines.length > 0 ) { if ( pipelines.length > 0 ) {
const lastPipeline = pipelines[0]; const lastPipeline = pipelines[0];
if ( lastPipeline.status != GitlabPipelineStatus.SUCCESS ) { if ( lastPipeline.status !== GitlabPipelineStatus.SUCCESS.valueOf() ) {
return { return {
isPublishable: false, isPublishable: false,
lastPipeline : lastPipeline, lastPipeline : lastPipeline,
......
class SharedExerciseHelper {}
export default new SharedExerciseHelper();
\ No newline at end of file
class LazyVal<T> { class LazyVal<T> {
private val: T | undefined = undefined; private val: T | undefined = undefined;
private readonly valLoader: () => Promise<T> | T;
constructor(private valLoader: () => Promise<T> | T) {} constructor(valLoader: () => Promise<T> | T) {
this.valLoader = valLoader;
}
get value(): Promise<T> { get value(): Promise<T> {
return new Promise<T>((resolve) => { return new Promise<T>(resolve => {
if ( this.val === undefined ) { if ( this.val === undefined ) {
Promise.resolve(this.valLoader()).then((value: T) => { Promise.resolve(this.valLoader()).then((value: T) => {
this.val = value; this.val = value;
......
...@@ -14,8 +14,9 @@ class Toolbox { ...@@ -14,8 +14,9 @@ class Toolbox {
const files = await fs.readdir(dirPath); const files = await fs.readdir(dirPath);
await Promise.all(files.map(async file => { await Promise.all(files.map(async file => {
if ( (await fs.stat(dirPath + '/' + file)).isDirectory() ) { const filePath = path.join(dirPath, file);
arrayOfFiles = await this.getAllFiles(dirPath + '/' + file, arrayOfFiles); if ( (await fs.stat(filePath)).isDirectory() ) {
arrayOfFiles = await this.getAllFiles(filePath, arrayOfFiles);
} else { } else {
arrayOfFiles.push(path.join(dirPath, file)); arrayOfFiles.push(path.join(dirPath, file));
} }
...@@ -50,6 +51,10 @@ class Toolbox { ...@@ -50,6 +51,10 @@ class Toolbox {
public getKeysWithPrefix(obj: object, prefix: string): Array<string> { public getKeysWithPrefix(obj: object, prefix: string): Array<string> {
return Object.keys(obj).filter(key => key.startsWith(prefix)); return Object.keys(obj).filter(key => key.startsWith(prefix));
} }
public isString(value: unknown): value is string {
return typeof value === 'string' || value instanceof String;
}
} }
......
...@@ -64,9 +64,7 @@ function registerStringCapitalizeName() { ...@@ -64,9 +64,7 @@ function registerStringCapitalizeName() {
function registerStringConvertWithEnvVars() { function registerStringConvertWithEnvVars() {
String.prototype.convertWithEnvVars = function (this: string): string { String.prototype.convertWithEnvVars = function (this: string): string {
return this.replace(/\${?([a-zA-Z0-9_]+)}?/g, (_match: string, p1: string) => { return this.replace(/\${?([a-zA-Z0-9_]+)}?/g, (_match: string, p1: string) => process.env[p1] || '');
return process.env[p1] || '';
});
}; };
} }
......
...@@ -51,32 +51,36 @@ class RecursiveFilesStats { ...@@ -51,32 +51,36 @@ class RecursiveFilesStats {
return this.getFiles(`${ path.resolve(rootPath) }/`, rootPath, options, [], callback); return this.getFiles(`${ path.resolve(rootPath) }/`, rootPath, options, [], callback);
} }
private async getFiles(absoluteBasePath: string, rootPath: string, options: RecursiveReaddirFilesOptions = {}, files: IFileDirStat[] = [], callback?: Callback): Promise<IFileDirStat[]> { private async getFilesDirsStat(rootPath: string, options: RecursiveReaddirFilesOptions): Promise<Array<IFileDirStat>> {
const {
ignored, include, exclude, filter
} = options;
const filesData = await fs.promises.readdir(rootPath); const filesData = await fs.promises.readdir(rootPath);
const fileDir: IFileDirStat[] = filesData.map((file) => ({ return filesData.map(file => ({
name: file, path: path.join(rootPath, file) name: file,
})).filter((item) => { path: path.join(rootPath, file)
if ( include && include.test(item.path) ) { })).filter(item => {
if ( options.include && options.include.test(item.path) ) {
return true; return true;
} }
if ( exclude && exclude.test(item.path) ) { if ( options.exclude && options.exclude.test(item.path) ) {
return false; return false;
} }
if ( ignored ) { if ( options.ignored ) {
return !ignored.test(item.path); return !options.ignored.test(item.path);
} }
return true; return true;
}); });
}
private async getFiles(absoluteBasePath: string, rootPath: string, options: RecursiveReaddirFilesOptions = {}, files: IFileDirStat[] = [], callback?: Callback): Promise<IFileDirStat[]> {
const fileDir: Array<IFileDirStat> = await this.getFilesDirsStat(rootPath, options);
if ( callback ) { if ( callback ) {
fileDir.map(async (item: IFileDirStat) => { fileDir.forEach(item => {
const stat = await this.getStat(item.path, absoluteBasePath, options); this.getStat(item.path, absoluteBasePath, options).then(stat => {
if ( stat.isDirectory!() ) { if ( stat.isDirectory!() ) {
await this.getFiles(absoluteBasePath, item.path, options, [], callback); this.getFiles(absoluteBasePath, item.path, options, [], callback).then();
} }
callback(item.path, stat); callback(item.path, stat);
});
}); });
} else { } else {
await Promise.all(fileDir.map(async (item: IFileDirStat) => { await Promise.all(fileDir.map(async (item: IFileDirStat) => {
...@@ -89,9 +93,9 @@ class RecursiveFilesStats { ...@@ -89,9 +93,9 @@ class RecursiveFilesStats {
} }
})); }));
} }
return files.filter((item) => { return files.filter(item => {
if ( filter && typeof filter === 'function' ) { if ( options.filter && typeof options.filter === 'function' ) {
return filter(item); return options.filter(item);
} }
return true; return true;
}); });
...@@ -124,10 +128,7 @@ class RecursiveFilesStats { ...@@ -124,10 +128,7 @@ class RecursiveFilesStats {
delete stat.ctimeMs; delete stat.ctimeMs;
delete stat.birthtimeMs; delete stat.birthtimeMs;
delete stat.atime; delete stat.atime;
//delete stat.mtime;
delete stat.ctime; delete stat.ctime;
//delete stat.birthtime;
//delete stat.mode;
} }
return stat; return stat;
......
import winston from 'winston'; import winston from 'winston';
import SharedConfig from '../config/SharedConfig'; import SharedConfig from '../config/SharedConfig.js';
import * as Transport from 'winston-transport'; import * as Transport from 'winston-transport';
...@@ -23,7 +23,11 @@ winston.addColors(colors); ...@@ -23,7 +23,11 @@ winston.addColors(colors);
const format = winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format(info => ({ const format = winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format(info => ({
...info, ...info,
level: info.level.toUpperCase() 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 }` : '' } `)); }))(), 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 => {
const metadata = info.metadata ? `\n${ JSON.stringify(info.metadata) }` : '';
const stack = info.stack ? `\n${ JSON.stringify(info.stack, undefined, 4) }` : '';
return `[${ info.timestamp }] (${ process.pid }) ${ info.level } ${ info.message } ${ metadata } ${ stack } `;
}));
const commonTransportOptions = { const commonTransportOptions = {
handleRejections: true, handleRejections: true,
...@@ -54,11 +58,11 @@ if ( SharedConfig.production ) { ...@@ -54,11 +58,11 @@ if ( SharedConfig.production ) {
}) ]); }) ]);
} }
const logger = winston.createLogger({ const WinstonLogger = winston.createLogger({
levels, levels,
format, format,
transports, transports,
exitOnError: false exitOnError: false
}); });
export default logger; export default WinstonLogger;
import axios from 'axios'; import axios from 'axios';
import GitlabPipeline from '../types/Gitlab/GitlabPipeline'; import * as GitlabCore from '@gitbeaker/core';
import GitlabRoute from '../types/Gitlab/GitlabRoute'; import { GitbeakerRequestError } from '@gitbeaker/requester-utils';
import SharedConfig from '../config/SharedConfig'; import { Gitlab, PipelineSchema, ProjectSchema, SimpleUserSchema, UserSchema } from '@gitbeaker/rest';
import GitlabToken from '../types/Gitlab/GitlabToken'; import GitlabToken from '../types/Gitlab/GitlabToken.js';
class GitlabManager { class SharedGitlabManager {
private getApiUrl(route: GitlabRoute): string { protected api!: GitlabCore.Gitlab<false>;
return `${ SharedConfig.gitlab.apiURL }${ route }`; private readonly refreshTokenFunction?: () => Promise<string>;
setToken(token: string) {
this.api = new Gitlab(Object.assign({
host: this.gitlabUrl ?? ''
}, this.refreshTokenFunction ? { oauthToken: token } : { token: token }));
}
constructor(public gitlabUrl: string, token: string, public clientId?: string, public urlRedirect?: string, public urlToken?: string, refreshTokenFunction?: () => Promise<string>) {
this.refreshTokenFunction = refreshTokenFunction;
this.setToken(token);
}
protected async executeGitlabRequest<T>(request: () => Promise<T>, refreshTokenIfNeeded: boolean = true): Promise<T> {
try {
return await request();
} catch ( error ) {
if ( this.refreshTokenFunction && refreshTokenIfNeeded && error instanceof GitbeakerRequestError ) {
this.setToken(await this.refreshTokenFunction());
return this.executeGitlabRequest(request, false);
} else {
throw error;
}
}
} }
async getTokens(codeOrRefresh: string, isRefresh: boolean = false, clientSecret: string = ''): Promise<GitlabToken> { async getTokens(codeOrRefresh: string, isRefresh: boolean = false, clientSecret: string = ''): Promise<GitlabToken> {
const response = await axios.post<GitlabToken>(SharedConfig.login.gitlab.url.token, { if ( !this.urlToken ) {
client_id : SharedConfig.login.gitlab.client.id, throw new Error('Error when initializing GitlabManager');
}
const response = await axios.post<GitlabToken>(this.urlToken, {
client_id : this.clientId,
client_secret: clientSecret, client_secret: clientSecret,
grant_type : isRefresh ? 'refresh_token' : 'authorization_code', grant_type : isRefresh ? 'refresh_token' : 'authorization_code',
refresh_token: codeOrRefresh, refresh_token: codeOrRefresh,
code : codeOrRefresh, code : codeOrRefresh,
redirect_uri : SharedConfig.login.gitlab.url.redirect redirect_uri : this.urlRedirect
}); });
return response.data; return response.data;
} }
async getRepositoryPipelines(repoId: number, branch: string = 'main'): Promise<Array<GitlabPipeline>> { public async getUserById(id: number): Promise<UserSchema | undefined> {
const response = await axios.get<Array<GitlabPipeline>>(this.getApiUrl(GitlabRoute.REPOSITORY_PIPELINES).replace('{{id}}', String(repoId)), { try {
params: { return await this.executeGitlabRequest(async () => {
ref: branch const user = await this.api.Users.show(id);
}
});
return response.data; return user.id === id ? user : undefined;
});
} catch ( e ) {
return undefined;
}
}
public async getUserByUsername(username: string): Promise<SimpleUserSchema | undefined> {
try {
return await this.executeGitlabRequest(async () => {
const user = await this.api.Users.all({
username: username,
maxPages: 1,
perPage : 1
});
return user.length > 0 && user[0].username === username ? user[0] : undefined;
});
} catch ( e ) {
return undefined;
}
}
async getRepository(projectIdOrNamespace: string): Promise<ProjectSchema> {
return this.executeGitlabRequest(() => this.api.Projects.show(projectIdOrNamespace));
}
async getRepositoryPipelines(repoId: number, branch: string = 'main'): Promise<Array<PipelineSchema>> {
return this.executeGitlabRequest(() => this.api.Pipelines.all(repoId, { ref: branch }));
} }
} }
export default new GitlabManager(); export default SharedGitlabManager;
import axios from 'axios';
import https from 'https';
import SharedConfig from '../config/SharedConfig';
class SharedSonarManager {
// Use custom instance to allow self-signed certificates
private instance = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
async isSonarSupported(): Promise<boolean> {
if (!SharedConfig.sonar.enabled) {
return false;
}
try {
const sonar = await this.instance.get(SharedConfig.sonar.url);
return sonar.status == 200;
} catch ( error ) {
return false;
}
}
/**
* Map a language name to the equivalent language ID in Sonar
* Most language have the same name, so by default the same name is returned, even for languages that doesn't exist in sonar.
* @param language
*/
mapLanguage(language: string): string {
switch (language) {
case "csharp":
return "cs";
case "python":
return "py";
default:
return language;
}
}
}
export default new SharedSonarManager();
\ No newline at end of file
...@@ -11,6 +11,9 @@ enum ExerciseCheckerError { ...@@ -11,6 +11,9 @@ enum ExerciseCheckerError {
COMPOSE_FILE_VOLUME_MISSING = 209, COMPOSE_FILE_VOLUME_MISSING = 209,
DOCKERFILE_NOT_FOUND = 210, DOCKERFILE_NOT_FOUND = 210,
COMPOSE_RUN_SUCCESSFULLY = 211, // Yes, this is an error COMPOSE_RUN_SUCCESSFULLY = 211, // Yes, this is an error
ASSIGNMENT_MISSING = 212,
BUILD_LINE_MISSING = 213,
SONAR_ANALYSIS_FAILED = 214
} }
......
import ImmutableFileDescriptor from './ImmutableFileDescriptor'; import ImmutableFileDescriptor from './ImmutableFileDescriptor.js';
import { z } from 'zod'; import { z } from 'zod';
const AssignmentFile = z.object({ const AssignmentFile = z.object({
dojoAssignmentVersion: z.number(), dojoAssignmentVersion: z.number(),
version : z.number(), version : z.number(),
immutable : z.array(ImmutableFileDescriptor.transform(value => value as ImmutableFileDescriptor)), buildLine : z.string().optional(),
immutable : z.array(ImmutableFileDescriptor),
result : z.object({ result : z.object({
container: z.string(), container: z.string(),
volume : z.string().optional() volume : z.string().optional()
......
enum DojoStatusCode { enum DojoStatusCode {
LOGIN_FAILED = 1, LOGIN_FAILED = 10001,
REFRESH_TOKENS_FAILED = 2, REFRESH_TOKENS_FAILED = 10002,
CLIENT_NOT_SUPPORTED = 100, CLIENT_NOT_SUPPORTED = 10100,
CLIENT_VERSION_NOT_SUPPORTED = 110, CLIENT_VERSION_NOT_SUPPORTED = 10110,
ASSIGNMENT_PUBLISH_NO_PIPELINE = 200, ASSIGNMENT_PUBLISH_NO_PIPELINE = 10200,
ASSIGNMENT_PUBLISH_PIPELINE_FAILED = 201, ASSIGNMENT_PUBLISH_PIPELINE_FAILED = 10201,
ASSIGNMENT_CREATION_GITLAB_ERROR = 202, ASSIGNMENT_CREATION_GITLAB_ERROR = 10202,
ASSIGNMENT_CREATION_INTERNAL_ERROR = 203, ASSIGNMENT_CREATION_INTERNAL_ERROR = 10203,
ASSIGNMENT_EXERCISE_NOT_RELATED = 204, ASSIGNMENT_EXERCISE_NOT_RELATED = 10204,
ASSIGNMENT_NOT_PUBLISHED = 205, ASSIGNMENT_NOT_PUBLISHED = 10205,
EXERCISE_CORRECTION_NOT_EXIST = 206, EXERCISE_CORRECTION_NOT_EXIST = 10206,
EXERCISE_CORRECTION_ALREADY_EXIST = 207, EXERCISE_CORRECTION_ALREADY_EXIST = 10207,
EXERCISE_CREATION_GITLAB_ERROR = 302, ASSIGNMENT_NAME_CONFLICT = 10208,
EXERCISE_CREATION_INTERNAL_ERROR = 303, EXERCISE_CREATION_GITLAB_ERROR = 10302,
MAX_EXERCISE_PER_ASSIGNMENT_REACHED = 304 EXERCISE_CREATION_INTERNAL_ERROR = 10303,
MAX_EXERCISE_PER_ASSIGNMENT_REACHED = 10304,
GITLAB_TEMPLATE_NOT_FOUND = 10401,
GITLAB_TEMPLATE_ACCESS_UNAUTHORIZED = 10402,
TAG_ONLY_ADMIN_CREATION = 11101,
TAG_WITH_ACTIVE_LINK_DELETION = 11102,
TAG_PROPOSAL_ANSWER_NOT_PENDING = 11103,
ASSIGNMENT_CREATION_SONAR_ERROR = 208,
ASSIGNMENT_CREATION_SONAR_MEMBER = 209,
EXERCISE_CREATION_SONAR_ERROR = 305,
ASSIGNMENT_SONAR_GATE_NOT_FOUND = 401,
ASSIGNMENT_SONAR_PROFILE_NOT_FOUND = 402
} }
......
...@@ -6,7 +6,10 @@ enum ExerciseCheckerError { ...@@ -6,7 +6,10 @@ enum ExerciseCheckerError {
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 DOCKER_COMPOSE_REMOVE_DANGLING_ERROR = 208,
SONAR_DOCKER_ERROR = 209,
SONAR_BUILD_ERROR = 210,
SONAR_GATE_FAILED = 211
} }
......
import Icon from '../Icon'; import Icon from '../Icon.js';
import { z } from 'zod'; import { z } from 'zod';
...@@ -19,7 +19,7 @@ const ExerciseResultsFile = z.object({ ...@@ -19,7 +19,7 @@ const ExerciseResultsFile = z.object({
icon : z.enum(Object.keys(Icon) as [ firstKey: string, ...otherKeys: Array<string> ]).optional(), icon : z.enum(Object.keys(Icon) as [ firstKey: string, ...otherKeys: Array<string> ]).optional(),
itemsOrInformations: z.union([ z.array(z.string()), z.string() ]) itemsOrInformations: z.union([ z.array(z.string()), z.string() ])
})) }))
.optional() .optional()
}).strict().transform(value => { }).strict().transform(value => {
if ( value.successfulTests === undefined && value.successfulTestsList !== undefined ) { if ( value.successfulTests === undefined && value.successfulTestsList !== undefined ) {
value.successfulTests = value.successfulTestsList.length; value.successfulTests = value.successfulTestsList.length;
......
enum GitlabAccessLevel {
GUEST = 10,
REPORTER = 20,
DEVELOPER = 30,
MAINTAINER = 40,
OWNER = 50,
ADMIN = 60
}
export default GitlabAccessLevel;
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 GitlabFile {
file_name: string,
file_path: string,
size: number,
encoding: string,
content_sha256: string,
ref: string,
blob_id: string,
commit_id: string,
last_commit_id: string,
execute_filemode: boolean,
content: string,
}
export default GitlabFile;
\ No newline at end of file
interface GitlabGroup {
group_id: number,
group_name: string,
group_full_path: string,
group_access_level: number,
expires_at: string
}
export default GitlabGroup;
\ No newline at end of file