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 send_message_to_all_contributors(project_id: int, message_dto: MessageDTO): """ Sends supplied message to all contributors on specified project. Message all contributors 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(): contributors = Message.get_all_contributors(project_id) project_link = MessageService.get_project_link(project_id) message_dto.message = ( f"{project_link}<br/><br/>" + message_dto.message ) # Append project link to end of message messages = [] for contributor in contributors: message = Message.from_dto(contributor[0], message_dto) message.message_type = MessageType.BROADCAST.value message.project_id = project_id message.save() user = UserService.get_user_by_id(contributor[0]) messages.append(dict(message=message, user=user)) MessageService._push_messages(messages)
def update_stats_after_task_state_change( project_id: int, user_id: int, last_state: TaskStatus, new_state: TaskStatus, action="change", ): """ Update stats when a task has had a state change """ if new_state in [ TaskStatus.READY, TaskStatus.LOCKED_FOR_VALIDATION, TaskStatus.LOCKED_FOR_MAPPING, ]: return # No stats to record for these states project = ProjectService.get_project_by_id(project_id) user = UserService.get_user_by_id(user_id) project, user = StatsService._update_tasks_stats( project, user, last_state, new_state, action ) UserService.upsert_mapped_projects(user_id, project_id) project.last_updated = timestamp() # Transaction will be saved when task is saved return project, user
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 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_message_to_all_contributors(project_id: int, message_dto: MessageDTO): """Sends supplied message to all contributors on specified project. Message all contributors 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(): contributors = Message.get_all_contributors(project_id) message_dto.message = "A message from {} managers:<br/><br/>{}".format( MessageService.get_project_link(project_id), markdown(message_dto.message, output_format="html"), ) messages = [] for contributor in contributors: message = Message.from_dto(contributor[0], message_dto) message.message_type = MessageType.BROADCAST.value message.project_id = project_id user = UserService.get_user_by_id(contributor[0]) messages.append(dict(message=message, user=user)) MessageService._push_messages(messages)
def send_favorite_project_activities(user_id: int): current_app.logger.debug("Sending Favorite Project Activities") favorited_projects = UserService.get_projects_favorited(user_id) contributed_projects = UserService.get_projects_mapped(user_id) if contributed_projects is None: contributed_projects = [] for favorited_project in favorited_projects.favorited_projects: contributed_projects.append(favorited_project.project_id) recently_updated_projects = (Project.query.with_entities( Project.id, func.DATE(Project.last_updated).label("last_updated")).filter( Project.id.in_(contributed_projects)).filter( func.DATE(Project.last_updated) > datetime.date.today() - datetime.timedelta(days=300))) user = UserService.get_user_by_id(user_id) messages = [] for project in recently_updated_projects: activity_message = [] query_last_active_users = """ select distinct(user_id) from (select user_id from task_history where project_id = :project_id order by action_date desc limit 15 ) t """ last_active_users = db.engine.execute( text(query_last_active_users), project_id=project.id) for recent_user_id in last_active_users: recent_user_details = UserService.get_user_by_id( recent_user_id) user_profile_link = MessageService.get_user_profile_link( recent_user_details.username) activity_message.append(user_profile_link) activity_message = str(activity_message)[1:-1] project_link = MessageService.get_project_link(project.id) message = Message() message.message_type = MessageType.PROJECT_ACTIVITY_NOTIFICATION.value message.project_id = project.id message.to_user_id = user.id message.subject = ( "Recent activities from your contributed/favorited Projects") message.message = ( f"{activity_message} contributed to {project_link} recently") messages.append(dict(message=message, user=user)) MessageService._push_messages(messages)
def get_team_as_dto(team_id: int, user_id: int) -> TeamDTO: team = TeamService.get_team_by_id(team_id) if team is None: raise NotFound() team_dto = TeamDetailsDTO() team_dto.team_id = team.id team_dto.name = team.name team_dto.invite_only = team.invite_only team_dto.visibility = TeamVisibility(team.visibility).name team_dto.description = team.description team_dto.logo = team.organisation.logo team_dto.organisation = team.organisation.name team_dto.organisation_id = team.organisation.id if user_id != 0: if UserService.is_user_an_admin(user_id): team_dto.is_general_admin = True if OrganisationService.is_user_an_org_manager( team.organisation.id, user_id): team_dto.is_org_admin = True else: team_dto.is_general_admin = False team_dto.is_org_admin = False team_members = TeamService._get_team_members(team_id) for member in team_members: user = UserService.get_user_by_id(member.user_id) member_dto = TeamMembersDTO() member_dto.username = user.username member_dto.pictureUrl = user.picture_url member_dto.function = TeamMemberFunctions(member.function).name member_dto.picture_url = user.picture_url member_dto.active = member.active team_dto.members.append(member_dto) team_projects = TeamService.get_projects_by_team_id(team.id) for team_project in team_projects: project_team_dto = TeamProjectDTO() project_team_dto.project_name = team_project.name project_team_dto.project_id = team_project.project_id project_team_dto.role = TeamRoles(team_project.role).name team_dto.team_projects.append(project_team_dto) org_projects = OrganisationService.get_projects_by_organisation_id( team.organisation.id) for org_project in org_projects: org_project_dto = OrganisationProjectsDTO() org_project_dto.project_id = org_project.id org_project_dto.project_name = org_project.name team_dto.organisation_projects.append(org_project_dto) return team_dto
def create_or_update_user_interests(user_id, interests): user = UserService.get_user_by_id(user_id) user.create_or_update_interests(interests) # Return DTO. dto = InterestsListDTO() dto.interests = [i.as_dto() for i in user.interests] return dto
def _push_messages(messages): if len(messages) == 0: return messages_objs = [] for i, message in enumerate(messages): user = message.get("user") obj = message.get("message") # Store message in the database only if mentions option are disabled. if (user.mentions_notifications is False and obj.message_type == MessageType.MENTION_NOTIFICATION.value): messages_objs.append(obj) continue if (user.projects_notifications is False and obj.message_type == MessageType.PROJECT_ACTIVITY_NOTIFICATION.value): continue if (user.projects_notifications is False and obj.message_type == MessageType.BROADCAST.value): continue if (user.teams_notifications is False and obj.message_type == MessageType.TEAM_BROADCAST.value): messages_objs.append(obj) continue if user.comments_notifications is False and obj.message_type in ( MessageType.TASK_COMMENT_NOTIFICATION.value, MessageType.PROJECT_CHAT_NOTIFICATION.value, ): continue if user.tasks_notifications is False and obj.message_type in ( MessageType.VALIDATION_NOTIFICATION.value, MessageType.INVALIDATION_NOTIFICATION.value, ): messages_objs.append(obj) continue messages_objs.append(obj) SMTPService.send_email_alert( user.email_address, user.username, message["message"].id, UserService.get_user_by_id( message["message"].from_user_id).username, message["message"].project_id, message["message"].task_id, clean_html(message["message"].subject), message["message"].message, obj.message_type, ) if i + 1 % 10 == 0: time.sleep(0.5) # Flush messages to the database. if len(messages_objs) > 0: db.session.add_all(messages_objs) db.session.flush() db.session.commit()
def is_user_an_org_manager(organisation_id: int, user_id: int): """ Check that the user is an manager for the org """ org = Organisation.get(organisation_id) if org is None: raise NotFound() user = UserService.get_user_by_id(user_id) return user in org.managers
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 create_draft_project(draft_project_dto: DraftProjectDTO) -> int: """ Validates and then persists draft projects in the DB :param draft_project_dto: Draft Project DTO with data from API :raises InvalidGeoJson :returns ID of new draft project """ user_id = draft_project_dto.user_id is_admin = UserService.is_user_an_admin(user_id) user_orgs = OrganisationService.get_organisations_managed_by_user_as_dto( user_id) is_org_manager = len(user_orgs.organisations) > 0 # First things first, we need to validate that the author_id is a PM. issue #1715 if not (is_admin or is_org_manager): user = UserService.get_user_by_id(user_id) raise (ProjectAdminServiceError( f"User {user.username} is not permitted to create project")) # If we're cloning we'll copy all the project details from the clone, otherwise create brand new project if draft_project_dto.cloneFromProjectId: draft_project = Project.clone(draft_project_dto.cloneFromProjectId, user_id) else: draft_project = Project() org = OrganisationService.get_organisation_by_id( draft_project_dto.organisation) if org is None: raise NotFound("Organisation does not exist") draft_project_dto.organisation = org draft_project.create_draft_project(draft_project_dto) draft_project.set_project_aoi(draft_project_dto) # if arbitrary_tasks requested, create tasks from aoi otherwise use tasks in DTO if draft_project_dto.has_arbitrary_tasks: tasks = GridService.tasks_from_aoi_features( draft_project_dto.area_of_interest) draft_project.task_creation_mode = TaskCreationMode.ARBITRARY.value else: tasks = draft_project_dto.tasks ProjectAdminService._attach_tasks_to_project(draft_project, tasks) if draft_project_dto.cloneFromProjectId: draft_project.save() # Update the clone else: draft_project.create() # Create the new project draft_project.set_default_changeset_comment() draft_project.set_country_info() return draft_project.id
def send_message_after_validation( status: int, validated_by: int, mapped_by: int, task_id: int, project_id: int ): """ Sends mapper a notification after their task has been marked valid or invalid """ if validated_by == mapped_by: return # No need to send a notification if you've verified your own task user = UserService.get_user_by_id(mapped_by) text_template = get_txt_template( "invalidation_message_en.txt" if status == TaskStatus.INVALIDATED else "validation_message_en.txt" ) status_text = ( "marked invalid" if status == TaskStatus.INVALIDATED else "validated" ) task_link = MessageService.get_task_link(project_id, task_id) replace_list = [ ["[USERNAME]", user.username], ["[TASK_LINK]", task_link], ["[ORG_NAME]", current_app.config["ORG_NAME"]], ] text_template = template_var_replacing(text_template, replace_list) messages = [] validation_message = Message() validation_message.message_type = ( MessageType.INVALIDATION_NOTIFICATION.value if status == TaskStatus.INVALIDATED else MessageType.VALIDATION_NOTIFICATION.value ) validation_message.project_id = project_id validation_message.task_id = task_id validation_message.from_user_id = validated_by validation_message.to_user_id = mapped_by validation_message.subject = ( f"{task_link} mapped by you in Project {project_id} has been {status_text}" ) validation_message.message = text_template messages.append(dict(message=validation_message, user=user)) # For email alerts MessageService._push_messages(messages)
def send_message_after_validation( status: int, validated_by: int, mapped_by: int, task_id: int, project_id: int ): """ Sends mapper a notification after their task has been marked valid or invalid """ if validated_by == mapped_by: return # No need to send a message to yourself user = UserService.get_user_by_id(mapped_by) if user.validation_message is False: return # No need to send validation message if user.projects_notifications is False: return text_template = get_template( "invalidation_message_en.txt" if status == TaskStatus.INVALIDATED else "validation_message_en.txt" ) status_text = ( "marked invalid" if status == TaskStatus.INVALIDATED else "validated" ) task_link = MessageService.get_task_link(project_id, task_id) text_template = text_template.replace("[USERNAME]", user.username) text_template = text_template.replace("[TASK_LINK]", task_link) validation_message = Message() validation_message.message_type = ( MessageType.INVALIDATION_NOTIFICATION.value if status == TaskStatus.INVALIDATED else MessageType.VALIDATION_NOTIFICATION.value ) validation_message.project_id = project_id validation_message.task_id = task_id validation_message.from_user_id = validated_by validation_message.to_user_id = mapped_by validation_message.subject = f"Your mapping in Project {project_id} on {task_link} has just been {status_text}" validation_message.message = text_template validation_message.add_message() SMTPService.send_email_alert( user.email_address, user.username, validation_message.id )
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 send_message_after_comment(comment_from: int, comment: str, task_id: int, project_id: int): """ Will send a canned message to anyone @'d in a comment """ usernames = MessageService._parse_message_for_username( comment, project_id) if len(usernames) != 0: task_link = MessageService.get_task_link(project_id, task_id) messages = [] for username in usernames: try: user = UserService.get_user_by_username(username) except NotFound: continue # If we can't find the user, keep going no need to fail message = Message() message.message_type = MessageType.MENTION_NOTIFICATION.value message.project_id = project_id message.task_id = task_id message.from_user_id = comment_from message.to_user_id = user.id message.subject = f"You were mentioned in a comment in {task_link} of Project {project_id}" message.message = comment messages.append(dict(message=message, user=user)) MessageService._push_messages(messages) # Notify all contributors except the user that created the comment. results = (TaskHistory.query.with_entities( TaskHistory.user_id.distinct() ).filter(TaskHistory.project_id == project_id).filter( TaskHistory.task_id == task_id).filter( TaskHistory.user_id != comment_from).filter( TaskHistory.action == TaskAction.STATE_CHANGE.name).all()) contributed_users = [r[0] for r in results] if len(contributed_users) != 0: user_from = User.query.get(comment_from) if user_from is None: raise ValueError("Username not found") user_link = MessageService.get_user_link(user_from.username) task_link = MessageService.get_task_link(project_id, task_id) messages = [] for user_id in contributed_users: try: user = UserService.get_user_by_id(user_id) # if user was mentioned, a message has already been sent to them, # so we can skip if user.username in usernames: break except NotFound: continue # If we can't find the user, keep going no need to fail message = Message() message.message_type = MessageType.TASK_COMMENT_NOTIFICATION.value message.project_id = project_id message.from_user_id = comment_from message.task_id = task_id message.to_user_id = user.id message.subject = ( f"{user_link} left a comment in {task_link} of Project {project_id}" ) message.message = comment messages.append(dict(message=message, user=user)) MessageService._push_messages(messages)
def _filter_projects(search_dto: ProjectSearchDTO, user): """ Filters all projects based on criteria provided by user""" query = ProjectSearchService.create_search_query(user) query = query.join(ProjectInfo).filter( ProjectInfo.locale.in_([search_dto.preferred_locale, "en"])) project_status_array = [] if search_dto.project_statuses: for project_status in search_dto.project_statuses: project_status_array.append( ProjectStatus[project_status].value) query = query.filter(Project.status.in_(project_status_array)) else: if not search_dto.created_by: project_status_array = [ProjectStatus.PUBLISHED.value] query = query.filter(Project.status.in_(project_status_array)) if search_dto.interests: query = query.join( project_interests, project_interests.c.project_id == Project.id).filter( project_interests.c.interest_id.in_(search_dto.interests)) if search_dto.created_by: query = query.filter(Project.author_id == search_dto.created_by) if search_dto.mapped_by: projects_mapped = UserService.get_projects_mapped( search_dto.mapped_by) query = query.filter(Project.id.in_(projects_mapped)) if search_dto.favorited_by: user = UserService.get_user_by_id(search_dto.favorited_by) projects_favorited = user.favorites query = query.filter( Project.id.in_([project.id for project in projects_favorited])) if search_dto.mapper_level and search_dto.mapper_level.upper( ) != "ALL": query = query.filter(Project.mapper_level == MappingLevel[ search_dto.mapper_level].value) if search_dto.organisation_name: query = query.filter( Organisation.name == search_dto.organisation_name) if search_dto.organisation_id: query = query.filter(Organisation.id == search_dto.organisation_id) if search_dto.team_id: query = query.join(ProjectTeams, ProjectTeams.project_id == Project.id).filter( ProjectTeams.team_id == search_dto.team_id) if search_dto.campaign: query = query.join(Campaign, Project.campaign).group_by( Project.id, Campaign.name) query = query.filter(Campaign.name == search_dto.campaign) if search_dto.mapping_types: # Construct array of mapping types for query mapping_type_array = [] for mapping_type in search_dto.mapping_types: mapping_type_array.append(MappingTypes[mapping_type].value) query = query.filter( Project.mapping_types.contains(mapping_type_array)) if search_dto.text_search: # We construct an OR search, so any projects that contain or more of the search terms should be returned or_search = " | ".join( [x for x in search_dto.text_search.split(" ") if x != ""]) opts = [ ProjectInfo.text_searchable.match( or_search, postgresql_regconfig="english"), ProjectInfo.name.like(f"%{or_search}%"), ] try: opts.append(Project.id == int(search_dto.text_search)) except ValueError: pass query = query.filter(or_(*opts)) if search_dto.country: # Unnest country column array. sq = Project.query.with_entities( Project.id, func.unnest(Project.country).label("country")).subquery() query = query.filter( sq.c.country.ilike("%{}%".format( search_dto.country))).filter(Project.id == sq.c.id) order_by = search_dto.order_by if search_dto.order_by_type == "DESC": order_by = desc(search_dto.order_by) query = query.order_by(order_by).group_by(Project.id) if search_dto.managed_by and user.role != UserRole.ADMIN.value: team_projects = query.join(ProjectTeams).filter( ProjectTeams.role == TeamRoles.PROJECT_MANAGER.value, ProjectTeams.project_id == Project.id, ) user_orgs_list = OrganisationService.get_organisations_managed_by_user( search_dto.managed_by) orgs_managed = [org.id for org in user_orgs_list] org_projects = query.filter( Project.organisation_id.in_(orgs_managed)) query = org_projects.union(team_projects) all_results = [] if not search_dto.omit_map_results: all_results = query.all() paginated_results = query.paginate(search_dto.page, 14, True) return all_results, paginated_results
def get_all_teams( user_id: int = None, team_name_filter: str = None, team_role_filter: str = None, member_filter: int = None, member_request_filter: int = None, manager_filter: int = None, organisation_filter: int = None, ) -> TeamsListDTO: query = db.session.query(Team).outerjoin(TeamMembers).outerjoin( ProjectTeams) orgs_query = None is_admin = UserService.is_user_an_admin(user_id) if organisation_filter: orgs_query = query.filter( Team.organisation_id.in_(organisation_filter)) if manager_filter and not (manager_filter == user_id and is_admin): query = query.filter( TeamMembers.user_id == manager_filter, TeamMembers.active == True, # noqa TeamMembers.function == TeamMemberFunctions.MANAGER.value, ) if team_name_filter: query = query.filter(Team.name.contains(team_name_filter)) if team_role_filter: try: role = TeamRoles[team_role_filter.upper()].value query = query.filter(ProjectTeams.role == role) except KeyError: pass if member_filter: query = query.filter( TeamMembers.user_id == member_filter, TeamMembers.active == True # noqa ) if member_request_filter: query = query.filter( TeamMembers.user_id == member_request_filter, TeamMembers.active == False, # noqa ) if orgs_query: query = query.union(orgs_query) teams_list_dto = TeamsListDTO() for team in query.all(): team_dto = TeamDTO() team_dto.team_id = team.id team_dto.name = team.name team_dto.invite_only = team.invite_only team_dto.visibility = TeamVisibility(team.visibility).name team_dto.description = team.description team_dto.logo = team.organisation.logo team_dto.organisation = team.organisation.name team_dto.organisation_id = team.organisation.id team_dto.members = [] team_members = TeamService._get_team_members(team.id) is_team_manager = False is_team_member = False for member in team_members: user = UserService.get_user_by_id(member.user_id) member_dto = TeamMembersDTO() member_dto.username = user.username member_dto.function = TeamMemberFunctions(member.function).name if member.user_id == user_id: is_team_member = True if member_dto.function == "MANAGER": is_team_manager = True member_dto.picture_url = user.picture_url member_dto.active = member.active team_dto.members.append(member_dto) if team_dto.visibility == "PRIVATE" and not is_admin: if is_team_manager or is_team_member: teams_list_dto.teams.append(team_dto) else: teams_list_dto.teams.append(team_dto) return teams_list_dto
def get(self): """ List and search for projects --- tags: - projects produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token type: string default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting type: string required: true default: en - in: query name: mapperLevel type: string - in: query name: orderBy type: string default: priority enum: [id,mapper_level,priority,status,last_updated,due_date] - in: query name: orderByType type: string default: ASC enum: [ASC, DESC] - in: query name: mappingTypes type: string - in: query name: mappingTypesExact type: boolean default: false description: if true, limits projects to match the exact mapping types requested - in: query name: organisationName description: Organisation name to search for type: string - in: query name: organisationId description: Organisation ID to search for type: integer - in: query name: campaign description: Campaign name to search for type: string - in: query name: page description: Page of results user requested type: integer default: 1 - in: query name: textSearch description: Text to search type: string - in: query name: country description: Project country type: string - in: query name: action description: Filter projects by possible actions enum: [map, validate, any] type: string - in: query name: projectStatuses description: Authenticated PMs can search for archived or draft statuses type: string - in: query name: interests type: string description: Filter by interest on project default: null - in: query name: createdByMe description: Limit to projects created by the authenticated user type: boolean default: false - in: query name: mappedByMe description: Limit to projects mapped/validated by the authenticated user type: boolean default: false - in: query name: favoritedByMe description: Limit to projects favorited by the authenticated user type: boolean default: false - in: query name: managedByMe description: Limit to projects that can be managed by the authenticated user, excluding the ones created by them type: boolean default: false - in: query name: teamId type: string description: Filter by team on project default: null name: omitMapResults type: boolean description: If true, it will not return the project centroid's geometries. default: false responses: 200: description: Projects found 404: description: No projects found 500: description: Internal Server Error """ try: user = None user_id = token_auth.current_user() if user_id: user = UserService.get_user_by_id(user_id) search_dto = self.setup_search_dto() results_dto = ProjectSearchService.search_projects(search_dto, user) return results_dto.to_primitive(), 200 except NotFound: return {"mapResults": {}, "results": []}, 200 except Exception as e: error_msg = f"Project GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to fetch projects"}, 500
def get(self, user_id): """ Get a list of tasks a user has interacted with --- tags: - users produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: user_id in: path description: Mapper's OpenStreetMap ID required: true type: integer - in: query name: status description: Task Status filter required: false type: string default: null - in: query name: project_status description: Project Status filter required: false type: string default: null - in: query name: project_id description: Project id required: false type: integer default: null - in: query name: start_date description: Date to filter as minimum required: false type: string default: null - in: query name: end_date description: Date to filter as maximum required: false type: string default: null - in: query name: sort_by description: criteria to sort by. The supported options are action_date, -action_date. The default value is -action_date. required: false type: string - in: query name: page description: Page of results user requested type: integer - in: query name: page_size description: Size of page, defaults to 10 type: integer responses: 200: description: Mapped projects found 404: description: No mapped projects found 500: description: Internal Server Error """ try: user = UserService.get_user_by_id(user_id) status = request.args.get("status") project_status = request.args.get("project_status") project_id = int(request.args.get("project_id", 0)) start_date = ( date_parse(request.args.get("start_date")) if request.args.get("start_date") else None ) end_date = ( date_parse(request.args.get("end_date")) if request.args.get("end_date") else None ) sort_by = request.args.get("sort_by", "-action_date") tasks = UserService.get_tasks_dto( user.id, project_id=project_id, project_status=project_status, task_status=status, start_date=start_date, end_date=end_date, page=request.args.get("page", None, type=int), page_size=request.args.get("page_size", 10, type=int), sort_by=sort_by, ) return tasks.to_primitive(), 200 except ValueError: return {"tasks": [], "pagination": {"total": 0}}, 200 except NotFound: return {"Error": "User or tasks not found"}, 404 except Exception as e: error_msg = f"User GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def delete(self, project_id): """ Delete a list of tasks from a project --- tags: - tasks produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - in: body name: body required: true description: JSON object with a list of tasks to delete schema: properties: tasks: type: array items: type: integer default: [ 1, 2 ] responses: 200: description: Task(s) deleted 400: description: Bad request 403: description: Forbidden 404: description: Project or Task Not Found 500: description: Internal Server Error """ user_id = token_auth.current_user() user = UserService.get_user_by_id(user_id) if user.role != UserRole.ADMIN.value: return {"Error": "This endpoint action is restricted to ADMIN users."}, 403 tasks_ids = request.get_json().get("tasks") if tasks_ids is None: return {"Error": "Tasks ids not provided"}, 400 if type(tasks_ids) != list: return {"Error": "Tasks were not provided as a list"}, 400 try: ProjectService.delete_tasks(project_id, tasks_ids) return {"Success": "Task(s) deleted"}, 200 except NotFound as e: return {"Error": f"Project or Task Not Found: {e}"}, 404 except ProjectServiceError as e: return {"Error": str(e)}, 403 except Exception as e: current_app.logger.critical(e) return {"Error": "Unable to delete tasks"}, 500
def resend_email_validation(user_id: int): """ Resends the email validation email to the logged in user """ user = UserService.get_user_by_id(user_id) SMTPService.send_verification_email(user.email_address, user.username)
def send_message_after_chat(chat_from: int, chat: str, project_id: int): """ Send alert to user if they were @'d in a chat message """ # Because message-all run on background thread it needs it's own app context app = create_app() with app.app_context(): usernames = MessageService._parse_message_for_username( chat, project_id) if len(usernames) != 0: link = MessageService.get_project_link( project_id, include_chat_section=True) messages = [] for username in usernames: current_app.logger.debug(f"Searching for {username}") try: user = UserService.get_user_by_username(username) except NotFound: current_app.logger.error( f"Username {username} not found") continue # If we can't find the user, keep going no need to fail message = Message() message.message_type = MessageType.MENTION_NOTIFICATION.value message.project_id = project_id message.from_user_id = chat_from message.to_user_id = user.id message.subject = f"You were mentioned in {link} chat" message.message = chat messages.append(dict(message=message, user=user)) MessageService._push_messages(messages) query = """ select user_id from project_favorites where project_id = :project_id""" favorited_users_results = db.engine.execute(text(query), project_id=project_id) favorited_users = [r[0] for r in favorited_users_results] # Notify all contributors except the user that created the comment. contributed_users_results = (TaskHistory.query.with_entities( TaskHistory.user_id.distinct() ).filter(TaskHistory.project_id == project_id).filter( TaskHistory.user_id != chat_from).filter( TaskHistory.action == TaskAction.STATE_CHANGE.name).all()) contributed_users = [r[0] for r in contributed_users_results] users_to_notify = list(set(contributed_users + favorited_users)) if len(users_to_notify) != 0: from_user = User.query.get(chat_from) from_user_link = MessageService.get_user_link( from_user.username) project_link = MessageService.get_project_link( project_id, include_chat_section=True) messages = [] for user_id in users_to_notify: try: user = UserService.get_user_by_id(user_id) except NotFound: continue # If we can't find the user, keep going no need to fail message = Message() message.message_type = MessageType.PROJECT_CHAT_NOTIFICATION.value message.project_id = project_id message.from_user_id = chat_from message.to_user_id = user.id message.subject = ( f"{from_user_link} left a comment in {project_link}") message.message = chat messages.append(dict(message=message, user=user)) # it's important to keep that line inside the if to avoid duplicated emails MessageService._push_messages(messages)