From 3e6e6ff2cd7400d99e5e1c8249015d17a7015c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Minelli?= <git@minelli.me> Date: Tue, 14 May 2024 23:06:25 +0200 Subject: [PATCH] Fix bug on duplicate repo creation --- ExpressAPI/.vscode/launch.json | 38 ++++ ExpressAPI/assets/OpenAPI/OpenAPI.yaml | 2 +- ExpressAPI/src/helpers/GlobalHelper.ts | 2 +- ExpressAPI/src/managers/GitlabManager.ts | 201 +++++++++++++++------- ExpressAPI/src/routes/AssignmentRoutes.ts | 6 +- 5 files changed, 180 insertions(+), 69 deletions(-) create mode 100644 ExpressAPI/.vscode/launch.json diff --git a/ExpressAPI/.vscode/launch.json b/ExpressAPI/.vscode/launch.json new file mode 100644 index 0000000..11cc7b0 --- /dev/null +++ b/ExpressAPI/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "tsx", + "type": "node", + "request": "launch", + + // Debug current file in VSCode + "program": "src/app.ts", + + /* + Path to tsx binary + Assuming locally installed + */ + "runtimeExecutable": "tsx", + + /* + Open terminal when debugging starts (Optional) + Useful to see console.logs + */ + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + + // Files to exclude from debugger (e.g. call stack) + "skipFiles": [ + // Node.js internal core modules + "<node_internals>/**", + + // Ignore all dependencies (optional) + "${workspaceFolder}/node_modules/**", + ], + } + ] +} \ No newline at end of file diff --git a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml index 7712e73..11ce447 100644 --- a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml +++ b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: Dojo API - version: 4.0.0 + version: 4.1.0 description: | **Backend API of the Dojo project.** diff --git a/ExpressAPI/src/helpers/GlobalHelper.ts b/ExpressAPI/src/helpers/GlobalHelper.ts index 308a941..9d5e5e8 100644 --- a/ExpressAPI/src/helpers/GlobalHelper.ts +++ b/ExpressAPI/src/helpers/GlobalHelper.ts @@ -41,7 +41,7 @@ class GlobalHelper { } isRepoNameAlreadyTaken(errorDescription: unknown) { - return errorDescription instanceof Object && 'name' in errorDescription && errorDescription.name instanceof Array && errorDescription.name.length > 0 && errorDescription.name[0] === 'has already been taken'; + return errorDescription instanceof Array && errorDescription.length > 0 && (errorDescription[0] as string).includes('has already been taken'); } addRepoMember(repositoryId: number) { diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts index 5571185..4c25d86 100644 --- a/ExpressAPI/src/managers/GitlabManager.ts +++ b/ExpressAPI/src/managers/GitlabManager.ts @@ -24,16 +24,27 @@ class GitlabManager extends SharedGitlabManager { return profileApi.Users.showCurrentUser(); } catch ( e ) { + logger.error(JSON.stringify(e)); return undefined; } } - getRepositoryMembers(idOrNamespace: string): Promise<Array<MemberSchema>> { - return this.api.ProjectMembers.all(idOrNamespace, { includeInherited: true }); + async getRepositoryMembers(idOrNamespace: string): Promise<Array<MemberSchema>> { + try { + return await this.api.ProjectMembers.all(idOrNamespace, { includeInherited: true }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - getRepositoryReleases(repoId: number): Promise<Array<ReleaseSchema>> { - return this.api.ProjectReleases.all(repoId); + async getRepositoryReleases(repoId: number): Promise<Array<ReleaseSchema>> { + try { + return await this.api.ProjectReleases.all(repoId); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } async getRepositoryLastCommit(repoId: number, branch: string = 'main'): Promise<CommitSchema | undefined> { @@ -51,57 +62,92 @@ class GitlabManager extends SharedGitlabManager { } } - createRepository(name: string, description: string, visibility: 'public' | 'internal' | 'private', initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, importUrl: string): Promise<ProjectSchema> { - return this.api.Projects.create({ - name : name, - description : description, - importUrl : importUrl, - initializeWithReadme: initializeWithReadme, - namespaceId : namespace, - sharedRunnersEnabled: sharedRunnersEnabled, - visibility : visibility, - wikiAccessLevel : wikiEnabled ? 'enabled' : 'disabled' - }); + async createRepository(name: string, description: string, visibility: 'public' | 'internal' | 'private', initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, importUrl: string): Promise<ProjectSchema> { + try { + return await this.api.Projects.create({ + name : name, + description : description, + importUrl : importUrl, + initializeWithReadme: initializeWithReadme, + namespaceId : namespace, + sharedRunnersEnabled: sharedRunnersEnabled, + visibility : visibility, + wikiAccessLevel : wikiEnabled ? 'enabled' : 'disabled' + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - deleteRepository(repoId: number): Promise<void> { - return this.api.Projects.remove(repoId); + async deleteRepository(repoId: number): Promise<void> { + try { + return await this.api.Projects.remove(repoId); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - forkRepository(forkId: number, name: string, path: string, description: string, visibility: 'public' | 'internal' | 'private', namespace: number): Promise<ProjectSchema> { - return this.api.Projects.fork(forkId, { - name : name, - path : path, - description: description, - namespaceId: namespace, - visibility : visibility - }); + async forkRepository(forkId: number, name: string, path: string, description: string, visibility: 'public' | 'internal' | 'private', namespace: number): Promise<ProjectSchema> { + try { + return await this.api.Projects.fork(forkId, { + name : name, + path : path, + description: description, + namespaceId: namespace, + visibility : visibility + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - editRepository(repoId: number, newAttributes: EditProjectOptions): Promise<ProjectSchema> { - return this.api.Projects.edit(repoId, newAttributes); + async editRepository(repoId: number, newAttributes: EditProjectOptions): Promise<ProjectSchema> { + try { + return await this.api.Projects.edit(repoId, newAttributes); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<ProjectSchema> { return this.editRepository(repoId, { visibility: visibility }); } - addRepositoryMember(repoId: number, userId: number, accessLevel: Exclude<AccessLevel, AccessLevel.ADMIN>): Promise<MemberSchema> { - return this.api.ProjectMembers.add(repoId, userId, accessLevel); + async addRepositoryMember(repoId: number, userId: number, accessLevel: Exclude<AccessLevel, AccessLevel.ADMIN>): Promise<MemberSchema> { + try { + return await this.api.ProjectMembers.add(repoId, userId, accessLevel); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - addRepositoryVariable(repoId: number, key: string, value: string, isProtected: boolean, isMasked: boolean): Promise<ProjectVariableSchema> { - return this.api.ProjectVariables.create(repoId, key, value, { - variableType: 'env_var', - protected : isProtected, - masked : isMasked - }); + async addRepositoryVariable(repoId: number, key: string, value: string, isProtected: boolean, isMasked: boolean): Promise<ProjectVariableSchema> { + try { + return await this.api.ProjectVariables.create(repoId, key, value, { + variableType: 'env_var', + protected : isProtected, + masked : isMasked + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - addRepositoryBadge(repoId: number, linkUrl: string, imageUrl: string, name: string): Promise<ProjectBadgeSchema> { - return this.api.ProjectBadges.add(repoId, linkUrl, imageUrl, { - name: name - }); + async addRepositoryBadge(repoId: number, linkUrl: string, imageUrl: string, name: string): Promise<ProjectBadgeSchema> { + try { + return await this.api.ProjectBadges.add(repoId, linkUrl, imageUrl, { + name: name + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } async checkTemplateAccess(projectIdOrNamespace: string, req: express.Request, res?: express.Response): Promise<boolean> { @@ -143,34 +189,54 @@ class GitlabManager extends SharedGitlabManager { } } - protectBranch(repoId: number, branchName: string, allowForcePush: boolean, allowedToMerge: ProtectedBranchAccessLevel, allowedToPush: ProtectedBranchAccessLevel, allowedToUnprotect: ProtectedBranchAccessLevel): Promise<ProtectedBranchSchema> { - return this.api.ProtectedBranches.protect(repoId, branchName, { - allowForcePush : allowForcePush, - mergeAccessLevel : allowedToMerge, - pushAccessLevel : allowedToPush, - unprotectAccessLevel: allowedToUnprotect - }); + async protectBranch(repoId: number, branchName: string, allowForcePush: boolean, allowedToMerge: ProtectedBranchAccessLevel, allowedToPush: ProtectedBranchAccessLevel, allowedToUnprotect: ProtectedBranchAccessLevel): Promise<ProtectedBranchSchema> { + try { + return await this.api.ProtectedBranches.protect(repoId, branchName, { + allowForcePush : allowForcePush, + mergeAccessLevel : allowedToMerge, + pushAccessLevel : allowedToPush, + unprotectAccessLevel: allowedToUnprotect + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<RepositoryTreeSchema>> { - return this.api.Repositories.allRepositoryTrees(repoId, { - recursive: recursive, - ref : branch - }); + async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<RepositoryTreeSchema>> { + try { + return await this.api.Repositories.allRepositoryTrees(repoId, { + recursive: recursive, + ref : branch + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<RepositoryFileExpandedSchema> { - return this.api.RepositoryFiles.show(repoId, filePath, branch); + async getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<RepositoryFileExpandedSchema> { + try { + return await this.api.RepositoryFiles.show(repoId, filePath, branch); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } - private 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.bind(this.api) : this.api.RepositoryFiles.edit.bind(this.api); + 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> { + try { + const gitFunction = create ? this.api.RepositoryFiles.create.bind(this.api) : this.api.RepositoryFiles.edit.bind(this.api); - return gitFunction(repoId, filePath, branch, fileBase64, commitMessage, { - encoding : 'base64', - authorName : authorName, - authorEmail: authorMail - }); + return await gitFunction(repoId, filePath, branch, fileBase64, commitMessage, { + encoding : 'base64', + authorName : authorName, + authorEmail: authorMail + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } createFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> { @@ -181,11 +247,16 @@ class GitlabManager extends SharedGitlabManager { return this.createUpdateFile(false, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail); } - deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<void> { - return this.api.RepositoryFiles.remove(repoId, filePath, branch, commitMessage, { - authorName : authorName, - authorEmail: authorMail - }); + async deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<void> { + try { + return await this.api.RepositoryFiles.remove(repoId, filePath, branch, commitMessage, { + authorName : authorName, + authorEmail: authorMail + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } } } diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index 18c79a4..2055e65 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -22,6 +22,7 @@ import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js' import DojoModelsHelper from '../helpers/DojoModelsHelper.js'; import * as Gitlab from '@gitbeaker/rest'; import { GitbeakerRequestError } from '@gitbeaker/requester-utils'; +import SharedConfig from '../shared/config/SharedConfig.js'; class AssignmentRoutes implements RoutesManager { @@ -203,10 +204,11 @@ class AssignmentRoutes implements RoutesManager { } else if ( isUpdate && !req.boundParams.exercise?.isCorrection ) { return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is not a correction', DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST); } - + const lastCommit = await GitlabManager.getRepositoryLastCommit(req.boundParams.exercise!.gitlabId); + if ( lastCommit ) { - if ( !isUpdate ) { + if ( !isUpdate && SharedConfig.production ) { //Disable in dev env because gitlab dev group is private and we can't change visibility of sub projects await GitlabManager.changeRepositoryVisibility(req.boundParams.exercise!.gitlabId, 'internal'); } -- GitLab