Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion server/mergin/sync/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,8 @@ class ProjectAccessDetail:
role: str
username: str
name: Optional[str]
project_permission: str
workspace_role: str
project_role: Optional[ProjectRole]
type: str


Expand Down
44 changes: 26 additions & 18 deletions server/mergin/sync/private_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -551,8 +551,7 @@ components:
- id
- type
- email
- project_permission
- role
- workspace_role
properties:
id:
description: User/Invitation (uu)id
Expand All @@ -569,16 +568,9 @@ components:
type: string
format: email
example: john.doe@example.com
role:
workspace_role:
description: Workspace role
type: string
enum:
- owner
- admin
- writer
- editor
- reader
- guest
$ref: "#/components/schemas/WorkspaceRole"
username:
description: Present only for type `member`
type: string
Expand All @@ -587,13 +579,13 @@ components:
description: Present only for type `member`
type: string
example: John Doe
project_permission:
type: string
enum:
- owner
- writer
- editor
- reader
role:
description: Project role defined as combination of project and workspace roles
$ref: "#/components/schemas/ProjectRole"
project_role:
nullable: true
description: Project role defined in database, not calculated version
$ref: "#/components/schemas/ProjectRole"
invitation:
description: Present only for type `invitation`
type: object
Expand Down Expand Up @@ -658,3 +650,19 @@ components:
items:
type: integer
example: [1]
WorkspaceRole:
type: string
enum:
- owner
- admin
- writer
- editor
- reader
- guest
ProjectRole:
type: string
enum:
- owner
- writer
- editor
- reader
1 change: 0 additions & 1 deletion server/mergin/sync/private_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
AdminProjectSchema,
ProjectAccessSchema,
ProjectAccessDetailSchema,
ProjectVersionListSchema,
)
from .permissions import (
require_project_by_uuid,
Expand Down
3 changes: 2 additions & 1 deletion server/mergin/sync/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ class ProjectAccessDetailSchema(Schema):
role = fields.String()
username = fields.String()
name = fields.String()
project_permission = fields.String()
workspace_role = fields.String()
project_role = fields.String()
type = fields.String()
invitation = fields.Nested(ProjectInvitationAccessSchema())

Expand Down
13 changes: 8 additions & 5 deletions server/mergin/sync/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,17 +323,19 @@ def project_access(self, project: Project) -> List[ProjectAccessDetail]:

direct_members_ids = [u.user_id for u in project.project_users]
users = User.query.filter(User.active.is_(True)).order_by(User.email)
direct_members = users.filter(User.id.in_(direct_members_ids)).all()
direct_members: list[User] = users.filter(User.id.in_(direct_members_ids)).all()

for dm in direct_members:
project_role = ProjectPermissions.get_user_project_role(project, dm)
project_permission = ProjectPermissions.get_user_project_role(project, dm)
project_role = project.get_role(dm.id)
member = ProjectAccessDetail(
id=dm.id,
username=dm.username,
role=ws.get_user_role(dm).value,
workspace_role=ws.get_user_role(dm).value,
name=dm.profile.name(),
email=dm.email,
project_permission=project_role and project_role.value,
role=project_permission and project_permission.value,
project_role=project_role.value if project_role else None,
type="member",
)
result.append(member)
Expand All @@ -345,8 +347,9 @@ def project_access(self, project: Project) -> List[ProjectAccessDetail]:
username=gm.username,
name=gm.profile.name(),
email=gm.email,
workspace_role=global_role,
role=global_role,
project_permission=global_role,
project_role=None,
type="member",
)
result.append(member)
Expand Down
28 changes: 14 additions & 14 deletions server/mergin/tests/test_private_project_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,45 +490,45 @@ def test_get_project_access(client):
resp = client.get(url)
assert resp.status_code == 200
assert len(resp.json) == 1
assert resp.json[0]["project_permission"] == "owner"
assert resp.json[0]["role"] == "owner"
project.set_role(users[0].id, ProjectRole.OWNER)
project.set_role(users[1].id, ProjectRole.WRITER)
project.set_role(users[2].id, ProjectRole.READER)
db.session.commit()
resp = client.get(url)
assert resp.status_code == 200
assert len(resp.json) == 4
assert sum(map(lambda x: int(x["project_permission"] == "owner"), resp.json)) == 2
assert sum(map(lambda x: int(x["project_permission"] == "writer"), resp.json)) == 1
assert sum(map(lambda x: int(x["project_permission"] == "reader"), resp.json)) == 1
assert sum(map(lambda x: int(x["role"] == "owner"), resp.json)) == 2
assert sum(map(lambda x: int(x["role"] == "writer"), resp.json)) == 1
assert sum(map(lambda x: int(x["role"] == "reader"), resp.json)) == 1
# user3 does not have access to the project
assert not any(users[3].email == access["email"] for access in resp.json)
assert any(users[2].email == access["email"] for access in resp.json)
Configuration.GLOBAL_READ = True
resp = client.get(url)
assert resp.status_code == 200
assert len(resp.json) == 6
assert sum(map(lambda x: int(x["project_permission"] == "owner"), resp.json)) == 2
assert sum(map(lambda x: int(x["project_permission"] == "writer"), resp.json)) == 1
assert sum(map(lambda x: int(x["project_permission"] == "reader"), resp.json)) == 3
assert sum(map(lambda x: int(x["role"] == "owner"), resp.json)) == 2
assert sum(map(lambda x: int(x["role"] == "writer"), resp.json)) == 1
assert sum(map(lambda x: int(x["role"] == "reader"), resp.json)) == 3
Configuration.GLOBAL_WRITE = True
resp = client.get(url)
assert resp.status_code == 200
assert len(resp.json) == 6
assert sum(map(lambda x: int(x["project_permission"] == "owner"), resp.json)) == 2
assert sum(map(lambda x: int(x["project_permission"] == "writer"), resp.json)) == 4
assert sum(map(lambda x: int(x["project_permission"] == "reader"), resp.json)) == 0
assert sum(map(lambda x: int(x["role"] == "owner"), resp.json)) == 2
assert sum(map(lambda x: int(x["role"] == "writer"), resp.json)) == 4
assert sum(map(lambda x: int(x["role"] == "reader"), resp.json)) == 0
Configuration.GLOBAL_ADMIN = True
resp = client.get(url)
assert resp.status_code == 200
assert len(resp.json) == 6
assert sum(map(lambda x: int(x["project_permission"] == "owner"), resp.json)) == 6
assert sum(map(lambda x: int(x["project_permission"] == "writer"), resp.json)) == 0
assert sum(map(lambda x: int(x["project_permission"] == "reader"), resp.json)) == 0
assert sum(map(lambda x: int(x["role"] == "owner"), resp.json)) == 6
assert sum(map(lambda x: int(x["role"] == "writer"), resp.json)) == 0
assert sum(map(lambda x: int(x["role"] == "reader"), resp.json)) == 0
# pretend a user was deleted to test that api can handle it
users[3].inactivate()
users[3].anonymize()
resp = client.get(url)
assert resp.status_code == 200
assert len(resp.json) == 5
assert sum(map(lambda x: int(x["project_permission"] == "owner"), resp.json)) == 5
assert sum(map(lambda x: int(x["role"] == "owner"), resp.json)) == 5
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ const changeStatusDialog = () => {
await adminStore.updateUser({
username: user.value.username,
data: {
active: !user.value.active
active: !user.value.active,
is_admin: user.value.is_admin
}
})
}
Expand Down
18 changes: 7 additions & 11 deletions web-app/packages/lib/src/common/permission_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export enum WorkspaceRole {
}

