From 0f3a3b84e08ac936d6c227690dc00c2c3a3415a2 Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Thu, 7 Mar 2024 09:07:28 +0100 Subject: [PATCH 01/15] Added: updates for updateAccess api --- server/mergin/sync/private_api.yaml | 54 ++++++++++++++++--- server/mergin/sync/private_api_controller.py | 39 ++++++++------ .../mergin/tests/test_private_project_api.py | 7 +++ 3 files changed, 79 insertions(+), 21 deletions(-) diff --git a/server/mergin/sync/private_api.yaml b/server/mergin/sync/private_api.yaml index cb09df24..f14ec2d8 100644 --- a/server/mergin/sync/private_api.yaml +++ b/server/mergin/sync/private_api.yaml @@ -367,12 +367,12 @@ paths: application/json: schema: type: object - required: - - user_id - - role properties: user_id: type: integer + public: + type: boolean + nullable: true role: type: string enum: @@ -380,10 +380,10 @@ paths: - writer - reader - none - example: write + example: writer responses: "200": - description: OK + $ref: "#/components/schemas/ProjectAccessUpdated" "400": $ref: "#/components/responses/BadStatusResp" "401": @@ -577,4 +577,46 @@ components: enum: - owner - writer - - reader \ No newline at end of file + - reader + ProjectAccessUpdated: + type: object + properties: + ownersnames: + type: array + nullable: false + items: + type: string + example: [ john.doe ] + writersnames: + type: array + nullable: false + items: + type: string + example: [ john.doe ] + readersnames: + nullable: false + type: array + items: + type: string + example: [ john.doe ] + public: + type: boolean + example: true + owners: + type: array + nullable: false + items: + type: integer + example: [1] + writers: + type: array + nullable: false + items: + type: integer + example: [1] + readers: + type: array + nullable: false + items: + type: integer + example: [1] diff --git a/server/mergin/sync/private_api_controller.py b/server/mergin/sync/private_api_controller.py index 438ac2f7..72fafea1 100644 --- a/server/mergin/sync/private_api_controller.py +++ b/server/mergin/sync/private_api_controller.py @@ -14,7 +14,12 @@ from ..auth.models import User, UserProfile from .forms import AccessPermissionForm from .models import Project, AccessRequest, ProjectRole -from .schemas import ProjectListSchema, ProjectAccessRequestSchema, AdminProjectSchema +from .schemas import ( + ProjectListSchema, + ProjectAccessRequestSchema, + AdminProjectSchema, + ProjectAccessSchema, +) from .permissions import ( require_project_by_uuid, ProjectPermissions, @@ -272,23 +277,27 @@ def update_project_access(id: str): """Modify shared project access :param id: Project uuid - :rtype: None """ project = require_project_by_uuid(id, ProjectPermissions.Update) - user = User.query.filter_by(id=request.json["user_id"], active=True).first_or_404( - "User does not exist" - ) - # prevent to remove ownership of project creator - if user.id == project.creator_id: - abort(400, "Ownership of project creator cannot be removed") - - if request.json["role"] == "none": - project.access.unset_role(user.id) - else: - project.access.set_role(user.id, ProjectRole(request.json["role"])) - project_access_granted.send(project, user_id=user.id) + + if "public" in request.json: + project.access.public = request.json["public"] + + if "user_id" in request.json and "role" in request.json: + user = User.query.filter_by( + id=request.json["user_id"], active=True + ).first_or_404("User does not exist") + # prevent to remove ownership of project creator + if user.id == project.creator_id: + abort(400, "Ownership of project creator cannot be removed") + + if request.json["role"] == "none": + project.access.unset_role(user.id) + else: + project.access.set_role(user.id, ProjectRole(request.json["role"])) + project_access_granted.send(project, user_id=user.id) db.session.commit() - return NoContent, 200 + return ProjectAccessSchema().dump(project.access), 200 @auth_required diff --git a/server/mergin/tests/test_private_project_api.py b/server/mergin/tests/test_private_project_api.py index 0f3a9261..5d5071fc 100644 --- a/server/mergin/tests/test_private_project_api.py +++ b/server/mergin/tests/test_private_project_api.py @@ -356,6 +356,13 @@ def test_update_project_access(client, diff_project): assert user.id not in diff_project.access.readers assert user.id not in diff_project.access.writers + # update public parameter => public: True + data["public"] = True + resp = client.patch(url, headers=json_headers, data=json.dumps(data)) + assert resp.status_code == 200 + assert diff_project.public == True + + # access of project creator can not be removed data["user_id"] = diff_project.creator_id resp = client.patch( From e0250727289ab2331234b98e294d4c4874677e1c Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Thu, 7 Mar 2024 13:23:47 +0100 Subject: [PATCH 02/15] Added: Collaborators tab --- web-app/packages/app/src/App.vue | 2 +- .../views/ProjectCollaboratorsView.vue | 40 ++++ .../project/views/ProjectSettingsView.vue | 38 +-- .../src/modules/project/views/ProjectView.vue | 12 +- web-app/packages/app/src/router.ts | 10 +- .../lib/src/common/permission_utils.ts | 2 +- .../components/DashboardAccessRequestsRow.vue | 23 +- .../layout/components/AppHeaderTemplate.vue | 10 +- .../project/components/ProjectForm.vue | 5 +- .../components/ProjectMembersTable.vue | 199 ++++++++++++++++ .../project/components/ProjectShareDialog.vue | 5 +- .../src/modules/project/components/index.ts | 1 + .../lib/src/modules/project/projectApi.ts | 16 +- .../lib/src/modules/project/routes.ts | 1 + .../packages/lib/src/modules/project/store.ts | 121 ++++++++-- .../packages/lib/src/modules/project/types.ts | 15 +- .../ProjectCollaboratorsViewTemplate.vue | 87 +++++++ .../views/ProjectSettingsViewTemplate.vue | 220 ++++++------------ .../project/views/ProjectViewTemplate.vue | 26 +-- .../lib/src/modules/project/views/index.ts | 1 + .../packages/lib/src/modules/user/store.ts | 23 +- .../user/views/ProfileViewTemplate.vue | 93 ++++---- 22 files changed, 636 insertions(+), 314 deletions(-) create mode 100644 web-app/packages/app/src/modules/project/views/ProjectCollaboratorsView.vue create mode 100644 web-app/packages/lib/src/modules/project/components/ProjectMembersTable.vue create mode 100644 web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue diff --git a/web-app/packages/app/src/App.vue b/web-app/packages/app/src/App.vue index a0aba4b0..67f24560 100644 --- a/web-app/packages/app/src/App.vue +++ b/web-app/packages/app/src/App.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
- +
diff --git a/web-app/packages/app/src/modules/project/views/ProjectCollaboratorsView.vue b/web-app/packages/app/src/modules/project/views/ProjectCollaboratorsView.vue new file mode 100644 index 00000000..e0c1853c --- /dev/null +++ b/web-app/packages/app/src/modules/project/views/ProjectCollaboratorsView.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/web-app/packages/app/src/modules/project/views/ProjectSettingsView.vue b/web-app/packages/app/src/modules/project/views/ProjectSettingsView.vue index 4b38ce6e..62424862 100644 --- a/web-app/packages/app/src/modules/project/views/ProjectSettingsView.vue +++ b/web-app/packages/app/src/modules/project/views/ProjectSettingsView.vue @@ -8,51 +8,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial - - diff --git a/web-app/packages/app/src/modules/project/views/ProjectView.vue b/web-app/packages/app/src/modules/project/views/ProjectView.vue index 01da8576..4a856a86 100644 --- a/web-app/packages/app/src/modules/project/views/ProjectView.vue +++ b/web-app/packages/app/src/modules/project/views/ProjectView.vue @@ -9,8 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial :show-namespace="false" :namespace="namespace" :projectName="projectName" - :asAdmin="asAdmin" - :location="location" :show-settings="isProjectOwner" :hide-clone-button="!canCreateProject" @open-clone-dialog="openCloneDialog" @@ -37,15 +35,7 @@ export default defineComponent({ }, props: { namespace: String, - projectName: String, - asAdmin: { - type: Boolean, - default: false - }, - location: { - type: String, - default: '' - } + projectName: String }, setup(props) { const userStore = useUserStore() diff --git a/web-app/packages/app/src/router.ts b/web-app/packages/app/src/router.ts index 93d4605b..3ee7f5fc 100644 --- a/web-app/packages/app/src/router.ts +++ b/web-app/packages/app/src/router.ts @@ -11,7 +11,8 @@ import { VerifyEmailView, routeUtils, useUserStore, - SideBarTemplate as SideBar + SideBarTemplate as SideBar, + ProjectRouteName } from '@mergin/lib' import { Pinia } from 'pinia' import { @@ -21,6 +22,7 @@ import { import DashboardView from '@/modules/dashboard/views/DashboardView.vue' import AppHeader from '@/modules/layout/components/AppHeader.vue' +import ProjectCollaboratorsView from '@/modules/project/views/ProjectCollaboratorsView.vue' import ProjectSettingsView from '@/modules/project/views/ProjectSettingsView.vue' import ProjectsListView from '@/modules/project/views/ProjectsListView.vue' import ProjectView from '@/modules/project/views/ProjectView.vue' @@ -204,6 +206,12 @@ export const createRouter = (pinia: Pinia) => { component: ProjectVersionsView, props: true }, + { + path: 'collaborators', + name: ProjectRouteName.ProjectCollaborators, + component: ProjectCollaboratorsView, + props: true + }, { path: 'history/:version_id/:path', name: 'file-version-detail', diff --git a/web-app/packages/lib/src/common/permission_utils.ts b/web-app/packages/lib/src/common/permission_utils.ts index 4b4cbb8c..d70ca8e9 100644 --- a/web-app/packages/lib/src/common/permission_utils.ts +++ b/web-app/packages/lib/src/common/permission_utils.ts @@ -151,7 +151,7 @@ export function getProjectPermissionsValues(): DropdownOption = { diff --git a/web-app/packages/lib/src/modules/dashboard/components/DashboardAccessRequestsRow.vue b/web-app/packages/lib/src/modules/dashboard/components/DashboardAccessRequestsRow.vue index b0ec23b1..eb13c380 100644 --- a/web-app/packages/lib/src/modules/dashboard/components/DashboardAccessRequestsRow.vue +++ b/web-app/packages/lib/src/modules/dashboard/components/DashboardAccessRequestsRow.vue @@ -27,17 +27,26 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial diff --git a/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue b/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue index 74d619f2..9b117cde 100644 --- a/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue +++ b/web-app/packages/lib/src/modules/layout/components/AppHeaderTemplate.vue @@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
{{ userName }} - {{ currentNamespace || 'no workspace' }} + {{ currentWorkspace?.name || 'no workspace' }}
+ + + + + + diff --git a/web-app/packages/lib/src/modules/project/components/ProjectShareDialog.vue b/web-app/packages/lib/src/modules/project/components/ProjectShareDialog.vue index a208a16c..0a169b3f 100644 --- a/web-app/packages/lib/src/modules/project/components/ProjectShareDialog.vue +++ b/web-app/packages/lib/src/modules/project/components/ProjectShareDialog.vue @@ -121,7 +121,7 @@ const search = async (e: AutoCompleteCompleteEvent) => { try { data.isPending = true const params: UserSearchParams = { - namespace: projectStore.currentNamespace, + namespace: userStore.currentWorkspace?.name, like: e.query } const response = await userStore.getAuthNotProjectUserSearch(params) @@ -149,7 +149,7 @@ const share = async () => { } = projectStore.project.access try { await projectStore.saveProjectAccessByRoleName({ - namespace: projectStore.currentNamespace, + namespace: userStore.currentWorkspace?.name, settings: { access: { ownersnames, @@ -162,6 +162,7 @@ const share = async () => { roleName: data.permission, projectName: projectStore.project.name }) + await projectStore.getProjectAccess(projectStore.project?.id) dialogStore.close() } catch (err) { notificationStore.error({ diff --git a/web-app/packages/lib/src/modules/project/components/index.ts b/web-app/packages/lib/src/modules/project/components/index.ts index 468f04ea..80b046ae 100644 --- a/web-app/packages/lib/src/modules/project/components/index.ts +++ b/web-app/packages/lib/src/modules/project/components/index.ts @@ -22,3 +22,4 @@ export { default as FileDetailSidebar } from './FileDetailSidebar.vue' export { default as VersionDetailSidebar } from './VersionDetailSidebar.vue' export { default as CommunityBanner } from './CommunityBanner.vue' export { default as UploadProgress } from './UploadProgress.vue' +export { default as ProjectMembersTable } from './ProjectMembersTable.vue' diff --git a/web-app/packages/lib/src/modules/project/projectApi.ts b/web-app/packages/lib/src/modules/project/projectApi.ts index 9b099191..df85415e 100644 --- a/web-app/packages/lib/src/modules/project/projectApi.ts +++ b/web-app/packages/lib/src/modules/project/projectApi.ts @@ -22,11 +22,13 @@ import { PushProjectChangesResponse, SaveProjectSettings, UpdateProjectAccessParams, - ProjectVersion + ProjectVersion, + ProjectAccessDetail, + ProjectAccess } from '@/modules/project/types' export const ProjectApi = { - async fetchProject( + async getProject( namespace: string, projectName: string ): Promise> { @@ -168,11 +170,11 @@ export const ProjectApi = { ) }, - async updateProjectAccessForUser( + async updateProjectAccess( id: string, data: UpdateProjectAccessParams, withRetry?: boolean - ): Promise> { + ): Promise> { return ProjectModule.httpService.patch(`/app/project/${id}/access`, data, { ...(withRetry ? getDefaultRetryOptions() : {}) }) @@ -266,5 +268,11 @@ export const ProjectApi = { async downloadFile(url: string): Promise> { return ProjectModule.httpService.get(url, { responseType: 'blob' }) + }, + + async getProjectAccess( + projectId: string + ): Promise> { + return ProjectModule.httpService.get(`/app/project/${projectId}/access`) } } diff --git a/web-app/packages/lib/src/modules/project/routes.ts b/web-app/packages/lib/src/modules/project/routes.ts index 3745f895..a07b601b 100644 --- a/web-app/packages/lib/src/modules/project/routes.ts +++ b/web-app/packages/lib/src/modules/project/routes.ts @@ -32,6 +32,7 @@ export enum ProjectRouteName { ProjectTree = 'project-tree', ProjectSettings = 'project-settings', ProjectHistory = 'project-versions', + ProjectCollaborators = 'project-collaborators', /** @deprecated */ VersionDetail = 'project-versions-detail', FileVersionDetail = 'file-version-detail' diff --git a/web-app/packages/lib/src/modules/project/store.ts b/web-app/packages/lib/src/modules/project/store.ts index 45be9b86..419bb98a 100644 --- a/web-app/packages/lib/src/modules/project/store.ts +++ b/web-app/packages/lib/src/modules/project/store.ts @@ -12,7 +12,7 @@ import { getErrorMessage } from '@/common/error_utils' import { waitCursor } from '@/common/html_utils' import { filesDiff } from '@/common/mergin_utils' import { - getProjectAccessUsersByRoleName as getProjectAccessKeyByRoleName, + getProjectAccessKeyByRoleName, isAtLeastProjectRole, ProjectRole, ProjectRoleName @@ -37,9 +37,11 @@ import { GetAccessRequestsPayload, DownloadPayload, DeleteProjectPayload, - ProjectsSortingParams, + SortingParams, SaveProjectSettings, - ErrorCodes + ErrorCodes, + ProjectAccessDetail, + UpdateProjectAccessParams } from '@/modules/project/types' import { useUserStore } from '@/modules/user/store' @@ -58,12 +60,15 @@ export interface ProjectState { projects: ProjectListItem[] projectsCount: number projectsSearch: string - projectsSorting: ProjectsSortingParams + projectsSorting: SortingParams uploads: object - currentNamespace: string versions: ProjectVersion[] versionsCount: number versionsLoading: boolean + access: ProjectAccessDetail[] + accessLoading: boolean + accessSearch: string + accessSorting: SortingParams } export const useProjectStore = defineStore('projectModule', { @@ -74,7 +79,6 @@ export const useProjectStore = defineStore('projectModule', { projectsCount: 0, projects: undefined, uploads: {}, - currentNamespace: null, versions: [], versionsCount: 0, versionsLoading: false, @@ -82,7 +86,11 @@ export const useProjectStore = defineStore('projectModule', { projectsSorting: { sortBy: 'updated', sortDesc: true - } + }, + access: [], + accessLoading: false, + accessSearch: '', + accessSorting: undefined }), getters: { @@ -92,6 +100,22 @@ export const useProjectStore = defineStore('projectModule', { return (payload) => { return state.projects?.find((project) => project.name === payload.name) } + }, + + /** + * Checks if the current user can remove the given project access detail. + * + * The user must be at least a project owner, and the access detail ID cannot + * be the same as the project creator ID. + * + * @param payload - The project access detail to check + * @returns True if the current user can remove the given access, false otherwise + */ + canRemoveProjectAccess: (state) => (payload: ProjectAccessDetail) => { + return ( + isAtLeastProjectRole(state.project?.role, ProjectRole.owner) && + payload.id !== state.project?.creator + ) } }, @@ -229,10 +253,6 @@ export const useProjectStore = defineStore('projectModule', { upload.diff = filesDiff(this.project.files, upload.files) }, - setCurrentNamespace(payload) { - this.currentNamespace = payload.currentNamespace - }, - async initProjects(payload: PaginatedProjectsPayload) { if (!(this.projects?.length > 0)) { await this.getProjects(payload) @@ -430,7 +450,7 @@ export const useProjectStore = defineStore('projectModule', { const notificationStore = useNotificationStore() try { - const projectResponse = await ProjectApi.fetchProject( + const projectResponse = await ProjectApi.getProject( payload.namespace, payload.projectName ) @@ -462,7 +482,7 @@ export const useProjectStore = defineStore('projectModule', { const { callbackStatus, namespace, projectName, isLoggedUser } = payload try { - const projectResponse = await ProjectApi.fetchProject( + const projectResponse = await ProjectApi.getProject( payload.namespace, payload.projectName ) @@ -679,8 +699,81 @@ export const useProjectStore = defineStore('projectModule', { ) }, - setProjectsSorting(payload: ProjectsSortingParams) { + setProjectsSorting(payload: SortingParams) { this.projectsSorting = payload + }, + + async getProjectAccess(projectId: string) { + const notificationStore = useNotificationStore() + + try { + this.accessLoading = true + const response = await ProjectApi.getProjectAccess(projectId) + this.access = response.data + } catch { + notificationStore.error({ + text: 'Failed to get project access' + }) + } finally { + this.accessLoading = false + } + }, + + /** + * Removes the given user's access to the current project. + * + * @param item - The project access detail object for the user to remove. + */ + async removeProjectAccess(item: ProjectAccessDetail) { + const notificationStore = useNotificationStore() + this.accessLoading = true + try { + const response = await ProjectApi.updateProjectAccess(this.project.id, { + user_id: item.id, + role: 'none' + }) + this.access = this.access.filter((access) => access.id !== item.id) + this.project.access = response.data + } catch { + notificationStore.error({ + text: `Failed to update project access for user ${item.username}` + }) + } finally { + this.accessLoading = false + } + }, + + /** + * Updates the access for a user in the given project by their role name. + * + * @param payload - Object containing the project ID and access update parameters. + * @returns Promise resolving when the access update completes. + */ + async updateProjectAccess(payload: { + projectId: string + data: UpdateProjectAccessParams + }) { + const notificationStore = useNotificationStore() + this.accessLoading = true + try { + const response = await ProjectApi.updateProjectAccess( + payload.projectId, + payload.data + ) + this.access = this.access.map((access) => { + if (access.id === payload.data.user_id) { + access.project_permission = payload.data.role + } + return access + }) + this.project.access = response.data + } catch { + notificationStore.error({ + text: `Failed to update project access` + }) + } finally { + this.accessLoading = false + } } } }) diff --git a/web-app/packages/lib/src/modules/project/types.ts b/web-app/packages/lib/src/modules/project/types.ts index 8d580b6e..0a8d2949 100644 --- a/web-app/packages/lib/src/modules/project/types.ts +++ b/web-app/packages/lib/src/modules/project/types.ts @@ -279,8 +279,9 @@ export type EnhancedProjectDetail = ProjectDetail & { } export interface UpdateProjectAccessParams { - user_id: number - role: ProjectRoleName + user_id?: number + role?: ProjectRoleName + public?: boolean } export interface DownloadPayload { @@ -316,7 +317,7 @@ export interface VDataIteratorOptions { sortBy?: Array<{ key: string; order?: boolean | 'asc' | 'desc' }> } -export interface ProjectsSortingParams { +export interface SortingParams { sortBy: string sortDesc: boolean } @@ -332,3 +333,11 @@ export interface ProjectVersionFileChange { } export type ErrorCodes = 'UpdateProjectAccessError' + +export interface ProjectAccessDetail { + id: number + type: 'user' + email: number + username: string + project_permission: ProjectRoleName +} diff --git a/web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue b/web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue new file mode 100644 index 00000000..54356573 --- /dev/null +++ b/web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue @@ -0,0 +1,87 @@ + + + + + + + diff --git a/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue b/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue index 670d7ecc..5b428ca3 100644 --- a/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue +++ b/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue @@ -10,197 +10,129 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial ({{ accessRequestsCount }}) - - - + -
-
- - -
-
- - - - + + + +
- -
- -
-
-
-

Delete project

- All data will be lost +
+
-
- - Delete project +
+
+

Delete project

+ All data will be lost +
+
+ + Delete project +
- +
- + diff --git a/web-app/packages/lib/src/modules/user/views/ProfileViewTemplate.vue b/web-app/packages/lib/src/modules/user/views/ProfileViewTemplate.vue index bedb7548..470c00e5 100644 --- a/web-app/packages/lib/src/modules/user/views/ProfileViewTemplate.vue +++ b/web-app/packages/lib/src/modules/user/views/ProfileViewTemplate.vue @@ -150,9 +150,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial @click="confirmDeleteUser" severity="danger" data-cy="profile-close-account-btn" - > - Close account + label="Close account" + />
From d8d4711fd7c6d026f20eb196781e8cf746d8208c Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Mon, 11 Mar 2024 13:27:48 +0100 Subject: [PATCH 05/15] Fix: missing if by parsing of axios errors --- .../packages/lib/src/modules/project/store.ts | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/web-app/packages/lib/src/modules/project/store.ts b/web-app/packages/lib/src/modules/project/store.ts index 419bb98a..6511d16e 100644 --- a/web-app/packages/lib/src/modules/project/store.ts +++ b/web-app/packages/lib/src/modules/project/store.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial -import { AxiosError, AxiosResponse } from 'axios' +import axios, { AxiosError, AxiosResponse } from 'axios' import FileSaver from 'file-saver' import keyBy from 'lodash/keyBy' import omit from 'lodash/omit' @@ -488,6 +488,12 @@ export const useProjectStore = defineStore('projectModule', { ) this.setProject({ project: projectResponse.data }) } catch (e) { + if (!axios.isAxiosError(e)) { + notificationStore.error({ + text: 'Failed to load project data' + }) + return + } callbackStatus(e.response.status) if (e.response.status !== 404 && e.response.status !== 403) { @@ -680,12 +686,14 @@ export const useProjectStore = defineStore('projectModule', { FileSaver.saveAs(resp.data, fileName) } catch (e) { // parse error details from blob - let resp - const blob = new Blob([e.response.data], { type: 'text/plain' }) - blob.text().then((text) => { - resp = JSON.parse(text) - notificationStore.error({ text: resp.detail }) - }) + if (axios.isAxiosError(e)) { + let resp + const blob = new Blob([e.response.data], { type: 'text/plain' }) + blob.text().then((text) => { + resp = JSON.parse(text) + notificationStore.error({ text: resp.detail }) + }) + } } }, @@ -724,7 +732,9 @@ export const useProjectStore = defineStore('projectModule', { * * @param item - The project access detail object for the user to remove. */ - async removeProjectAccess(item: ProjectAccessDetail) { + async removeProjectAccess( + item: Pick + ) { const notificationStore = useNotificationStore() this.accessLoading = true try { @@ -744,10 +754,10 @@ export const useProjectStore = defineStore('projectModule', { }, /** - * Updates the access for a user in the given project by their role name. + * Updates the access for a user on the given project. `project.access` contains also public attribute. This attribute can be changed also. * - * @param payload - Object containing the project ID and access update parameters. - * @returns Promise resolving when the access update completes. + * @param payload - Object containing the project ID and access update details. + * @returns Promise resolving when the API call completes. */ async updateProjectAccess(payload: { projectId: string From fb176e8adb9295a2e8362142759393c61f65cf2b Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Mon, 11 Mar 2024 13:29:13 +0100 Subject: [PATCH 06/15] Added: - mapping to new api for project members - design: padding for logo fix Fix: - pass access detail not id to canRemoveProject access --- web-app/packages/lib/src/common/components/index.ts | 2 +- .../modules/layout/components/SideBarTemplate.vue | 2 +- .../project/components/ProjectMembersTable.vue | 13 ++++++++++--- web-app/packages/lib/src/modules/project/types.ts | 4 ++-- .../views/ProjectCollaboratorsViewTemplate.vue | 4 +--- .../project/views/ProjectSettingsViewTemplate.vue | 3 +-- .../project/views/ProjectVersionsViewTemplate.vue | 1 + 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/web-app/packages/lib/src/common/components/index.ts b/web-app/packages/lib/src/common/components/index.ts index 708031f7..eed28a95 100644 --- a/web-app/packages/lib/src/common/components/index.ts +++ b/web-app/packages/lib/src/common/components/index.ts @@ -10,7 +10,7 @@ export { default as AppPanelToggleable } from './AppPanelToggleable.vue' export { default as AppsidebarRight } from './AppSidebarRight.vue' export { default as AppPasswordTooltip } from './AppPasswordTooltip.vue' export { default as AppMenu } from './AppMenu.vue' -export { default as AppDropdwown } from './AppDropdown.vue' +export { default as AppDropdown } from './AppDropdown.vue' export { default as FullStorageWarning } from './FullStorageWarning.vue' export { default as FullStorageWarningTemplate } from './FullStorageWarningTemplate.vue' export { default as TipMessage } from './TipMessage.vue' diff --git a/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue b/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue index 85c78fd6..02792d14 100644 --- a/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue +++ b/web-app/packages/lib/src/modules/layout/components/SideBarTemplate.vue @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial >
-
+
{{ col.text }}

- +
(me) - +
@@ -97,7 +103,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial @click.stop="removeMember(item)" class="text-xl p-0" :style="{ - visibility: projectStore.canRemoveProjectAccess(item.id) + visibility: projectStore.canRemoveProjectAccess(item) ? 'visible' : 'hidden' }" @@ -130,6 +136,7 @@ import { } from '@/common/permission_utils' import { useUserStore } from '@/main' +// TODO: Externalize to AppCol element interface ColumnItem { text: string value: string diff --git a/web-app/packages/lib/src/modules/project/types.ts b/web-app/packages/lib/src/modules/project/types.ts index 0a8d2949..98135f98 100644 --- a/web-app/packages/lib/src/modules/project/types.ts +++ b/web-app/packages/lib/src/modules/project/types.ts @@ -336,8 +336,8 @@ export type ErrorCodes = 'UpdateProjectAccessError' export interface ProjectAccessDetail { id: number - type: 'user' - email: number + type: 'member' + email: string username: string project_permission: ProjectRoleName } diff --git a/web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue b/web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue index 54356573..e7168466 100644 --- a/web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue +++ b/web-app/packages/lib/src/modules/project/views/ProjectCollaboratorsViewTemplate.vue @@ -7,9 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial diff --git a/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue b/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue index 92e51267..1cdf8942 100644 --- a/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue +++ b/web-app/packages/lib/src/modules/project/components/ProjectAccessRequests.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial />
+ + + @@ -39,18 +51,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial diff --git a/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue b/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue index 062e035c..6d70aa2e 100644 --- a/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue +++ b/web-app/packages/lib/src/modules/project/views/ProjectSettingsViewTemplate.vue @@ -6,14 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial