diff --git a/server/mergin/sync/interfaces.py b/server/mergin/sync/interfaces.py index aa916b38..bb2e9843 100644 --- a/server/mergin/sync/interfaces.py +++ b/server/mergin/sync/interfaces.py @@ -188,6 +188,12 @@ def get_push_permission(self, changes: dict): """ pass + def get_email_receivers(self, project): + """ + Return list of members who should receive email notifications about project changes + """ + pass + class WorkspaceRole(Enum): GUEST = "guest" diff --git a/server/mergin/sync/private_api_controller.py b/server/mergin/sync/private_api_controller.py index 00d0a13d..643c274c 100644 --- a/server/mergin/sync/private_api_controller.py +++ b/server/mergin/sync/private_api_controller.py @@ -17,8 +17,6 @@ AccessRequest, ProjectRole, RequestStatus, - ProjectVersion, - ProjectUser, ) from .schemas import ( ProjectListSchema, @@ -64,16 +62,7 @@ def create_project_access_request(namespace, project_name): # noqa: E501 db.session.add(access_request) db.session.commit() # notify project owners - owners = ( - User.query.join(UserProfile, ProjectUser) - .filter( - ProjectUser.project_id == project.id, - ProjectUser.role == ProjectRole.OWNER.value, - User.verified_email, - UserProfile.receive_notifications, - ) - .all() - ) + owners = current_app.project_handler.get_email_receivers(project) for owner in owners: email_data = { "subject": "Project access requested", diff --git a/server/mergin/sync/project_handler.py b/server/mergin/sync/project_handler.py index 8bf17af8..8299935a 100644 --- a/server/mergin/sync/project_handler.py +++ b/server/mergin/sync/project_handler.py @@ -1,7 +1,30 @@ +from .models import ProjectRole, ProjectUser, Project from .interfaces import AbstractProjectHandler from .permissions import ProjectPermissions +from sqlalchemy import or_, and_ +from typing import List +from ..auth.models import User, UserProfile class ProjectHandler(AbstractProjectHandler): def get_push_permission(self, changes: dict): return ProjectPermissions.Upload + + def get_email_receivers(self, project: Project) -> List[User]: + return ( + User.query.join(UserProfile) + .outerjoin(ProjectUser, ProjectUser.user_id == User.id) + .filter( + or_( + and_( + ProjectUser.project_id == project.id, + ProjectUser.role == ProjectRole.OWNER.value, + ), + User.is_admin, + ), + User.active, + User.verified_email, + UserProfile.receive_notifications, + ) + .all() + ) diff --git a/server/mergin/tests/test_project_handler.py b/server/mergin/tests/test_project_handler.py index d44bc5f5..76040ca8 100644 --- a/server/mergin/tests/test_project_handler.py +++ b/server/mergin/tests/test_project_handler.py @@ -1,8 +1,53 @@ +from ..sync.models import Project, ProjectRole +from .utils import add_user, create_project, create_workspace from ..sync.project_handler import ProjectHandler from ..sync.permissions import ProjectPermissions +from ..auth.models import User +from ..app import db -def test_project_handler(): + +def test_project_permissions(client): project_handler = ProjectHandler() project_permission = project_handler.get_push_permission(None) assert project_permission == ProjectPermissions.Upload + + +def test_email_receivers(client): + project_handler = ProjectHandler() + # test project email receivers (owners and super admins) + workspace = create_workspace() + user = add_user() + project = create_project("test_project", workspace, user) + project.set_role(user.id, ProjectRole.READER) + db.session.commit() + receivers = project_handler.get_email_receivers(project) + assert len(receivers) == 1 + + project.set_role(user.id, ProjectRole.OWNER) + db.session.commit() + receivers = project_handler.get_email_receivers(project) + assert len(receivers) == 2 + + user.verified_email = False + db.session.commit() + receivers = project_handler.get_email_receivers(project) + assert len(receivers) == 1 + + user.verified_email = True + user.profile.receive_notifications = False + db.session.commit() + receivers = project_handler.get_email_receivers(project) + assert len(receivers) == 1 + + user.profile.receive_notifications = True + user.active = False + db.session.commit() + receivers = project_handler.get_email_receivers(project) + assert len(receivers) == 1 + + admin = User.query.filter(User.username == "mergin").first() + admin.is_admin = False + db.session.commit() + receivers = project_handler.get_email_receivers(project) + assert len(receivers) == 0 diff --git a/server/mergin/tests/utils.py b/server/mergin/tests/utils.py index 0d4d4f9c..0f768c6e 100644 --- a/server/mergin/tests/utils.py +++ b/server/mergin/tests/utils.py @@ -28,7 +28,7 @@ CHUNK_SIZE = 1024 -def add_user(username="random", password="random", is_admin=False): +def add_user(username="random", password="random", is_admin=False) -> User: """Helper function to create not-privileged user. Associated user workspace is created with db hook.