export enum ProjectRole {
none,
reader,
editor,
writer,
Expand All @@ -37,9 +36,10 @@ export type WorkspaceRoleName =
| 'admin'
| 'owner'

export type ProjectRoleName =
| Extract<WorkspaceRoleName, 'reader' | 'editor' | 'writer' | 'owner'>
| 'none'
export type ProjectRoleName = Extract<
WorkspaceRoleName,
'reader' | 'editor' | 'writer' | 'owner'
>

export type ProjectPermissionName = 'owner' | 'write' | 'edit' | 'read'

Expand All @@ -63,15 +63,13 @@ export const USER_ROLE_BY_NAME: Record<WorkspaceRoleName, WorkspaceRole> = {
}

export const PROJECT_ROLE_NAME_BY_ROLE: Record<ProjectRole, ProjectRoleName> = {
[ProjectRole.none]: 'none',
[ProjectRole.reader]: 'reader',
[ProjectRole.editor]: 'editor',
[ProjectRole.writer]: 'writer',
[ProjectRole.owner]: 'owner'
}

export const PROJECT_ROLE_BY_NAME: Record<ProjectRoleName, ProjectRole> = {
none: ProjectRole.none,
reader: ProjectRole.reader,
editor: ProjectRole.editor,
writer: ProjectRole.writer,
Expand Down Expand Up @@ -191,21 +189,19 @@ export function getProjectAccessKeyByRoleName(
owner: 'ownersnames',
writer: 'writersnames',
editor: 'editorsnames',
reader: 'readersnames',
none: undefined
reader: 'readersnames'
}
return mapper[roleName]
}

export function getProjectPermissionByRoleName(
roleName: ProjectRoleName
): ProjectPermissionName {
const mapper: Record<ProjectRoleName, ProjectPermissionName | undefined> = {
const mapper: Record<ProjectRoleName, ProjectPermissionName> = {
owner: 'owner',
writer: 'write',
editor: 'edit',
reader: 'read',
none: undefined
reader: 'read'
}
return mapper[roleName]
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export default defineComponent({
await this.acceptProjectAccessRequest({
data,
itemId: request.id,
namespace: this.namespace
workspace: this.namespace
})
await this.updatePaginationOrFetch()
} catch (err) {
Expand All @@ -200,7 +200,7 @@ export default defineComponent({
async cancelRequest(request) {
await this.cancelProjectAccessRequest({
itemId: request.id,
namespace: this.namespace
workspace: this.namespace
})
await this.updatePaginationOrFetch()
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export default defineComponent({
await this.acceptProjectAccessRequest({
data,
itemId: request.id,
namespace: this.project.namespace
workspace: this.project.namespace
})
await this.updatePaginationOrFetch()
} catch (err) {
Expand All @@ -173,7 +173,7 @@ export default defineComponent({
async cancelRequest(request) {
await this.cancelProjectAccessRequest({
itemId: request.id,
namespace: this.project.namespace
workspace: this.project.namespace
})
await this.updatePaginationOrFetch()
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
<template #col-roles="{ item }">
<AppDropdown
:options="roles"
:model-value="item.project_permission"
:model-value="item.role"
@update:model-value="(e) => roleUpdate(item, e)"
:disabled="item.id === loggedUser.id"
class="w-6 lg:w-full"
Expand Down Expand Up @@ -163,7 +163,7 @@ function removeMember(item: ProjectAccessDetail) {
function roleUpdate(item: ProjectAccessDetail, value: ProjectRoleName) {
projectStore.updateProjectAccess({
projectId: projectStore.project.id,
userId: item.id,
access: item,
data: { role: value }
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ export default defineComponent({
default: false
},
namespace: String,
asAdmin: {
type: Boolean,
default: false
},
public: {
type: Boolean,
default: true
Expand Down Expand Up @@ -136,9 +132,6 @@ export default defineComponent({
if (projectGridState.namespace) {
params.only_namespace = projectGridState.namespace
}
if (this.asAdmin) {
params.as_admin = true
}
if (!this.public) {
params.public = false
}
Expand Down
Loading