diff --git a/server/mergin/sync/public_api.yaml b/server/mergin/sync/public_api.yaml index 48d39c13..c8072ad6 100644 --- a/server/mergin/sync/public_api.yaml +++ b/server/mergin/sync/public_api.yaml @@ -1044,6 +1044,23 @@ components: detail: Maximum number of people in this workspace is reached. Please upgrade your subscription to add more people (UsersLimitHit) rejected_emails: [ rejected@example.com ] users_quota: 6 + EditorsLimitHit: + allOf: + - $ref: '#/components/schemas/CustomError' + type: object + properties: + rejected_emails: + nullable: true + type: array + items: + type: string + editors_quota: + type: integer + example: + code: EditorsLimitHit + detail: Maximum number of editors in this workspace is reached. Please upgrade your subscription to add more (EditorsLimitHit) + rejected_emails: [ rejected@example.com ] + editors_quota: 6 UpdateProjectAccessError: allOf: - $ref: '#/components/schemas/CustomError' diff --git a/server/mergin/sync/public_api_controller.py b/server/mergin/sync/public_api_controller.py index 77faeec2..6a39e441 100644 --- a/server/mergin/sync/public_api_controller.py +++ b/server/mergin/sync/public_api_controller.py @@ -680,11 +680,34 @@ def update_project(namespace, project_name): # noqa: E501 # pylint: disable=W0 """ project = require_project(namespace, project_name, ProjectPermissions.Update) parsed_access = parse_project_access_update_request(request.json.get("access", {})) + + # get current status for easier rollback + modified_user_ids = [] + for role in list(ProjectRole.__reversed__()): + modified_user_ids.extend(parsed_access.get(role, [])) + current_permissions_map = { + user_id: project.get_role(user_id) for user_id in modified_user_ids + } + # get set of modified user_ids and possible (custom) errors id_diffs, error = current_app.ws_handler.update_project_members( project, parsed_access ) + # revert back rejected changes + if error and hasattr(error, "rejected_emails"): + rejected_users = ( + db.session.query(User.id) + .filter(User.email.in_(error.rejected_emails)) + .all() + ) + for user in rejected_users: + if current_permissions_map[user.id] is None: + project.unset_role(user.id) + else: + project.set_role(user.id, current_permissions_map[user.id]) + db.session.commit() + if not id_diffs and error: # nothing was done but there are errors return jsonify(error.to_dict()), 422