def accept_reject_join_request(team_id, from_user_id, username, function, action): from_user = UserService.get_user_by_id(from_user_id) to_user_id = UserService.get_user_by_username(username).id team = TeamService.get_team_by_id(team_id) MessageService.accept_reject_request_to_join_team( from_user_id, from_user.username, to_user_id, team.name, team_id, action) is_member = TeamService.is_user_team_member(team_id, to_user_id) if action == "accept": if is_member: TeamService.activate_team_member(team_id, to_user_id) else: TeamService.add_team_member( team_id, to_user_id, TeamMemberFunctions[function.upper()].value, True, ) elif action == "reject": if is_member: TeamService.delete_invite(team_id, to_user_id) else: raise TeamServiceError("Invalid action type")
def send_invite(team_id, from_user_id, username): to_user = UserService.get_user_by_username(username) from_user = UserService.get_user_by_id(from_user_id) team = TeamService.get_team_by_id(team_id) MessageService.send_invite_to_join_team(from_user_id, from_user.username, to_user.id, team.name, team_id)
def patch(self): """ Resends the verification email token to the logged in user --- tags: - users produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== responses: 200: description: Resends the user their email verification email 500: description: Internal Server Error """ try: MessageService.resend_email_validation(tm.authenticated_user_id) return {"Success": "Verification email resent"}, 200 except Exception as e: error_msg = f"User GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to send verification email"}, 500
def stop_validating_tasks( stop_validating_dto: StopValidationDTO) -> TaskDTOs: """ Unlocks supplied tasks after validation :raises ValidatorServiceError """ reset_tasks = stop_validating_dto.reset_tasks project_id = stop_validating_dto.project_id user_id = stop_validating_dto.user_id tasks_to_unlock = ValidatorService.get_tasks_locked_by_user( project_id, reset_tasks, user_id) dtos = [] for task_to_unlock in tasks_to_unlock: task = task_to_unlock["task"] if task_to_unlock["comment"]: # Parses comment to see if any users have been @'d MessageService.send_message_after_comment( user_id, task_to_unlock["comment"], task.id, project_id) task.reset_lock(user_id, task_to_unlock["comment"]) dtos.append( task.as_dto_with_instructions( stop_validating_dto.preferred_locale)) task_dtos = TaskDTOs() task_dtos.tasks = dtos return task_dtos
def unlock_task_after_mapping(mapped_task: MappedTaskDTO) -> TaskDTO: """ Unlocks the task and sets the task history appropriately """ task = MappingService.get_task_locked_by_user(mapped_task.project_id, mapped_task.task_id, mapped_task.user_id) new_state = TaskStatus[mapped_task.status.upper()] if new_state not in [ TaskStatus.MAPPED, TaskStatus.BADIMAGERY, TaskStatus.READY, ]: raise MappingServiceError( "Can only set status to MAPPED, BADIMAGERY, READY after mapping" ) # Update stats around the change of state last_state = TaskHistory.get_last_status(mapped_task.project_id, mapped_task.task_id, True) StatsService.update_stats_after_task_state_change( mapped_task.project_id, mapped_task.user_id, last_state, new_state) if mapped_task.comment: # Parses comment to see if any users have been @'d MessageService.send_message_after_comment( mapped_task.user_id, mapped_task.comment, task.id, mapped_task.project_id, ) task.unlock_task(mapped_task.user_id, new_state, mapped_task.comment) return task.as_dto_with_instructions(mapped_task.preferred_locale)
def send_message_to_all_team_members(team_id: int, team_name: str, message_dto: MessageDTO): """Sends supplied message to all contributors in a team. Message all team members can take over a minute to run, so this method is expected to be called on its own thread""" app = ( create_app() ) # Because message-all run on background thread it needs it's own app context with app.app_context(): team_members = TeamService._get_active_team_members(team_id) sender = UserService.get_user_by_id( message_dto.from_user_id).username message_dto.message = ( "A message from {}, manager of {} team:<br/><br/>{}".format( MessageService.get_user_profile_link(sender), MessageService.get_team_link(team_name, team_id, False), markdown(message_dto.message, output_format="html"), )) messages = [] for team_member in team_members: if team_member.user_id != message_dto.from_user_id: message = Message.from_dto(team_member.user_id, message_dto) message.message_type = MessageType.TEAM_BROADCAST.value message.save() user = UserService.get_user_by_id(team_member.user_id) messages.append(dict(message=message, user=user)) MessageService._push_messages(messages)
def test_welcome_message_sent(self): self.test_user = create_canned_user() # Act message_id = MessageService.send_welcome_message(self.test_user) self.assertIsNotNone(message_id) message = MessageService.get_message(message_id, self.test_user.id) # Assert self.assertTrue(message, "Message should be saved to DB") # Tidyup MessageService.delete_message(message_id, self.test_user.id)
def test_welcome_message_sent(self): if self.skip_tests: return # Act message_id = MessageService.send_welcome_message(self.test_user) message = MessageService.get_message(message_id, self.test_user.id) # Assert self.assertTrue(message, "Message should be saved to DB") # Tidyup MessageService.delete_message(message_id, self.test_user.id)
def stop_mapping_task(stop_task: StopMappingTaskDTO) -> TaskDTO: """ Unlocks the task and sets the task history appropriately """ task = MappingService.get_task_locked_by_user(stop_task.project_id, stop_task.task_id, stop_task.user_id) if stop_task.comment: # Parses comment to see if any users have been @'d MessageService.send_message_after_comment(stop_task.user_id, stop_task.comment, task.id, stop_task.project_id) task.reset_lock(stop_task.user_id, stop_task.comment) return task.as_dto_with_instructions(stop_task.preferred_locale)
def add_task_comment(task_comment: TaskCommentDTO) -> TaskDTO: """ Adds the comment to the task history """ task = Task.get(task_comment.task_id, task_comment.project_id) if task is None: raise MappingServiceError(f"Task {task_comment.task_id} not found") task.set_task_history(TaskAction.COMMENT, task_comment.user_id, task_comment.comment) # Parse comment to see if any users have been @'d MessageService.send_message_after_comment(task_comment.user_id, task_comment.comment, task.id, task_comment.project_id) task.update() return task.as_dto_with_instructions(task_comment.preferred_locale)
def get(self): """ Gets count of unread messages --- tags: - notifications produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== responses: 200: description: Message info 500: description: Internal Server Error """ try: unread_count = MessageService.has_user_new_messages( token_auth.current_user() ) return unread_count, 200 except Exception as e: error_msg = f"User GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to fetch messages count"}, 500
def test_message_service_generates_correct_chat_link(self): # Act link = MessageService.get_project_link(1, "http://test.com") self.assertEqual( link, '<a href="http://test.com/projects/1#questionsAndComments">Project 1</a>', )
def test_message_service_generates_correct_task_link(self): # Act link = MessageService.get_task_link(1, 1, "http://test.com") # Assert self.assertEqual( link, '<a href="http://test.com/projects/1/tasks/?search=1">Task 1</a>')
def test_message_service_identifies_all_users(self): # Act usernames = MessageService._parse_message_for_username( 'Hello @[Iain Hunter] and "[LindaA1]') # Assert self.assertEqual(usernames[0], "Iain Hunter") self.assertEqual(usernames[1], "LindaA1")
def post_message(chat_dto: ChatMessageDTO, project_id: int, authenticated_user_id: int) -> ProjectChatDTO: """ Save message to DB and return latest chat""" current_app.logger.debug("Posting Chat Message") if UserService.is_user_blocked(authenticated_user_id): raise ValueError("User is on read only mode") project = ProjectService.get_project_by_id(project_id) is_allowed_user = True is_manager_permission = ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id) is_team_member = False # Draft (public/private) accessible only for is_manager_permission if (ProjectStatus(project.status) == ProjectStatus.DRAFT and not is_manager_permission): raise ValueError("User not permitted to post Comment") if project.private: is_allowed_user = False if not is_manager_permission: allowed_roles = [ TeamRoles.PROJECT_MANAGER.value, TeamRoles.VALIDATOR.value, TeamRoles.MAPPER.value, ] is_team_member = TeamService.check_team_membership( project_id, allowed_roles, authenticated_user_id) if not is_team_member: is_allowed_user = (len([ user for user in project.allowed_users if user.id == authenticated_user_id ]) > 0) if is_manager_permission or is_team_member or is_allowed_user: chat_message = ProjectChat.create_from_dto(chat_dto) MessageService.send_message_after_chat(chat_dto.user_id, chat_message.message, chat_dto.project_id) db.session.commit() # Ensure we return latest messages after post return ProjectChat.get_messages(chat_dto.project_id, 1) else: raise ValueError("User not permitted to post Comment")
def join_team(team_id: int, requesting_user: int, username: str, role: str = None): is_manager = TeamService.is_user_team_manager(team_id, requesting_user) team = TeamService.get_team_by_id(team_id) user = UserService.get_user_by_username(username) if TeamService.is_user_team_member(team.id, user.id): raise TeamJoinNotAllowed( "User is already a member of this team or has already requested to join" ) if is_manager: if role: try: role = TeamMemberFunctions[role.upper()].value except KeyError: raise Exception("Invalid TeamMemberFunction") else: role = TeamMemberFunctions.MEMBER.value TeamService.add_team_member(team_id, user.id, role, True) else: if user.id != requesting_user: raise TeamJoinNotAllowed("User not allowed to join team") role = TeamMemberFunctions.MEMBER.value # active if the team is open if team.invite_only: active = False else: active = True TeamService.add_team_member(team_id, user.id, role, active) if team.invite_only: team_managers = team.get_team_managers() for member in team_managers: MessageService.send_request_to_join_team( user.id, user.username, member.user_id, team.name, team_id)
def delete(self, message_id): """ Deletes the specified message --- tags: - notifications produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: message_id in: path description: The unique message required: true type: integer default: 1 responses: 200: description: Messages found 403: description: Forbidden, if user attempting to ready other messages 404: description: Not found 500: description: Internal Server Error """ try: MessageService.delete_message(message_id, token_auth.current_user()) return {"Success": "Message deleted"}, 200 except MessageServiceError: return {"Error": "Unable to delete message"}, 403 except NotFound: return {"Error": "No messages found"}, 404 except Exception as e: error_msg = f"Messages GET all - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to delete message"}, 500
def delete(self): """ Delete specified messages for logged in user --- tags: - notifications produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - in: body name: body required: true description: JSON object containing message ids to delete schema: properties: messageIds: type: array items: integer required: true responses: 200: description: Messages deleted 500: description: Internal Server Error """ try: message_ids = request.get_json()["messageIds"] if message_ids: MessageService.delete_multiple_messages( message_ids, tm.authenticated_user_id) return {"Success": "Messages deleted"}, 200 except Exception as e: error_msg = f"DeleteMultipleMessages - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to delete messages"}, 500
def accept_reject_invitation_request(team_id, from_user_id, username, function, action): from_user = UserService.get_user_by_id(from_user_id) to_user = UserService.get_user_by_username(username) team = TeamService.get_team_by_id(team_id) team_members = TeamService._get_team_managers(team_id) for member in team_members: MessageService.accept_reject_invitation_request_for_team( from_user_id, from_user.username, member.user_id, to_user.username, team.name, action, ) if action == "accept": TeamService.add_team_member( team_id, from_user_id, TeamMemberFunctions[function.upper()].value)
def login_user(osm_user_details, email, user_element="user") -> str: """ Generates authentication details for user, creating in DB if user is unknown to us :param osm_user_details: XML response from OSM :param redirect_to: Route to redirect user to, from callback url :param user_element: Exists for unit testing :raises AuthServiceError :returns Authorized URL with authentication details in query string """ osm_user = osm_user_details.find(user_element) if osm_user is None: raise AuthServiceError("User element not found in OSM response") osm_id = int(osm_user.attrib["id"]) username = osm_user.attrib["display_name"] try: # get gravatar profile picture file name user_picture = osm_user.find("img").attrib["href"] except (AttributeError, IndexError): user_picture = None try: UserService.get_user_by_id(osm_id) UserService.update_user(osm_id, username, user_picture) except NotFound: # User not found, so must be new user changesets = osm_user.find("changesets") changeset_count = int(changesets.attrib["count"]) new_user = UserService.register_user(osm_id, username, changeset_count, user_picture, email) MessageService.send_welcome_message(new_user) session_token = AuthenticationService.generate_session_token_for_user( osm_id) return { "username": username, "session_token": session_token, "picture": user_picture, }
def post_message( chat_dto: ChatMessageDTO, project_id: int, authenticated_user_id: int ) -> ProjectChatDTO: """ Save message to DB and return latest chat""" current_app.logger.debug("Posting Chat Message") if UserService.is_user_blocked(authenticated_user_id): return ValueError("User is on read only mode") project = ProjectService.get_project_by_id(project_id) if project.private: author_id = project.author_id allowed_roles = [ TeamRoles.PROJECT_MANAGER.value, TeamRoles.VALIDATOR.value, TeamRoles.MAPPER.value, ] is_admin = UserService.is_user_an_admin(authenticated_user_id) is_author = UserService.is_user_the_project_author( authenticated_user_id, author_id ) is_org_manager = False if hasattr(project, "organisation_id") and project.organisation_id: org_id = project.organisation_id org = OrganisationService.get_organisation_by_id_as_dto( org_id, authenticated_user_id ) if org.is_manager: is_org_manager = True is_team_member = None if hasattr(project, "project_teams") and project.project_teams: teams_dto = TeamService.get_project_teams_as_dto(project_id) if teams_dto.teams: teams_allowed = [ team_dto for team_dto in teams_dto.teams if team_dto.role in allowed_roles ] user_membership = [ team_dto.team_id for team_dto in teams_allowed if TeamService.is_user_member_of_team( team_dto.team_id, authenticated_user_id ) ] if user_membership: is_team_member = True for user in project.allowed_users: if user.id == authenticated_user_id: is_allowed_user = True break if ( is_admin or is_author or is_org_manager or is_team_member or is_allowed_user ): chat_message = ProjectChat.create_from_dto(chat_dto) MessageService.send_message_after_chat( chat_dto.user_id, chat_message.message, chat_dto.project_id ) db.session.commit() # Ensure we return latest messages after post return ProjectChat.get_messages(chat_dto.project_id, 1) else: raise ValueError("User not permitted to post Comment") else: chat_message = ProjectChat.create_from_dto(chat_dto) MessageService.send_message_after_chat( chat_dto.user_id, chat_message.message, chat_dto.project_id ) db.session.commit() # Ensure we return latest messages after post return ProjectChat.get_messages(chat_dto.project_id, 1)
def get(self): """ Get all messages for logged in user --- tags: - notifications produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - in: query name: messageType type: string description: Optional message-type filter; leave blank to retrieve all default: 1,2 - in: query name: from description: Optional from username filter type: string - in: query name: project description: Optional project filter type: string - in: query name: taskId description: Optional task filter type: integer - in: query name: sortBy description: field to sort by, defaults to date type: string - in: query name: sortDirection description: direction of sort, defaults to desc type: string - in: query name: page description: Page of results user requested type: integer - in: query name: pageSize description: Size of page, defaults to 10 type: integer responses: 200: description: Messages found 404: description: User has no messages 500: description: Internal Server Error """ try: preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") page = request.args.get("page", 1, int) page_size = request.args.get("pageSize", 10, int) sort_by = request.args.get("sortBy", "date") sort_direction = request.args.get("sortDirection", "desc") message_type = request.args.get("messageType", None) from_username = request.args.get("from") project = request.args.get("project", None, int) task_id = request.args.get("taskId", None, int) user_messages = MessageService.get_all_messages( token_auth.current_user(), preferred_locale, page, page_size, sort_by, sort_direction, message_type, from_username, project, task_id, ) return user_messages.to_primitive(), 200 except Exception as e: error_msg = f"Messages GET all - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to fetch messages"}, 500
def unlock_tasks_after_validation( validated_dto: UnlockAfterValidationDTO, ) -> TaskDTOs: """ Unlocks supplied tasks after validation :raises ValidatorServiceError """ validated_tasks = validated_dto.validated_tasks project_id = validated_dto.project_id user_id = validated_dto.user_id tasks_to_unlock = ValidatorService.get_tasks_locked_by_user( project_id, validated_tasks, user_id) # Unlock all tasks dtos = [] message_sent_to = [] for task_to_unlock in tasks_to_unlock: task = task_to_unlock["task"] if task_to_unlock["comment"]: # Parses comment to see if any users have been @'d MessageService.send_message_after_comment( validated_dto.user_id, task_to_unlock["comment"], task.id, validated_dto.project_id, ) if (task_to_unlock["new_state"] == TaskStatus.VALIDATED or task_to_unlock["new_state"] == TaskStatus.INVALIDATED): # All mappers get a notification if their task has been validated or invalidated. # Only once if multiple tasks mapped if task.mapped_by not in message_sent_to: MessageService.send_message_after_validation( task_to_unlock["new_state"], validated_dto.user_id, task.mapped_by, task.id, validated_dto.project_id, ) message_sent_to.append(task.mapped_by) if task_to_unlock["new_state"] == TaskStatus.VALIDATED: # Set last_validation_date for the mapper to current date task.mapper.last_validation_date = timestamp() # Update stats if user setting task to a different state from previous state prev_status = TaskHistory.get_last_status(project_id, task.id) if prev_status != task_to_unlock["new_state"]: StatsService.update_stats_after_task_state_change( validated_dto.project_id, validated_dto.user_id, prev_status, task_to_unlock["new_state"], ) task_mapping_issues = ValidatorService.get_task_mapping_issues( task_to_unlock) task.unlock_task( validated_dto.user_id, task_to_unlock["new_state"], task_to_unlock["comment"], issues=task_mapping_issues, ) dtos.append( task.as_dto_with_instructions(validated_dto.preferred_locale)) task_dtos = TaskDTOs() task_dtos.tasks = dtos return task_dtos