diff --git a/ExpressAPI/src/config/Config.ts b/ExpressAPI/src/config/Config.ts index a768fded7ca22aae497741693e813a785157a61f..2eaa5ec692b2c84a82c917564ac27634c3433639 100644 --- a/ExpressAPI/src/config/Config.ts +++ b/ExpressAPI/src/config/Config.ts @@ -1,8 +1,8 @@ -import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; import path from 'path'; import fs from 'fs'; import { Exercise } from '../types/DatabaseTypes'; import JSON5 from 'json5'; +import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; type ConfigGitlabBadge = { @@ -52,13 +52,13 @@ class Config { public readonly assignment: { default: { - description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: string; wikiEnabled: boolean; template: string + description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: GitlabVisibility; wikiEnabled: boolean; template: string }; baseFiles: Array<string>; filename: string }; public readonly exercise: { maxSameName: number; maxPerAssignment: number; resultsFolder: string, pipelineResultsFolder: string; default: { - description: string; visibility: string; + description: string; visibility: GitlabVisibility; }; }; @@ -116,7 +116,7 @@ class Config { description : process.env.ASSIGNMENT_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', initReadme : process.env.ASSIGNMENT_DEFAULT_INIT_README?.toBoolean() ?? false, sharedRunnersEnabled: process.env.ASSIGNMENT_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true, - visibility : process.env.ASSIGNMENT_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE, + visibility : process.env.ASSIGNMENT_DEFAULT_VISIBILITY as GitlabVisibility || 'private', wikiEnabled : process.env.ASSIGNMENT_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false, template : process.env.ASSIGNMENT_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? '' }, @@ -131,7 +131,7 @@ class Config { pipelineResultsFolder: process.env.EXERCISE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercise creation and muste be interpreted at exercise runtime default : { description: process.env.EXERCISE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '', - visibility : process.env.EXERCISE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE + visibility : process.env.EXERCISE_DEFAULT_VISIBILITY as GitlabVisibility || 'private' } }; } diff --git a/ExpressAPI/src/helpers/DojoCliVersionHelper.ts b/ExpressAPI/src/helpers/DojoCliVersionHelper.ts index 962bf1c1d41ed2d9da7c8dfbac9fb9c51be8396e..4fd8d2505f55d38cd7f0925c94a6b23eb154d9aa 100644 --- a/ExpressAPI/src/helpers/DojoCliVersionHelper.ts +++ b/ExpressAPI/src/helpers/DojoCliVersionHelper.ts @@ -1,6 +1,6 @@ import Config from '../config/Config'; -import GitlabRelease from '../shared/types/Gitlab/GitlabRelease'; import GitlabManager from '../managers/GitlabManager'; +import * as Gitlab from '@gitbeaker/rest'; class DojoCliVersionHelper { @@ -8,7 +8,7 @@ class DojoCliVersionHelper { private latestVersion: string | undefined; private async updateVersion(): Promise<void> { - const releases: Array<GitlabRelease> = await GitlabManager.getRepositoryReleases(Config.dojoCLI.repositoryId); + const releases: Array<Gitlab.ReleaseSchema> = await GitlabManager.getRepositoryReleases(Config.dojoCLI.repositoryId); for ( const release of releases ) { if ( !isNaN(+release.tag_name.replace('.', '')) ) { this.latestVersion = release.tag_name; diff --git a/ExpressAPI/src/helpers/GlobalHelper.ts b/ExpressAPI/src/helpers/GlobalHelper.ts index e7e6e344f29769d48a8517d77ec7eb0b7493aa2d..9f6f131a11c0291555018ea8cfad65e35cf6e44b 100644 --- a/ExpressAPI/src/helpers/GlobalHelper.ts +++ b/ExpressAPI/src/helpers/GlobalHelper.ts @@ -1,14 +1,18 @@ -import express from 'express'; -import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; -import logger from '../shared/logging/WinstonLogger'; -import GitlabManager from '../managers/GitlabManager'; -import { AxiosError } from 'axios'; -import { StatusCodes } from 'http-status-codes'; -import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; +import express from 'express'; +import logger from '../shared/logging/WinstonLogger'; +import GitlabManager from '../managers/GitlabManager'; +import { AxiosError } from 'axios'; +import { StatusCodes } from 'http-status-codes'; +import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; +import SharedGitlabManager from '../shared/managers/SharedGitlabManager'; +import Config from '../config/Config'; +import * as Gitlab from '@gitbeaker/rest'; class GlobalHelper { - async repositoryCreationError(message: string, error: unknown, req: express.Request, res: express.Response, gitlabError: DojoStatusCode, internalError: DojoStatusCode, repositoryToRemove?: GitlabRepository): Promise<void> { + readonly sharedGitlabManager = new SharedGitlabManager(Config.gitlab.account.token); + + async repositoryCreationError(message: string, error: unknown, req: express.Request, res: express.Response, gitlabError: DojoStatusCode, internalError: DojoStatusCode, repositoryToRemove?: Gitlab.ProjectSchema): Promise<void> { logger.error(message); logger.error(error); diff --git a/ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts b/ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts index 8a8640ea20e19fa9df3692a920a7cb05f0a4fb6a..6630bca34634f7e35b84af873e5b1cfecc94fcbd 100644 --- a/ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts +++ b/ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts @@ -1,7 +1,7 @@ import { Prisma, UserRole } from '@prisma/client'; import LazyVal from '../../../shared/helpers/LazyVal'; -import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; import GitlabManager from '../../../managers/GitlabManager'; +import * as Gitlab from '@gitbeaker/rest'; export default Prisma.defineExtension(client => { @@ -26,7 +26,7 @@ export default Prisma.defineExtension(client => { }, gitlabProfile : { compute(user) { - return new LazyVal<GitlabUser | undefined>(() => GitlabManager.getUserById(user.id)); + return new LazyVal<Gitlab.UserSchema | undefined>(() => GitlabManager.getUserById(user.id)); } } } diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts index 528d24dd41e5da167008484c70f0802eb05b0353..3bf07a4f562b6f72343cdffbc401df07221a8784 100644 --- a/ExpressAPI/src/managers/GitlabManager.ts +++ b/ExpressAPI/src/managers/GitlabManager.ts @@ -1,51 +1,38 @@ +import Config from '../config/Config'; +import { StatusCodes } from 'http-status-codes'; +import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; +import express from 'express'; +import SharedConfig from '../shared/config/SharedConfig'; +import { CommitSchema, ExpandedUserSchema, Gitlab, MemberSchema, ProjectBadgeSchema, ProjectSchema, ReleaseSchema, RepositoryFileExpandedSchema, RepositoryFileSchema, RepositoryTreeSchema, UserSchema } from '@gitbeaker/rest'; +import logger from '../shared/logging/WinstonLogger'; +import { AccessLevel, EditProjectOptions, ProjectVariableSchema, ProtectedBranchAccessLevel, ProtectedBranchSchema } from '@gitbeaker/core'; import axios from 'axios'; -import Config from '../config/Config'; -import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; -import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel'; -import GitlabMember from '../shared/types/Gitlab/GitlabMember'; -import { StatusCodes } from 'http-status-codes'; -import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; -import GitlabUser from '../shared/types/Gitlab/GitlabUser'; -import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile'; import parseLinkHeader from 'parse-link-header'; -import GitlabFile from '../shared/types/Gitlab/GitlabFile'; -import express from 'express'; -import GitlabRoute from '../shared/types/Gitlab/GitlabRoute'; -import SharedConfig from '../shared/config/SharedConfig'; -import GitlabProfile from '../shared/types/Gitlab/GitlabProfile'; -import GitlabRelease from '../shared/types/Gitlab/GitlabRelease'; -import { CommitSchema, Gitlab } from '@gitbeaker/rest'; -import logger from '../shared/logging/WinstonLogger'; import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; class GitlabManager { - readonly api = new Gitlab({ - host : SharedConfig.gitlab.URL, - token: Config.gitlab.account.token - }); + private readonly api = new Gitlab({ + host : SharedConfig.gitlab.URL, + token: Config.gitlab.account.token + }); - private getApiUrl(route: GitlabRoute): string { - return `${ SharedConfig.gitlab.apiURL }${ route }`; - } - - public async getUserProfile(token: string): Promise<GitlabProfile | undefined> { + public async getUserProfile(token: string): Promise<ExpandedUserSchema | undefined> { try { - return (await axios.get<GitlabProfile>(this.getApiUrl(GitlabRoute.PROFILE_GET), { - headers: { - DojoOverrideAuthorization: true, - DojoAuthorizationHeader : 'Authorization', - DojoAuthorizationValue : `Bearer ${ token }` - } - })).data; + const profileApi = new Gitlab({ + host : SharedConfig.gitlab.URL, + token: token + }); + + return await profileApi.Users.showCurrentUser(); } catch ( e ) { return undefined; } } - public async getUserById(id: number): Promise<GitlabUser | undefined> { + public async getUserById(id: number): Promise<UserSchema | undefined> { try { - const user = (await axios.get<GitlabUser>(`${ this.getApiUrl(GitlabRoute.USERS_GET) }/${ String(id) }`)).data; + const user = await this.api.Users.show(id); return user.id === id ? user : undefined; } catch ( e ) { @@ -53,34 +40,31 @@ class GitlabManager { } } - public async getUserByUsername(username: string): Promise<GitlabUser | undefined> { + public async getUserByUsername(username: string): Promise<UserSchema | undefined> { try { - const params: Record<string, string> = {}; - params['search'] = username; - const user = (await axios.get<Array<GitlabUser>>(this.getApiUrl(GitlabRoute.USERS_GET), { params: params })).data[0]; + const user = await this.api.Users.all({ + username: username, + maxPages: 1, + perPage : 1 + }); + - return user.username === username ? user : undefined; + return user.length > 0 && user[0].username === username ? user[0] : undefined; } catch ( e ) { return undefined; } } - async getRepository(projectIdOrNamespace: string): Promise<GitlabRepository> { - const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(projectIdOrNamespace))); - - return response.data; + async getRepository(projectIdOrNamespace: string): Promise<ProjectSchema> { + return await this.api.Projects.show(projectIdOrNamespace); } - async getRepositoryMembers(idOrNamespace: string): Promise<Array<GitlabMember>> { - const response = await axios.get<Array<GitlabMember>>(this.getApiUrl(GitlabRoute.REPOSITORY_MEMBERS_GET).replace('{{id}}', encodeURIComponent(idOrNamespace))); - - return response.data; + async getRepositoryMembers(idOrNamespace: string): Promise<Array<MemberSchema>> { + return await this.api.ProjectMembers.all(idOrNamespace, { includeInherited: true }); } - async getRepositoryReleases(repoId: number): Promise<Array<GitlabRelease>> { - const response = await axios.get<Array<GitlabRelease>>(this.getApiUrl(GitlabRoute.REPOSITORY_RELEASES_GET).replace('{{id}}', String(repoId))); - - return response.data; + async getRepositoryReleases(repoId: number): Promise<Array<ReleaseSchema>> { + return await this.api.ProjectReleases.all(repoId); } async getRepositoryLastCommit(repoId: number, branch: string = 'main'): Promise<CommitSchema | undefined> { @@ -98,86 +82,66 @@ class GitlabManager { } } - async createRepository(name: string, description: string, visibility: string, initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, importUrl: string): Promise<GitlabRepository> { - const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_CREATE), { - name : name, - description : description, - import_url : importUrl, - initialize_with_readme: initializeWithReadme, - namespace_id : namespace, - shared_runners_enabled: sharedRunnersEnabled, - visibility : visibility, - wiki_enabled : wikiEnabled - }); - - return response.data; + async createRepository(name: string, description: string, visibility: 'public' | 'internal' | 'private', initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, import_url: string): Promise<ProjectSchema> { + return await this.api.Projects.create({ + name : name, + description : description, + importUrl : import_url, + initializeWithReadme: initializeWithReadme, + namespaceId : namespace, + sharedRunnersEnabled: sharedRunnersEnabled, + visibility : visibility, + wikiAccessLevel : wikiEnabled ? 'enabled' : 'disabled' + }); } - deleteRepository(repoId: number): Promise<void> { - return axios.delete(this.getApiUrl(GitlabRoute.REPOSITORY_DELETE).replace('{{id}}', String(repoId))); + async deleteRepository(repoId: number): Promise<void> { + return await this.api.Projects.remove(repoId); } - async forkRepository(forkId: number, name: string, path: string, description: string, visibility: string, namespace: number): Promise<GitlabRepository> { - const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_FORK).replace('{{id}}', String(forkId)), { - name : name, - path : path, - description : description, - namespace_id: namespace, - visibility : visibility + async forkRepository(forkId: number, name: string, path: string, description: string, visibility: 'public' | 'internal' | 'private', namespace: number): Promise<ProjectSchema> { + return await this.api.Projects.fork(forkId, { + name : name, + path : path, + description: description, + namespaceId: namespace, + visibility : visibility }); - - return response.data; } - async editRepository(repoId: number, newAttributes: Partial<GitlabRepository>): Promise<GitlabRepository> { - const response = await axios.put<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_EDIT).replace('{{id}}', String(repoId)), newAttributes); - - return response.data; + async editRepository(repoId: number, newAttributes: EditProjectOptions): Promise<ProjectSchema> { + return await this.api.Projects.edit(repoId, newAttributes); } - changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<GitlabRepository> { - return this.editRepository(repoId, { visibility: visibility.toString() }); + async changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<ProjectSchema> { + return await this.editRepository(repoId, { visibility: visibility }); } - async addRepositoryMember(repoId: number, userId: number, accessLevel: GitlabAccessLevel): Promise<GitlabMember> { - const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_MEMBER_ADD).replace('{{id}}', String(repoId)), { - user_id : userId, - access_level: accessLevel - }); - - return response.data; + async addRepositoryMember(repoId: number, userId: number, accessLevel: Exclude<AccessLevel, AccessLevel.ADMIN>): Promise<MemberSchema> { + return await this.api.ProjectMembers.add(repoId, userId, accessLevel); } - async addRepositoryVariable(repoId: number, key: string, value: string, isProtected: boolean, isMasked: boolean): Promise<GitlabMember> { - const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_VARIABLES_ADD).replace('{{id}}', String(repoId)), { - key : key, - variable_type: 'env_var', - value : value, - protected : isProtected, - masked : isMasked + async addRepositoryVariable(repoId: number, key: string, value: string, isProtected: boolean, isMasked: boolean): Promise<ProjectVariableSchema> { + return await this.api.ProjectVariables.create(repoId, key, value, { + variableType: 'env_var', + protected : isProtected, + masked : isMasked }); - - return response.data; } - async addRepositoryBadge(repoId: number, linkUrl: string, imageUrl: string, name: string): Promise<GitlabMember> { - const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_BADGES_ADD).replace('{{id}}', String(repoId)), { - link_url : linkUrl, - image_url: imageUrl, - name : name + async addRepositoryBadge(repoId: number, linkUrl: string, imageUrl: string, name: string): Promise<ProjectBadgeSchema> { + return await this.api.ProjectBadges.add(repoId, linkUrl, imageUrl, { + name: name }); - - return response.data; } async checkTemplateAccess(projectIdOrNamespace: string, req: express.Request, res?: express.Response): Promise<boolean> { // Get the Gitlab project and check if it have public or internal visibility try { - const project: GitlabRepository = await this.getRepository(projectIdOrNamespace); + const project: ProjectSchema = await this.getRepository(projectIdOrNamespace); - if ( [ GitlabVisibility.PUBLIC.valueOf(), GitlabVisibility.INTERNAL.valueOf() ].includes(project.visibility) ) { - req.session.sendResponse(res, StatusCodes.OK); - return true; + if ( [ 'public', 'internal' ].includes(project.visibility) ) { + return StatusCodes.OK; } } catch ( e ) { req.session.sendResponse(res, StatusCodes.NOT_FOUND, undefined, 'Template not found', DojoStatusCode.GITLAB_TEMPLATE_NOT_FOUND); @@ -191,7 +155,7 @@ class GitlabManager { dojo: false }; members.forEach(member => { - if ( member.access_level >= GitlabAccessLevel.REPORTER ) { + if ( member.access_level >= AccessLevel.REPORTER ) { if ( member.id === req.session.profile.id ) { isUsersAtLeastReporter.user = true; } else if ( member.id === Config.gitlab.account.id ) { @@ -209,91 +173,49 @@ class GitlabManager { } } - async protectBranch(repoId: number, branchName: string, allowForcePush: boolean, allowedToMerge: GitlabAccessLevel, allowedToPush: GitlabAccessLevel, allowedToUnprotect: GitlabAccessLevel): Promise<GitlabMember> { - const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_BRANCHES_PROTECT).replace('{{id}}', String(repoId)), { - name : branchName, - allow_force_push : allowForcePush, - merge_access_level : allowedToMerge.valueOf(), - push_access_level : allowedToPush.valueOf(), - unprotect_access_level: allowedToUnprotect.valueOf() + async protectBranch(repoId: number, branchName: string, allowForcePush: boolean, allowedToMerge: ProtectedBranchAccessLevel, allowedToPush: ProtectedBranchAccessLevel, allowedToUnprotect: ProtectedBranchAccessLevel): Promise<ProtectedBranchSchema> { + return await this.api.ProtectedBranches.protect(repoId, branchName, { + allowForcePush : allowForcePush, + mergeAccessLevel : allowedToMerge, + pushAccessLevel : allowedToPush, + unprotectAccessLevel: allowedToUnprotect }); - - return response.data; } - async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<GitlabTreeFile>> { - const address: string | undefined = this.getApiUrl(GitlabRoute.REPOSITORY_TREE).replace('{{id}}', String(repoId)); - let params: Partial<parseLinkHeader.Link | { recursive: boolean, per_page: number }> | undefined = { - pagination: 'keyset', - recursive : recursive, - per_page : 100, - ref : branch - }; - - const results: Array<GitlabTreeFile> = []; - - while ( params !== undefined ) { - const response = await axios.get<Array<GitlabTreeFile>>(address, { - params: params - }); - - results.push(...response.data); - - if ( 'link' in response.headers ) { - params = parseLinkHeader(response.headers['link'])?.next ?? undefined; - } else { - params = undefined; - } - } - - return results; - } - - private getRepositoryFileUrl(repoId: number, filePath: string): string { - return this.getApiUrl(GitlabRoute.REPOSITORY_FILE).replace('{{id}}', String(repoId)).replace('{{filePath}}', encodeURIComponent(filePath)); - } - - async getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<GitlabFile> { - const response = await axios.get<GitlabFile>(this.getRepositoryFileUrl(repoId, filePath), { - params: { - ref: branch - } + async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<RepositoryTreeSchema>> { + return await this.api.Repositories.allRepositoryTrees(repoId, { + recursive: recursive, + ref : branch }); + } - return response.data; + async getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<RepositoryFileExpandedSchema> { + return await this.api.RepositoryFiles.show(repoId, filePath, branch); } - private async createUpdateFile(create: boolean, repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) { - const axiosFunction = create ? axios.post : axios.put; + private async createUpdateFile(create: boolean, repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> { + const gitFunction = create ? this.api.RepositoryFiles.create : this.api.RepositoryFiles.edit; - await axiosFunction(this.getRepositoryFileUrl(repoId, filePath), { - encoding : 'base64', - branch : branch, - commit_message: commitMessage, - content : fileBase64, - author_name : authorName, - author_email : authorMail + return await gitFunction(repoId, filePath, branch, fileBase64, commitMessage, { + encoding : 'base64', + authorName : authorName, + authorEmail: authorMail }); } - async createFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) { + async createFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> { return this.createUpdateFile(true, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail); } - async updateFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) { + async updateFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> { return this.createUpdateFile(false, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail); } - async deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) { - await axios.delete(this.getRepositoryFileUrl(repoId, filePath), { - data: { - branch : branch, - commit_message: commitMessage, - author_name : authorName, - author_email : authorMail - } + async deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<void> { + await this.api.RepositoryFiles.remove(repoId, filePath, branch, commitMessage, { + authorName : authorName, + authorEmail: authorMail }); - } } diff --git a/ExpressAPI/src/managers/HttpManager.ts b/ExpressAPI/src/managers/HttpManager.ts index e67828d06b4b4289b74d241e1c0aa60b25d280be..408adc592d9119e511e1eff457cfde149937c68f 100644 --- a/ExpressAPI/src/managers/HttpManager.ts +++ b/ExpressAPI/src/managers/HttpManager.ts @@ -1,8 +1,6 @@ import axios, { AxiosError, AxiosRequestHeaders } from 'axios'; -import Config from '../config/Config'; import FormData from 'form-data'; import logger from '../shared/logging/WinstonLogger'; -import SharedConfig from '../shared/config/SharedConfig'; class HttpManager { @@ -16,19 +14,6 @@ class HttpManager { if ( config.data instanceof FormData ) { config.headers = { ...config.headers, ...(config.data as FormData).getHeaders() } as AxiosRequestHeaders; } - - if ( config.url && config.url.indexOf(SharedConfig.gitlab.apiURL) !== -1 && !config.headers.DojoOverrideAuthorization ) { - config.headers['PRIVATE-TOKEN'] = Config.gitlab.account.token; - } - - if ( config.headers.DojoOverrideAuthorization && 'DojoAuthorizationHeader' in config.headers && 'DojoAuthorizationValue' in config.headers ) { - config.headers[config.headers.DojoAuthorizationHeader] = config.headers.DojoAuthorizationValue; - - delete config.headers.DojoOverrideAuthorization; - delete config.headers.DojoAuthorizationHeader; - delete config.headers.DojoAuthorizationValue; - } - return config; }); } diff --git a/ExpressAPI/src/managers/UserManager.ts b/ExpressAPI/src/managers/UserManager.ts index 686ee267551004d22b1745e93f343c30c21aec02..6008cf5d7d301e02fbd83919f076d29193cf5bac 100644 --- a/ExpressAPI/src/managers/UserManager.ts +++ b/ExpressAPI/src/managers/UserManager.ts @@ -1,8 +1,7 @@ -import GitlabUser from '../shared/types/Gitlab/GitlabUser'; -import { Prisma } from '@prisma/client'; -import db from '../helpers/DatabaseHelper'; -import GitlabProfile from '../shared/types/Gitlab/GitlabProfile'; -import { User } from '../types/DatabaseTypes'; +import { Prisma } from '@prisma/client'; +import db from '../helpers/DatabaseHelper'; +import { User } from '../types/DatabaseTypes'; +import * as Gitlab from '@gitbeaker/rest'; class UserManager { @@ -24,7 +23,7 @@ class UserManager { }) as unknown as User ?? undefined; } - async getUpdateFromGitlabProfile(gitlabProfile: GitlabProfile): Promise<User> { + async getUpdateFromGitlabProfile(gitlabProfile: Gitlab.ExpandedUserSchema): Promise<User> { await db.user.upsert({ where : { id: gitlabProfile.id @@ -46,7 +45,7 @@ class UserManager { return (await this.getById(gitlabProfile.id))!; } - async getFromGitlabUser(gitlabUser: GitlabUser, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<User | number | undefined> { + async getFromGitlabUser(gitlabUser: Gitlab.UserSchema, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<User | number | undefined> { let user = await this.getById(gitlabUser.id, include) ?? gitlabUser.id; if ( typeof user === 'number' && createIfNotExist ) { @@ -61,7 +60,7 @@ class UserManager { return user; } - async getFromGitlabUsers(gitlabUsers: Array<GitlabUser>, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<Array<User | number | undefined>> { + async getFromGitlabUsers(gitlabUsers: Array<Gitlab.UserSchema>, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<Array<User | number | undefined>> { return Promise.all(gitlabUsers.map(gitlabUser => this.getFromGitlabUser(gitlabUser, createIfNotExist, include))); } } diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index 9f46f4973a57441ea2b043c117e343e77ccbed5a..f3af21116e817d9cf3fa8b41da15294e11e82eff 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -6,12 +6,8 @@ import RoutesManager from '../express/RoutesManager'; import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware'; import SecurityMiddleware from '../middlewares/SecurityMiddleware'; import SecurityCheckType from '../types/SecurityCheckType'; -import GitlabUser from '../shared/types/Gitlab/GitlabUser'; import GitlabManager from '../managers/GitlabManager'; import Config from '../config/Config'; -import GitlabMember from '../shared/types/Gitlab/GitlabMember'; -import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel'; -import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; import { AxiosError, HttpStatusCode } from 'axios'; import logger from '../shared/logging/WinstonLogger'; import DojoValidators from '../helpers/DojoValidators'; @@ -19,13 +15,13 @@ import { Prisma } from '@prisma/client'; import db from '../helpers/DatabaseHelper'; import { Assignment } from '../types/DatabaseTypes'; import AssignmentManager from '../managers/AssignmentManager'; -import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility'; import fs from 'fs'; import path from 'path'; import SharedAssignmentHelper from '../shared/helpers/Dojo/SharedAssignmentHelper'; import GlobalHelper from '../helpers/GlobalHelper'; import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; import DojoModelsHelper from '../helpers/DojoModelsHelper'; +import * as Gitlab from '@gitbeaker/rest'; class AssignmentRoutes implements RoutesManager { @@ -84,13 +80,13 @@ class AssignmentRoutes implements RoutesManager { private async createAssignment(req: express.Request, res: express.Response) { const params: { - name: string, members: Array<GitlabUser>, template: string + name: string, members: Array<Gitlab.UserSchema>, template: string } = req.body; params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ]; params.members = params.members.removeObjectDuplicates(gitlabUser => gitlabUser.id); - let repository: GitlabRepository; + let repository: Gitlab.ProjectSchema; try { repository = await GitlabManager.createRepository(params.name, Config.assignment.default.description.replace('{{ASSIGNMENT_NAME}}', params.name), Config.assignment.default.visibility, Config.assignment.default.initReadme, Config.gitlab.group.assignments, Config.assignment.default.sharedRunnersEnabled, Config.assignment.default.wikiEnabled, params.template); } catch ( error ) { @@ -111,7 +107,7 @@ class AssignmentRoutes implements RoutesManager { await new Promise(resolve => setTimeout(resolve, Config.gitlab.repository.timeoutAfterCreation)); try { - await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER); + await GitlabManager.protectBranch(repository.id, '*', true, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.ADMIN); await GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status'); } catch ( error ) { @@ -129,9 +125,9 @@ class AssignmentRoutes implements RoutesManager { } try { - await Promise.all(params.members.map(member => member.id).map(async (memberId: number): Promise<GitlabMember | false> => { + await Promise.all(params.members.map(member => member.id).map(async (memberId: number): Promise<Gitlab.MemberSchema | false> => { try { - return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER); + return await GitlabManager.addRepositoryMember(repository.id, memberId, Gitlab.AccessLevel.DEVELOPER); } catch ( error ) { logger.error('Add member error'); logger.error(error); @@ -180,7 +176,7 @@ class AssignmentRoutes implements RoutesManager { } try { - await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE); + await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? 'internal' : 'private'); await db.assignment.update({ where: { diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts index d5cb5b68c164dec82a2bf3c4c4750c0477f73225..72a0276668b393ac8f8a4940182fdc4401e522a8 100644 --- a/ExpressAPI/src/routes/ExerciseRoutes.ts +++ b/ExpressAPI/src/routes/ExerciseRoutes.ts @@ -5,23 +5,16 @@ import { StatusCodes } from 'http-status-codes'; import RoutesManager from '../express/RoutesManager'; import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware'; import SecurityMiddleware from '../middlewares/SecurityMiddleware'; -import GitlabUser from '../shared/types/Gitlab/GitlabUser'; import GitlabManager from '../managers/GitlabManager'; import Config from '../config/Config'; -import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; import { AxiosError } from 'axios'; import logger from '../shared/logging/WinstonLogger'; import DojoValidators from '../helpers/DojoValidators'; import { v4 as uuidv4 } from 'uuid'; -import GitlabMember from '../shared/types/Gitlab/GitlabMember'; -import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel'; import { Prisma } from '@prisma/client'; import { Assignment, Exercise } from '../types/DatabaseTypes'; import db from '../helpers/DatabaseHelper'; import SecurityCheckType from '../types/SecurityCheckType'; -import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile'; -import GitlabFile from '../shared/types/Gitlab/GitlabFile'; -import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType'; import JSON5 from 'json5'; import fs from 'fs'; import path from 'path'; @@ -31,6 +24,8 @@ import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; import GlobalHelper from '../helpers/GlobalHelper'; import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats'; import ExerciseManager from '../managers/ExerciseManager'; +import * as Gitlab from '@gitbeaker/rest'; +import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType'; class ExerciseRoutes implements RoutesManager { @@ -77,19 +72,19 @@ class ExerciseRoutes implements RoutesManager { backend.post('/exercises/:exerciseIdOrUrl/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this)); } - private getExerciseName(assignment: Assignment, members: Array<GitlabUser>, suffix: number): string { + private getExerciseName(assignment: Assignment, members: Array<Gitlab.UserSchema>, suffix: number): string { const memberNames: string = members.map(member => member.username).sort((a, b) => a.localeCompare(b)).join(' + '); const suffixString: string = suffix > 0 ? ` - ${ suffix }` : ''; return `DojoEx - ${ assignment.name } - ${ memberNames }${ suffixString }`; } private getExercisePath(assignment: Assignment, exerciseId: string): string { - return `dojo-ex_${ (assignment.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciseId }`; + return `dojo-ex_${ (assignment.gitlabLastInfo as unknown as Gitlab.ProjectSchema).path }_${ exerciseId }`; } - private async checkExerciseLimit(assignment: Assignment, members: Array<GitlabUser>): Promise<Array<GitlabUser>> { - const exercises: Array<Exercise> = await ExerciseManager.getFromAssignment(assignment.name, { members: true }); - const reachedLimitUsers: Array<GitlabUser> = []; + private async checkExerciseLimit(assignment: Assignment, members: Array<Gitlab.UserSchema>): Promise<Array<Gitlab.UserSchema>> { + const exercises: Array<Exercise> | undefined = await ExerciseManager.getFromAssignment(assignment.name, { members: true }); + const reachedLimitUsers: Array<Gitlab.UserSchema> = []; if ( exercises.length > 0 ) { for ( const member of members ) { const exerciseCount: number = exercises.filter(exercise => exercise.members.findIndex(exerciseMember => exerciseMember.id === member.id) !== -1).length; @@ -102,13 +97,13 @@ class ExerciseRoutes implements RoutesManager { return reachedLimitUsers; } - private async createExerciseRepository(assignment: Assignment, members: Array<GitlabUser>, exerciseId: string, req: express.Request, res: express.Response): Promise<GitlabRepository | undefined> { - let repository!: GitlabRepository; + private async createExerciseRepository(assignment: Assignment, members: Array<Gitlab.UserSchema>, exerciseId: string, req: express.Request, res: express.Response): Promise<Gitlab.ProjectSchema | undefined> { + let repository!: Gitlab.ProjectSchema; let suffix: number = 0; do { try { - repository = await GitlabManager.forkRepository((assignment.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciseName(assignment, members, suffix), this.getExercisePath(req.boundParams.assignment!, exerciseId), Config.exercise.default.description.replace('{{ASSIGNMENT_NAME}}', assignment.name), Config.exercise.default.visibility, Config.gitlab.group.exercises); + repository = await GitlabManager.forkRepository((assignment.gitlabCreationInfo as unknown as Gitlab.ProjectSchema).id, this.getExerciseName(assignment, params.members, suffix), this.getExercisePath(req.boundParams.assignment!, exerciseId), Config.exercise.default.description.replace('{{ASSIGNMENT_NAME}}', assignment.name), Config.exercise.default.visibility, Config.gitlab.group.exercises); break; } catch ( error ) { logger.error('Repo creation error'); @@ -161,7 +156,7 @@ class ExerciseRoutes implements RoutesManager { await new Promise(resolve => setTimeout(resolve, Config.gitlab.repository.timeoutAfterCreation)); try { - await GitlabManager.protectBranch(repository.id, '*', false, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER); + await GitlabManager.protectBranch(repository.id, '*', false, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.ADMIN); await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_EXERCISE_ID', exerciseId, false, true); await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_SECRET', secret, false, true); @@ -181,9 +176,9 @@ class ExerciseRoutes implements RoutesManager { } try { - await Promise.all([ ...new Set([ ...assignment.staff.map(user => user.id), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<GitlabMember | false> => { + await Promise.all([ ...new Set([ ...assignment.staff.map(user => user.id), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<Gitlab.MemberSchema | false> => { try { - return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER); + return await GitlabManager.addRepositoryMember(repository.id, memberId, Gitlab.AccessLevel.DEVELOPER); } catch ( error ) { logger.error('Add member error'); logger.error(error); @@ -227,10 +222,10 @@ class ExerciseRoutes implements RoutesManager { } private async getAssignment(req: express.Request, res: express.Response) { - const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId); + const repoTree: Array<Gitlab.RepositoryTreeSchema> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId); - let assignmentHjsonFile!: GitlabFile; - const immutableFiles: Array<GitlabFile> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => { + let assignmentHjsonFile!: Gitlab.RepositoryFileExpandedSchema; + const immutableFiles: Array<Gitlab.RepositoryFileExpandedSchema> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => { const file = await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, baseFile); if ( baseFile === Config.assignment.filename ) { diff --git a/ExpressAPI/src/routes/SessionRoutes.ts b/ExpressAPI/src/routes/SessionRoutes.ts index 60b630d9fcf37254f4f068acc0272265ca149d1c..e1b1a24b2f13b7de5d2aba56c7a7426395fe92a4 100644 --- a/ExpressAPI/src/routes/SessionRoutes.ts +++ b/ExpressAPI/src/routes/SessionRoutes.ts @@ -8,8 +8,8 @@ import SecurityMiddleware from '../middlewares/SecurityMiddleware'; import GitlabManager from '../managers/GitlabManager'; import UserManager from '../managers/UserManager'; import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; -import SharedGitlabManager from '../shared/managers/SharedGitlabManager'; import Config from '../config/Config'; +import GlobalHelper from '../helpers/GlobalHelper'; class SessionRoutes implements RoutesManager { @@ -64,7 +64,7 @@ class SessionRoutes implements RoutesManager { refreshToken: string } = req.body; - const gitlabTokens = await SharedGitlabManager.getTokens(params.refreshToken, true, Config.login.gitlab.client.secret); + const gitlabTokens = await GlobalHelper.sharedGitlabManager.getTokens(params.refreshToken, true, Config.login.gitlab.client.secret); req.session.sendResponse(res, StatusCodes.OK, gitlabTokens); } catch ( error ) { diff --git a/ExpressAPI/src/shared b/ExpressAPI/src/shared index 985c0b6a5dcb640404e5cf5fc91978215f961e3b..47b289f99ef88df993f5977efa6a53e4bd0abe85 160000 --- a/ExpressAPI/src/shared +++ b/ExpressAPI/src/shared @@ -1 +1 @@ -Subproject commit 985c0b6a5dcb640404e5cf5fc91978215f961e3b +Subproject commit 47b289f99ef88df993f5977efa6a53e4bd0abe85