def delete(self, project_id, campaign_id): """ Delete a campaign for a project --- tags: - campaigns 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: Unique project ID required: true type: integer default: 1 - name: campaign_id in: path description: Unique campaign ID required: true type: integer default: 1 responses: 200: description: Campaign assigned successfully 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: ProjectAdminService.is_user_action_permitted_on_project( tm.authenticated_user_id, project_id) except ValueError as e: error_msg = f"ProjectsCampaignsAPI DELETE: {str(e)}" return {"Error": error_msg}, 403 try: CampaignService.delete_project_campaign(project_id, campaign_id) return {"Success": "Campaigns Deleted"}, 200 except NotFound: return {"Error": "Campaign Not Found"}, 404 except Exception as e: error_msg = f"ProjectsCampaignsAPI DELETE - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def delete(self, project_id): """ Deletes a Tasking-Manager project --- tags: - projects 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: Unique project ID required: true type: integer default: 1 responses: 200: description: Project deleted 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Project not found 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id ) except ValueError as e: error_msg = f"ProjectsRestAPI DELETE: {str(e)}" return {"Error": error_msg}, 403 try: ProjectAdminService.delete_project(project_id, authenticated_user_id) return {"Success": "Project deleted"}, 200 except ProjectAdminServiceError: return {"Error": "Project has some mapping"}, 403 except NotFound: return {"Error": "Project Not Found"}, 404 except Exception as e: error_msg = f"ProjectsRestAPI DELETE - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to delete project"}, 500
def post(self, project_id): """ Set a project as featured --- tags: - projects 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: Unique project ID required: true type: integer default: 1 responses: 200: description: Featured projects 400: description: Bad request 403: description: Forbidden 404: description: Project not found 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id) except ValueError as e: error_msg = f"FeaturedProjects POST: {str(e)}" return {"Error": error_msg}, 403 try: ProjectService.set_project_as_featured(project_id) return {"Success": True}, 200 except NotFound: return {"Error": "Project Not Found"}, 404 except ValueError as e: error_msg = f"FeaturedProjects POST: {str(e)}" return {"Error": error_msg}, 400 except Exception as e: error_msg = f"FeaturedProjects POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def post(self, project_id): """ Set all bad imagery tasks as ready for mapping --- 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: Unique project ID required: true type: integer default: 1 responses: 200: description: All bad imagery tasks marked ready for mapping 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id) except ValueError as e: error_msg = f"TasksActionsResetBadImageryAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: MappingService.reset_all_badimagery(project_id, authenticated_user_id) return { "Success": "All bad imagery tasks marked ready for mapping" }, 200 except Exception as e: error_msg = ( f"TasksActionsResetBadImageryAllAPI POST - unhandled error: {str(e)}" ) current_app.logger.critical(error_msg) return {"Error": "Unable to reset tasks"}, 500
def get(self, project_id): """ Retrieves a Tasking-Manager project --- tags: - projects 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: Unique project ID required: true type: integer default: 1 responses: 200: description: Project found 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Project not found 500: description: Internal Server Error """ try: ProjectAdminService.is_user_action_permitted_on_project( token_auth.current_user(), project_id ) except ValueError as e: error_msg = f"ProjectsQueriesNoTasksAPI GET: {str(e)}" return {"Error": error_msg}, 403 try: project_dto = ProjectAdminService.get_project_dto_for_admin(project_id) return project_dto.to_primitive(), 200 except NotFound: return {"Error": "Project Not Found"}, 404 except Exception as e: error_msg = f"Project GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def get_project_dto_for_mapper( project_id, current_user_id, locale="en", abbrev=False ) -> ProjectDTO: """ Get the project DTO for mappers :param project_id: ID of the Project mapper has requested :param locale: Locale the mapper has requested :raises ProjectServiceError, NotFound """ project = ProjectService.get_project_by_id(project_id) # if project is public and is not draft, we don't need to check permissions if not project.private and not project.status == ProjectStatus.DRAFT.value: return project.as_dto_for_mapping(current_user_id, locale, abbrev) is_allowed_user = True is_team_member = None is_manager_permission = False if current_user_id: is_manager_permission = ProjectAdminService.is_user_action_permitted_on_project( current_user_id, project_id ) # Draft Projects - admins, authors, org admins & team managers permitted if project.status == ProjectStatus.DRAFT.value: if not is_manager_permission: is_allowed_user = False raise ProjectServiceError("Unable to fetch project") # Private Projects - allowed_users, admins, org admins & # assigned teams (mappers, validators, project managers), authors permitted if project.private and not is_manager_permission: is_allowed_user = False if current_user_id: is_allowed_user = ( len( [ user for user in project.allowed_users if user.id == current_user_id ] ) > 0 ) if not (is_allowed_user or is_manager_permission): if current_user_id: allowed_roles = [ TeamRoles.MAPPER.value, TeamRoles.VALIDATOR.value, TeamRoles.PROJECT_MANAGER.value, ] is_team_member = TeamService.check_team_membership( project_id, allowed_roles, current_user_id ) if is_allowed_user or is_manager_permission or is_team_member: return project.as_dto_for_mapping(current_user_id, locale, abbrev) else: raise ProjectServiceError("Unable to fetch project")
def post(self, project_id): """ Reset all tasks on project back to ready, preserving history --- 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: Unique project ID required: true type: integer default: 1 responses: 200: description: All tasks reset 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: ProjectAdminService.is_user_action_permitted_on_project( tm.authenticated_user_id, project_id) except ValueError as e: error_msg = f"TasksActionsResetAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: ProjectAdminService.reset_all_tasks(project_id, tm.authenticated_user_id) return {"Success": "All tasks reset"}, 200 except Exception as e: error_msg = f"TasksActionsResetAllAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to reset tasks"}, 500
def is_user_permitted_to_validate(project_id, user_id): """ Check if the user is allowed to validate on the project in scope """ if UserService.is_user_blocked(user_id): return False, ValidatingNotAllowed.USER_NOT_ON_ALLOWED_LIST project = ProjectService.get_project_by_id(project_id) if project.license_id: if not UserService.has_user_accepted_license(user_id, project.license_id): return False, ValidatingNotAllowed.USER_NOT_ACCEPTED_LICENSE validation_permission = project.validation_permission # is_admin or is_author or is_org_manager or is_manager_team is_manager_permission = False if ProjectAdminService.is_user_action_permitted_on_project(user_id, project_id): is_manager_permission = True # Draft (public/private) accessible only for is_manager_permission if ( ProjectStatus(project.status) == ProjectStatus.DRAFT and not is_manager_permission ): return False, ValidatingNotAllowed.PROJECT_NOT_PUBLISHED is_restriction = None if not is_manager_permission and validation_permission: is_restriction = ProjectService.evaluate_validation_permission( project_id, user_id, validation_permission ) tasks = Task.get_locked_tasks_for_user(user_id) if len(tasks.locked_tasks) > 0: return False, ValidatingNotAllowed.USER_ALREADY_HAS_TASK_LOCKED is_allowed_user = None if project.private and not is_manager_permission: # Check if user is in allowed user list is_allowed_user = ProjectService.is_user_in_the_allowed_list( project.allowed_users, user_id ) if is_allowed_user: return True, "User allowed to validate" if not is_manager_permission and is_restriction: return is_restriction elif project.private and not ( is_manager_permission or is_allowed_user or not is_restriction ): return False, ValidatingNotAllowed.USER_NOT_ON_ALLOWED_LIST return True, "User allowed to validate"
def is_user_permitted_to_validate(project_id, user_id): """ Check if the user is allowed to validate on the project in scope """ if UserService.is_user_blocked(user_id): return False, ValidatingNotAllowed.USER_NOT_ON_ALLOWED_LIST project = ProjectService.get_project_by_id(project_id) if project.license_id: if not UserService.has_user_accepted_license(user_id, project.license_id): return False, ValidatingNotAllowed.USER_NOT_ACCEPTED_LICENSE validation_permission = project.validation_permission is_manager_permission = False # is_admin or is_author or is_org_manager or is_manager_team if ProjectAdminService.is_user_action_permitted_on_project(user_id, project_id): is_manager_permission = True if ( ProjectStatus(project.status) != ProjectStatus.PUBLISHED and not is_manager_permission ): return False, ValidatingNotAllowed.PROJECT_NOT_PUBLISHED tasks = Task.get_locked_tasks_for_user(user_id) if len(tasks.locked_tasks) > 0: return False, ValidatingNotAllowed.USER_ALREADY_HAS_TASK_LOCKED if project.private and not is_manager_permission: # Check user is in allowed users try: next(user for user in project.allowed_users if user.id == user_id) except StopIteration: return False, ValidatingNotAllowed.USER_NOT_ON_ALLOWED_LIST is_restriction = ProjectService.evaluate_validation_permission( project_id, user_id, validation_permission ) if is_restriction: return is_restriction if project.validation_permission and not is_manager_permission: is_restriction = ProjectService.evaluate_validation_permission( project_id, user_id, validation_permission ) if is_restriction: return is_restriction return True, "User allowed to validate"
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 is_user_permitted_to_map(project_id: int, user_id: int): """ Check if the user is allowed to map the on the project in scope """ if UserService.is_user_blocked(user_id): return False, MappingNotAllowed.USER_NOT_ON_ALLOWED_LIST project = ProjectService.get_project_by_id(project_id) mapping_permission = project.mapping_permission if ProjectStatus( project.status ) != ProjectStatus.PUBLISHED and not ProjectAdminService.is_user_action_permitted_on_project( user_id, project_id): return False, MappingNotAllowed.PROJECT_NOT_PUBLISHED tasks = Task.get_locked_tasks_for_user(user_id) if len(tasks.locked_tasks) > 0: return False, MappingNotAllowed.USER_ALREADY_HAS_TASK_LOCKED if project.private: # Check user is in allowed users try: next(user for user in project.allowed_users if user.id == user_id) except StopIteration: return False, MappingNotAllowed.USER_NOT_ON_ALLOWED_LIST is_restriction = ProjectService.evaluate_mapping_permission( project_id, user_id, mapping_permission) if is_restriction: return is_restriction if project.mapping_permission: is_restriction = ProjectService.evaluate_mapping_permission( project_id, user_id, mapping_permission) if is_restriction: return is_restriction if project.license_id: if not UserService.has_user_accepted_license( user_id, project.license_id): return False, MappingNotAllowed.USER_NOT_ACCEPTED_LICENSE return True, "User allowed to map"
def post(self, project_id, campaign_id): """ Assign a campaign for a project --- tags: - campaigns 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: Unique project ID required: true type: integer default: 1 - name: campaign_id in: path description: Unique campaign ID required: true type: integer default: 1 responses: 201: description: Campaign assigned successfully 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: ProjectAdminService.is_user_action_permitted_on_project( tm.authenticated_user_id, project_id) except ValueError as e: error_msg = f"ProjectsCampaignsAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: campaign_project_dto = CampaignProjectDTO() campaign_project_dto.campaign_id = campaign_id campaign_project_dto.project_id = project_id campaign_project_dto.validate() except DataError as e: current_app.logger.error(f"error validating request: {str(e)}") return str(e), 400 try: CampaignService.create_campaign_project(campaign_project_dto) message = "campaign with id {} assigned successfully for project with id {}".format( campaign_id, project_id) return ({"Success": message}, 200) except Exception as e: error_msg = f"ProjectsCampaignsAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def post(self, project_id): """ Send message to all contributors of a project --- tags: - projects 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: Unique project ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object for creating draft project schema: properties: subject: type: string default: Thanks required: true message: type: string default: Thanks for your contribution required: true responses: 200: description: Message sent successfully 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() message_dto = MessageDTO(request.get_json()) message_dto.from_user_id = authenticated_user_id message_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Unable to send message to mappers"}, 400 try: ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id) threading.Thread( target=MessageService.send_message_to_all_contributors, args=(project_id, message_dto), ).start() return {"Success": "Messages started"}, 200 except ValueError as e: return {"Error": str(e)}, 403 except Exception as e: error_msg = f"Send message all - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to send messages to mappers"}, 500
def post(self, project_id): """ Creates a relationship between project and interests --- tags: - interests 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: Unique project ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object for creating/updating project and interests relationships schema: properties: interests: type: array items: type: integer responses: 200: description: New project interest relationship created 400: description: Invalid Request 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: ProjectAdminService.is_user_action_permitted_on_project( token_auth.current_user(), project_id) except ValueError as e: error_msg = f"ProjectsActionsSetInterestsAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: data = request.get_json() project_interests = InterestService.create_or_update_project_interests( project_id, data["interests"]) return project_interests.to_primitive(), 200 except NotFound: return {"Error": "Project not Found"}, 404 except Exception as e: error_msg = ( f"ProjectsActionsSetInterestsAPI POST - unhandled error: {str(e)}" ) current_app.logger.critical(error_msg) return {"Error": error_msg}, 500
def patch(self, project_id): """ Updates a Tasking-Manager project --- tags: - projects 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: Unique project ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object for updating an existing project schema: properties: projectStatus: type: string default: DRAFT projectPriority: type: string default: MEDIUM defaultLocale: type: string default: en mapperLevel: type: string default: BEGINNER validation_permission: type: string default: ANY mapping_permission: type: string default: ANY private: type: boolean default: false changesetComment: type: string default: hotosm-project-1 dueDate: type: date default: "2017-04-11T12:38:49" imagery: type: string default: http//www.bing.com/maps/ josmPreset: type: string default: josm preset goes here mappingTypes: type: array items: type: string default: [BUILDINGS, ROADS] mappingEditors: type: array items: type: string default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS] validationEditors: type: array items: type: string default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS] campaign: type: string default: malaria organisation: type: integer default: 1 countryTag: type: array items: type: string default: [] licenseId: type: integer default: 1 description: Id of imagery license associated with the project allowedUsernames: type: array items: type: string default: ["Iain Hunter", LindaA1] priorityAreas: type: array items: schema: $ref: "#/definitions/GeoJsonPolygon" projectInfoLocales: type: array items: schema: $ref: "#/definitions/ProjectInfo" taskCreationMode: type: integer default: GRID responses: 200: description: Project updated 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Project not found 500: description: Internal Server Error """ authenticated_user_id = token_auth.current_user() try: ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id ) except ValueError as e: error_msg = f"ProjectsRestAPI PATCH: {str(e)}" return {"Error": error_msg}, 403 try: project_dto = ProjectDTO(request.get_json()) project_dto.project_id = project_id project_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Unable to update project"}, 400 try: ProjectAdminService.update_project(project_dto, authenticated_user_id) return {"Status": "Updated"}, 200 except InvalidGeoJson as e: return {"Invalid GeoJson": str(e)}, 400 except NotFound as e: return {"Error": str(e) or "Project Not Found"}, 404 except Exception as e: error_msg = f"ProjectsRestAPI PATCH - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to update project"}, 500