diff --git a/backend/api/annotations/resources.py b/backend/api/annotations/resources.py index 3bcb6dc806..9505b066c6 100644 --- a/backend/api/annotations/resources.py +++ b/backend/api/annotations/resources.py @@ -2,7 +2,7 @@ from schematics.exceptions import DataError from backend.models.postgis.task import Task from backend.models.postgis.task_annotation import TaskAnnotation -from backend.services.project_service import ProjectService, NotFound +from backend.services.project_service import ProjectService from backend.services.task_annotations_service import TaskAnnotationsService from backend.services.application_service import ApplicationService @@ -35,24 +35,14 @@ def get(self, project_id: int, annotation_type: str = None): 500: description: Internal Server Error """ - try: - ProjectService.exists(project_id) - except NotFound as e: - current_app.logger.error(f"Error validating project: {str(e)}") - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 - - try: - if annotation_type: - annotations = TaskAnnotation.get_task_annotations_by_project_id_type( - project_id, annotation_type - ) - else: - annotations = TaskAnnotation.get_task_annotations_by_project_id( - project_id - ) - return annotations.to_primitive(), 200 - except NotFound: - return {"Error": "Annotations not found", "SubCode": "NotFound"}, 404 + ProjectService.exists(project_id) + if annotation_type: + annotations = TaskAnnotation.get_task_annotations_by_project_id_type( + project_id, annotation_type + ) + else: + annotations = TaskAnnotation.get_task_annotations_by_project_id(project_id) + return annotations.to_primitive(), 200 def post(self, project_id: int, annotation_type: str): """ @@ -122,13 +112,7 @@ def post(self, project_id: int, annotation_type: str): if "Application-Token" in request.headers: application_token = request.headers["Application-Token"] - try: - is_valid_token = ApplicationService.check_token( # noqa - application_token - ) - except NotFound: - current_app.logger.error("Invalid token") - return {"Error": "Invalid token", "SubCode": "NotFound"}, 500 + is_valid_token = ApplicationService.check_token(application_token) # noqa else: current_app.logger.error("No token supplied") return {"Error": "No token supplied", "SubCode": "NotFound"}, 500 @@ -138,11 +122,7 @@ def post(self, project_id: int, annotation_type: str): except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") - try: - ProjectService.exists(project_id) - except NotFound as e: - current_app.logger.error(f"Error validating project: {str(e)}") - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 + ProjectService.exists(project_id) task_ids = [t["taskId"] for t in annotations["tasks"]] diff --git a/backend/api/campaigns/resources.py b/backend/api/campaigns/resources.py index a7e82bcd27..466090ce00 100644 --- a/backend/api/campaigns/resources.py +++ b/backend/api/campaigns/resources.py @@ -4,7 +4,6 @@ from backend.models.dtos.campaign_dto import CampaignDTO, NewCampaignDTO from backend.services.campaign_service import CampaignService from backend.services.organisation_service import OrganisationService -from backend.models.postgis.utils import NotFound from backend.services.users.authentication_service import token_auth @@ -43,17 +42,14 @@ def get(self, campaign_id): 500: description: Internal Server Error """ - try: - authenticated_user_id = token_auth.current_user() - if authenticated_user_id: - campaign = CampaignService.get_campaign_as_dto( - campaign_id, authenticated_user_id - ) - else: - campaign = CampaignService.get_campaign_as_dto(campaign_id, 0) - return campaign.to_primitive(), 200 - except NotFound: - return {"Error": "No campaign found", "SubCode": "NotFound"}, 404 + authenticated_user_id = token_auth.current_user() + if authenticated_user_id: + campaign = CampaignService.get_campaign_as_dto( + campaign_id, authenticated_user_id + ) + else: + campaign = CampaignService.get_campaign_as_dto(campaign_id, 0) + return campaign.to_primitive(), 200 @token_auth.login_required def patch(self, campaign_id): @@ -139,8 +135,6 @@ def patch(self, campaign_id): try: campaign = CampaignService.update_campaign(campaign_dto, campaign_id) return {"Success": "Campaign {} updated".format(campaign.id)}, 200 - except NotFound: - return {"Error": "Campaign not found", "SubCode": "NotFound"}, 404 except ValueError: error_msg = "Campaign PATCH - name already exists" return {"Error": error_msg, "SubCode": "NameExists"}, 409 @@ -195,12 +189,9 @@ def delete(self, campaign_id): error_msg = f"CampaignsRestAPI DELETE: {str(e)}" return {"Error": error_msg, "SubCode": "UserNotPermitted"}, 403 - try: - campaign = CampaignService.get_campaign(campaign_id) - CampaignService.delete_campaign(campaign.id) - return {"Success": "Campaign deleted"}, 200 - except NotFound: - return {"Error": "Campaign not found", "SubCode": "NotFound"}, 404 + campaign = CampaignService.get_campaign(campaign_id) + CampaignService.delete_campaign(campaign.id) + return {"Success": "Campaign deleted"}, 200 class CampaignsAllAPI(Resource): diff --git a/backend/api/comments/resources.py b/backend/api/comments/resources.py index 616ae85733..0e031360ef 100644 --- a/backend/api/comments/resources.py +++ b/backend/api/comments/resources.py @@ -3,7 +3,6 @@ from backend.models.dtos.message_dto import ChatMessageDTO from backend.models.dtos.mapping_dto import TaskCommentDTO -from backend.models.postgis.utils import NotFound from backend.services.messaging.chat_service import ChatService from backend.services.users.user_service import UserService from backend.services.project_service import ProjectService @@ -109,19 +108,11 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - ProjectService.exists(project_id) - except NotFound as e: - current_app.logger.error(f"Error validating project: {str(e)}") - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 - - try: - page = int(request.args.get("page")) if request.args.get("page") else 1 - per_page = int(request.args.get("perPage", 20)) - project_messages = ChatService.get_messages(project_id, page, per_page) - return project_messages.to_primitive(), 200 - except NotFound: - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 + ProjectService.exists(project_id) + page = int(request.args.get("page")) if request.args.get("page") else 1 + per_page = int(request.args.get("perPage", 20)) + project_messages = ChatService.get_messages(project_id, page, per_page) + return project_messages.to_primitive(), 200 class CommentsProjectsRestAPI(Resource): @@ -169,8 +160,6 @@ def delete(self, project_id, comment_id): project_id, comment_id, authenticated_user_id ) return {"Success": "Comment deleted"}, 200 - except NotFound: - return {"Error": "Comment not found", "SubCode": "NotFound"}, 404 except ValueError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 @@ -248,8 +237,6 @@ def post(self, project_id, task_id): try: task = MappingService.add_task_comment(task_comment) return task.to_primitive(), 201 - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 except MappingServiceError: return {"Error": "Task update failed"}, 403 @@ -320,7 +307,5 @@ def get(self, project_id, task_id): # task = MappingService.add_task_comment(task_comment) # return task.to_primitive(), 200 return - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 except MappingServiceError as e: return {"Error": str(e)}, 403 diff --git a/backend/api/interests/resources.py b/backend/api/interests/resources.py index 27bbb86b64..d1c0a0e1c6 100644 --- a/backend/api/interests/resources.py +++ b/backend/api/interests/resources.py @@ -2,7 +2,6 @@ from schematics.exceptions import DataError from backend.models.dtos.interests_dto import InterestDTO -from backend.models.postgis.utils import NotFound from backend.services.interests_service import InterestService from backend.services.organisation_service import OrganisationService from backend.services.users.authentication_service import token_auth @@ -144,11 +143,8 @@ def get(self, interest_id): error_msg = f"InterestsRestAPI GET: {str(e)}" return {"Error": error_msg, "SubCode": "UserNotPermitted"}, 403 - try: - interest = InterestService.get(interest_id) - return interest.to_primitive(), 200 - except NotFound: - return {"Error": INTEREST_NOT_FOUND, "SubCode": "NotFound"}, 404 + interest = InterestService.get(interest_id) + return interest.to_primitive(), 200 @token_auth.login_required def patch(self, interest_id): @@ -212,11 +208,8 @@ def patch(self, interest_id): current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": str(e), "SubCode": "InvalidData"}, 400 - try: - update_interest = InterestService.update(interest_id, interest_dto) - return update_interest.to_primitive(), 200 - except NotFound: - return {"Error": INTEREST_NOT_FOUND, "SubCode": "NotFound"}, 404 + update_interest = InterestService.update(interest_id, interest_dto) + return update_interest.to_primitive(), 200 @token_auth.login_required def delete(self, interest_id): @@ -262,8 +255,5 @@ def delete(self, interest_id): error_msg = f"InterestsRestAPI DELETE: {str(e)}" return {"Error": error_msg, "SubCode": "UserNotPermitted"}, 403 - try: - InterestService.delete(interest_id) - return {"Success": "Interest deleted"}, 200 - except NotFound: - return {"Error": INTEREST_NOT_FOUND, "SubCode": "NotFound"}, 404 + InterestService.delete(interest_id) + return {"Success": "Interest deleted"}, 200 diff --git a/backend/api/issues/resources.py b/backend/api/issues/resources.py index b6cb97dc0c..3a60deadf6 100644 --- a/backend/api/issues/resources.py +++ b/backend/api/issues/resources.py @@ -2,7 +2,6 @@ from schematics.exceptions import DataError from backend.models.dtos.mapping_issues_dto import MappingIssueCategoryDTO -from backend.models.postgis.utils import NotFound from backend.services.mapping_issues_service import MappingIssueCategoryService from backend.services.users.authentication_service import token_auth, tm @@ -33,18 +32,10 @@ def get(self, category_id): 500: description: Internal Server Error """ - try: - category_dto = ( - MappingIssueCategoryService.get_mapping_issue_category_as_dto( - category_id - ) - ) - return category_dto.to_primitive(), 200 - except NotFound: - return { - "Error": ISSUE_NOT_FOUND, - "SubCode": "NotFound", - }, 404 + category_dto = MappingIssueCategoryService.get_mapping_issue_category_as_dto( + category_id + ) + return category_dto.to_primitive(), 200 @tm.pm_only() @token_auth.login_required @@ -102,16 +93,10 @@ def patch(self, category_id): "SubCode": "InvalidData", }, 400 - try: - updated_category = ( - MappingIssueCategoryService.update_mapping_issue_category(category_dto) - ) - return updated_category.to_primitive(), 200 - except NotFound: - return { - "Error": ISSUE_NOT_FOUND, - "SubCode": "NotFound", - }, 404 + updated_category = MappingIssueCategoryService.update_mapping_issue_category( + category_dto + ) + return updated_category.to_primitive(), 200 @tm.pm_only() @token_auth.login_required @@ -149,14 +134,8 @@ def delete(self, category_id): 500: description: Internal Server Error """ - try: - MappingIssueCategoryService.delete_mapping_issue_category(category_id) - return {"Success": "Mapping-issue category deleted"}, 200 - except NotFound: - return { - "Error": ISSUE_NOT_FOUND, - "SubCode": "NotFound", - }, 404 + MappingIssueCategoryService.delete_mapping_issue_category(category_id) + return {"Success": "Mapping-issue category deleted"}, 200 class IssuesAllAPI(Resource): diff --git a/backend/api/licenses/actions.py b/backend/api/licenses/actions.py index fd7ed2c829..86ec6f8e20 100644 --- a/backend/api/licenses/actions.py +++ b/backend/api/licenses/actions.py @@ -1,7 +1,7 @@ from flask_restful import Resource from backend.services.users.authentication_service import token_auth -from backend.services.users.user_service import UserService, NotFound +from backend.services.users.user_service import UserService class LicensesActionsAcceptAPI(Resource): @@ -37,8 +37,5 @@ def post(self, license_id): 500: description: Internal Server Error """ - try: - UserService.accept_license_terms(token_auth.current_user(), license_id) - return {"Success": "Terms Accepted"}, 200 - except NotFound: - return {"Error": "User or License not found", "SubCode": "NotFound"}, 404 + UserService.accept_license_terms(token_auth.current_user(), license_id) + return {"Success": "Terms Accepted"}, 200 diff --git a/backend/api/licenses/resources.py b/backend/api/licenses/resources.py index 79cdc74715..4b3e78066d 100644 --- a/backend/api/licenses/resources.py +++ b/backend/api/licenses/resources.py @@ -2,7 +2,6 @@ from schematics.exceptions import DataError from backend.models.dtos.licenses_dto import LicenseDTO -from backend.models.postgis.utils import NotFound from backend.services.license_service import LicenseService from backend.services.users.authentication_service import token_auth, tm @@ -86,11 +85,8 @@ def get(self, license_id): 500: description: Internal Server Error """ - try: - license_dto = LicenseService.get_license_as_dto(license_id) - return license_dto.to_primitive(), 200 - except NotFound: - return {"Error": "License Not Found", "SubCode": "NotFound"}, 404 + license_dto = LicenseService.get_license_as_dto(license_id) + return license_dto.to_primitive(), 200 @tm.pm_only() @token_auth.login_required @@ -148,11 +144,8 @@ def patch(self, license_id): current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": str(e), "SubCode": "InvalidData"}, 400 - try: - updated_license = LicenseService.update_licence(license_dto) - return updated_license.to_primitive(), 200 - except NotFound: - return {"Error": "License Not Found", "SubCode": "NotFound"}, 404 + updated_license = LicenseService.update_licence(license_dto) + return updated_license.to_primitive(), 200 @tm.pm_only() @token_auth.login_required @@ -187,11 +180,8 @@ def delete(self, license_id): 500: description: Internal Server Error """ - try: - LicenseService.delete_license(license_id) - return {"Success": "License deleted"}, 200 - except NotFound: - return {"Error": "License Not Found", "SubCode": "NotFound"}, 404 + LicenseService.delete_license(license_id) + return {"Success": "License deleted"}, 200 class LicensesAllAPI(Resource): @@ -211,8 +201,5 @@ def get(self): 500: description: Internal Server Error """ - try: - licenses_dto = LicenseService.get_all_licenses() - return licenses_dto.to_primitive(), 200 - except NotFound: - return {"Error": "License Not Found", "SubCode": "NotFound"}, 404 + licenses_dto = LicenseService.get_all_licenses() + return licenses_dto.to_primitive(), 200 diff --git a/backend/api/notifications/resources.py b/backend/api/notifications/resources.py index 474c55472a..6575fad152 100644 --- a/backend/api/notifications/resources.py +++ b/backend/api/notifications/resources.py @@ -1,7 +1,6 @@ from flask_restful import Resource, request from backend.services.messaging.message_service import ( MessageService, - NotFound, MessageServiceError, ) from backend.services.notification_service import NotificationService @@ -49,8 +48,6 @@ def get(self, message_id): return user_message.to_primitive(), 200 except MessageServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "No messages found", "SubCode": "NotFound"}, 404 @tm.pm_only(False) @token_auth.login_required @@ -90,8 +87,6 @@ def delete(self, message_id): return {"Success": "Message deleted"}, 200 except MessageServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "No messages found", "SubCode": "NotFound"}, 404 class NotificationsAllAPI(Resource): diff --git a/backend/api/organisations/campaigns.py b/backend/api/organisations/campaigns.py index 1a4ff8dcf7..9abaf40440 100644 --- a/backend/api/organisations/campaigns.py +++ b/backend/api/organisations/campaigns.py @@ -2,7 +2,6 @@ from backend.services.campaign_service import CampaignService from backend.services.organisation_service import OrganisationService -from backend.models.postgis.utils import NotFound from backend.services.users.authentication_service import token_auth @@ -98,13 +97,8 @@ def get(self, organisation_id): 500: description: Internal Server Error """ - try: - campaigns = CampaignService.get_organisation_campaigns_as_dto( - organisation_id - ) - return campaigns.to_primitive(), 200 - except NotFound: - return {"Error": "No campaign found", "SubCode": "NotFound"}, 404 + campaigns = CampaignService.get_organisation_campaigns_as_dto(organisation_id) + return campaigns.to_primitive(), 200 @token_auth.login_required def delete(self, organisation_id, campaign_id): @@ -146,24 +140,16 @@ def delete(self, organisation_id, campaign_id): 500: description: Internal Server Error """ - try: - if OrganisationService.can_user_manage_organisation( - organisation_id, token_auth.current_user() - ): - CampaignService.delete_organisation_campaign( - organisation_id, campaign_id - ) - return ( - {"Success": "Organisation and campaign unassociated successfully"}, - 200, - ) - else: - return { - "Error": "User is not a manager of the organisation", - "SubCode": "UserNotPermitted", - }, 403 - except NotFound: + if OrganisationService.can_user_manage_organisation( + organisation_id, token_auth.current_user() + ): + CampaignService.delete_organisation_campaign(organisation_id, campaign_id) + return ( + {"Success": "Organisation and campaign unassociated successfully"}, + 200, + ) + else: return { - "Error": "Organisation Campaign Not Found", - "SubCode": "NotFound", - }, 404 + "Error": "User is not a manager of the organisation", + "SubCode": "UserNotPermitted", + }, 403 diff --git a/backend/api/organisations/resources.py b/backend/api/organisations/resources.py index de9fd99119..86e2badb0b 100644 --- a/backend/api/organisations/resources.py +++ b/backend/api/organisations/resources.py @@ -10,7 +10,6 @@ from backend.services.organisation_service import ( OrganisationService, OrganisationServiceError, - NotFound, ) from backend.models.postgis.statuses import OrganisationType from backend.services.users.authentication_service import token_auth @@ -51,20 +50,17 @@ def get(self, slug): 500: description: Internal Server Error """ - try: - authenticated_user_id = token_auth.current_user() - if authenticated_user_id is None: - user_id = 0 - else: - user_id = authenticated_user_id - # Validate abbreviated. - omit_managers = strtobool(request.args.get("omitManagerList", "false")) - organisation_dto = OrganisationService.get_organisation_by_slug_as_dto( - slug, user_id, omit_managers - ) - return organisation_dto.to_primitive(), 200 - except NotFound: - return {"Error": "Organisation Not Found", "SubCode": "NotFound"}, 404 + authenticated_user_id = token_auth.current_user() + if authenticated_user_id is None: + user_id = 0 + else: + user_id = authenticated_user_id + # Validate abbreviated. + omit_managers = strtobool(request.args.get("omitManagerList", "false")) + organisation_dto = OrganisationService.get_organisation_by_slug_as_dto( + slug, user_id, omit_managers + ) + return organisation_dto.to_primitive(), 200 class OrganisationsRestAPI(Resource): @@ -195,8 +191,6 @@ def delete(self, organisation_id): "Error": "Organisation has some projects", "SubCode": "OrgHasProjects", }, 403 - except NotFound: - return {"Error": "Organisation Not Found", "SubCode": "NotFound"}, 404 @token_auth.login_required(optional=True) def get(self, organisation_id): @@ -234,20 +228,17 @@ def get(self, organisation_id): 500: description: Internal Server Error """ - try: - authenticated_user_id = token_auth.current_user() - if authenticated_user_id is None: - user_id = 0 - else: - user_id = authenticated_user_id - # Validate abbreviated. - omit_managers = strtobool(request.args.get("omitManagerList", "false")) - organisation_dto = OrganisationService.get_organisation_by_id_as_dto( - organisation_id, user_id, omit_managers - ) - return organisation_dto.to_primitive(), 200 - except NotFound: - return {"Error": "Organisation Not Found", "SubCode": "NotFound"}, 404 + authenticated_user_id = token_auth.current_user() + if authenticated_user_id is None: + user_id = 0 + else: + user_id = authenticated_user_id + # Validate abbreviated. + omit_managers = strtobool(request.args.get("omitManagerList", "false")) + organisation_dto = OrganisationService.get_organisation_by_id_as_dto( + organisation_id, user_id, omit_managers + ) + return organisation_dto.to_primitive(), 200 @token_auth.login_required def patch(self, organisation_id): @@ -332,8 +323,6 @@ def patch(self, organisation_id): try: OrganisationService.update_organisation(organisation_dto) return {"Status": "Updated"}, 200 - except NotFound as e: - return {"Error": str(e), "SubCode": "NotFound"}, 404 except OrganisationServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 402 @@ -362,14 +351,11 @@ def get(self, organisation_id): 500: description: Internal Server Error """ - try: - OrganisationService.get_organisation_by_id(organisation_id) - organisation_dto = OrganisationService.get_organisation_stats( - organisation_id, None - ) - return organisation_dto.to_primitive(), 200 - except NotFound: - return {"Error": "Organisation Not Found", "SubCode": "NotFound"}, 404 + OrganisationService.get_organisation_by_id(organisation_id) + organisation_dto = OrganisationService.get_organisation_stats( + organisation_id, None + ) + return organisation_dto.to_primitive(), 200 class OrganisationsAllAPI(Resource): @@ -441,13 +427,10 @@ def get(self): omit_managers = bool(strtobool(request.args.get("omitManagerList", "false"))) omit_stats = bool(strtobool(request.args.get("omitOrgStats", "true"))) # Obtain organisations - try: - results_dto = OrganisationService.get_organisations_as_dto( - manager_user_id, - authenticated_user_id, - omit_managers, - omit_stats, - ) - return results_dto.to_primitive(), 200 - except NotFound: - return {"Error": "No organisations found", "SubCode": "NotFound"}, 404 + results_dto = OrganisationService.get_organisations_as_dto( + manager_user_id, + authenticated_user_id, + omit_managers, + omit_stats, + ) + return results_dto.to_primitive(), 200 diff --git a/backend/api/projects/actions.py b/backend/api/projects/actions.py index beee65873b..23bcb73e6f 100644 --- a/backend/api/projects/actions.py +++ b/backend/api/projects/actions.py @@ -5,7 +5,7 @@ from backend.models.dtos.message_dto import MessageDTO from backend.models.dtos.grid_dto import GridDTO -from backend.services.project_service import ProjectService, NotFound +from backend.services.project_service import ProjectService from backend.services.project_admin_service import ( ProjectAdminService, ProjectAdminServiceError, @@ -73,8 +73,6 @@ def post(self, project_id): return {"Success": "Project Transferred"}, 200 except (ValueError, ProjectAdminServiceError) as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "Project or user not found", "SubCode": "NotFound"}, 404 class ProjectsActionsMessageContributorsAPI(Resource): @@ -136,21 +134,18 @@ def post(self, project_id): "SubCode": "InvalidData", }, 400 - try: - if not ProjectAdminService.is_user_action_permitted_on_project( - authenticated_user_id, project_id - ): - return { - "Error": "User is not a manager of the project", - "SubCode": "UserPermissionError", - }, 403 - threading.Thread( - target=MessageService.send_message_to_all_contributors, - args=(project_id, message_dto), - ).start() - return {"Success": "Messages started"}, 200 - except NotFound: - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 + if not ProjectAdminService.is_user_action_permitted_on_project( + authenticated_user_id, project_id + ): + return { + "Error": "User is not a manager of the project", + "SubCode": "UserPermissionError", + }, 403 + threading.Thread( + target=MessageService.send_message_to_all_contributors, + args=(project_id, message_dto), + ).start() + return {"Success": "Messages started"}, 200 class ProjectsActionsFeatureAPI(Resource): @@ -203,8 +198,6 @@ def post(self, project_id): try: ProjectService.set_project_as_featured(project_id) return {"Success": True}, 200 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 except ValueError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 @@ -259,8 +252,6 @@ def post(self, project_id): try: ProjectService.unset_project_as_featured(project_id) return {"Success": True}, 200 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 except ValueError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 @@ -322,14 +313,11 @@ def post(self, project_id): "SubCode": "UserPermissionError", }, 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", "SubCode": "NotFound"}, 404 + data = request.get_json() + project_interests = InterestService.create_or_update_project_interests( + project_id, data["interests"] + ) + return project_interests.to_primitive(), 200 class ProjectActionsIntersectingTilesAPI(Resource): diff --git a/backend/api/projects/activities.py b/backend/api/projects/activities.py index 6966508c0e..9d829dfd76 100644 --- a/backend/api/projects/activities.py +++ b/backend/api/projects/activities.py @@ -1,7 +1,7 @@ -from flask_restful import Resource, current_app, request +from flask_restful import Resource, request + from backend.services.stats_service import StatsService from backend.services.project_service import ProjectService -from backend.models.postgis.utils import NotFound class ProjectsActivitiesAPI(Resource): @@ -32,12 +32,7 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - ProjectService.exists(project_id) - except NotFound as e: - current_app.logger.error(f"Error validating project: {str(e)}") - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 - + ProjectService.exists(project_id) page = int(request.args.get("page")) if request.args.get("page") else 1 activity = StatsService.get_latest_activity(project_id, page) return activity.to_primitive(), 200 @@ -66,11 +61,6 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - ProjectService.exists(project_id) - except NotFound as e: - current_app.logger.error(f"Error validating project: {str(e)}") - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 - + ProjectService.exists(project_id) activity = StatsService.get_last_activity(project_id) return activity.to_primitive(), 200 diff --git a/backend/api/projects/campaigns.py b/backend/api/projects/campaigns.py index 584cebb026..6d29087970 100644 --- a/backend/api/projects/campaigns.py +++ b/backend/api/projects/campaigns.py @@ -4,7 +4,6 @@ from backend.models.dtos.campaign_dto import CampaignProjectDTO from backend.services.campaign_service import CampaignService from backend.services.project_admin_service import ProjectAdminService -from backend.models.postgis.utils import NotFound from backend.services.users.authentication_service import token_auth @@ -49,17 +48,14 @@ def post(self, project_id, campaign_id): 500: description: Internal Server Error """ - try: - authenticated_user_id = token_auth.current_user() - if not ProjectAdminService.is_user_action_permitted_on_project( - authenticated_user_id, project_id - ): - return { - "Error": "User is not a manager of the project", - "SubCode": "UserPermissionError", - }, 403 - except NotFound: - return {"Error": "User or Project not found", "SubCode": "NotFound"}, 404 + authenticated_user_id = token_auth.current_user() + if not ProjectAdminService.is_user_action_permitted_on_project( + authenticated_user_id, project_id + ): + return { + "Error": "User is not a manager of the project", + "SubCode": "UserPermissionError", + }, 403 try: campaign_project_dto = CampaignProjectDTO() campaign_project_dto.campaign_id = campaign_id @@ -69,17 +65,13 @@ def post(self, project_id, campaign_id): current_app.logger.error(f"error validating request: {str(e)}") return {"Error": str(e), "SubCode": "InvalidData"}, 400 - try: - CampaignService.create_campaign_project(campaign_project_dto) - message = "campaign with id {} assigned successfully for project with id {}".format( + 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 NotFound: - return { - "Error": "Campaign or Project not found", - "SubCode": "NotFound", - }, 404 + ) + return ({"Success": message}, 200) def get(self, project_id): """ @@ -106,11 +98,8 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - campaigns = CampaignService.get_project_campaigns_as_dto(project_id) - return campaigns.to_primitive(), 200 - except NotFound: - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 + campaigns = CampaignService.get_project_campaigns_as_dto(project_id) + return campaigns.to_primitive(), 200 @token_auth.login_required def delete(self, project_id, campaign_id): @@ -153,16 +142,13 @@ def delete(self, project_id, campaign_id): description: Internal Server Error """ authenticated_user_id = token_auth.current_user() - try: - if not ProjectAdminService.is_user_action_permitted_on_project( - authenticated_user_id, project_id - ): - return { - "Error": "User is not a manager of the project", - "SubCode": "UserPermissionError", - }, 403 + if not ProjectAdminService.is_user_action_permitted_on_project( + authenticated_user_id, project_id + ): + return { + "Error": "User is not a manager of the project", + "SubCode": "UserPermissionError", + }, 403 - CampaignService.delete_project_campaign(project_id, campaign_id) - return {"Success": "Campaigns Deleted"}, 200 - except NotFound: - return {"Error": "Campaign Not Found", "SubCode": "NotFound"}, 404 + CampaignService.delete_project_campaign(project_id, campaign_id) + return {"Success": "Campaigns Deleted"}, 200 diff --git a/backend/api/projects/contributions.py b/backend/api/projects/contributions.py index 3d4f24af13..18bfb59146 100644 --- a/backend/api/projects/contributions.py +++ b/backend/api/projects/contributions.py @@ -1,7 +1,7 @@ -from flask_restful import Resource, current_app +from flask_restful import Resource from backend.services.project_service import ProjectService -from backend.services.stats_service import StatsService, NotFound +from backend.services.stats_service import StatsService class ProjectsContributionsAPI(Resource): @@ -28,12 +28,7 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - ProjectService.exists(project_id) - except NotFound as e: - current_app.logger.error(f"Error validating project: {str(e)}") - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 - + ProjectService.exists(project_id) contributions = StatsService.get_user_contributions(project_id) return contributions.to_primitive(), 200 @@ -62,8 +57,5 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - contribs = ProjectService.get_contribs_by_day(project_id) - return contribs.to_primitive(), 200 - except NotFound: - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 + contribs = ProjectService.get_contribs_by_day(project_id) + return contribs.to_primitive(), 200 diff --git a/backend/api/projects/favorites.py b/backend/api/projects/favorites.py index f9f13b4dc7..5dc9ea9009 100644 --- a/backend/api/projects/favorites.py +++ b/backend/api/projects/favorites.py @@ -1,6 +1,5 @@ from flask_restful import Resource -from backend.models.postgis.utils import NotFound from backend.models.dtos.project_dto import ProjectFavoriteDTO from backend.services.project_service import ProjectService from backend.services.users.authentication_service import token_auth @@ -38,15 +37,12 @@ def get(self, project_id: int): 500: description: Internal Server Error """ - try: - user_id = token_auth.current_user() - favorited = ProjectService.is_favorited(project_id, user_id) - if favorited is True: - return {"favorited": True}, 200 + user_id = token_auth.current_user() + favorited = ProjectService.is_favorited(project_id, user_id) + if favorited is True: + return {"favorited": True}, 200 - return {"favorited": False}, 200 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 + return {"favorited": False}, 200 @token_auth.login_required def post(self, project_id: int): @@ -83,11 +79,8 @@ def post(self, project_id: int): favorite_dto = ProjectFavoriteDTO() favorite_dto.project_id = project_id favorite_dto.user_id = authenticated_user_id - try: - ProjectService.favorite(project_id, authenticated_user_id) - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 + ProjectService.favorite(project_id, authenticated_user_id) return {"project_id": project_id}, 200 @token_auth.login_required @@ -123,8 +116,6 @@ def delete(self, project_id: int): """ try: ProjectService.unfavorite(project_id, token_auth.current_user()) - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 except ValueError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 400 diff --git a/backend/api/projects/resources.py b/backend/api/projects/resources.py index ab439a2202..3831ccb071 100644 --- a/backend/api/projects/resources.py +++ b/backend/api/projects/resources.py @@ -116,17 +116,8 @@ def get(self, project_id): "Error": "User not permitted: Private Project", "SubCode": "PrivateProject", }, 403 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 except ProjectServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except Exception as e: - error_msg = f"Project GET - unhandled error: {str(e)}" - current_app.logger.critical(error_msg) - return { - "Error": "Unable to fetch project", - "SubCode": "InternalServerError", - }, 500 finally: # this will try to unlock tasks that have been locked too long try: @@ -262,11 +253,8 @@ def head(self, project_id): "SubCode": "UserPermissionError", }, 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", "SubCode": "NotFound"}, 404 + project_dto = ProjectAdminService.get_project_dto_for_admin(project_id) + return project_dto.to_primitive(), 200 @token_auth.login_required def patch(self, project_id): @@ -392,16 +380,13 @@ def patch(self, project_id): description: Internal Server Error """ authenticated_user_id = token_auth.current_user() - try: - if not ProjectAdminService.is_user_action_permitted_on_project( - authenticated_user_id, project_id - ): - return { - "Error": "User is not a manager of the project", - "SubCode": "UserPermissionError", - }, 403 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 + if not ProjectAdminService.is_user_action_permitted_on_project( + authenticated_user_id, project_id + ): + return { + "Error": "User is not a manager of the project", + "SubCode": "UserPermissionError", + }, 403 try: project_dto = ProjectDTO(request.get_json()) project_dto.project_id = project_id @@ -415,8 +400,6 @@ def patch(self, project_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", "SubCode": "NotFound"}, 404 except ProjectAdminServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 @@ -471,8 +454,6 @@ def delete(self, project_id): return {"Success": "Project deleted"}, 200 except ProjectAdminServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 class ProjectSearchBase(Resource): @@ -869,16 +850,13 @@ def get(self, username): 500: description: Internal Server Error """ - try: - locale = ( - request.environ.get("HTTP_ACCEPT_LANGUAGE") - if request.environ.get("HTTP_ACCEPT_LANGUAGE") - else "en" - ) - user_dto = UserService.get_mapped_projects(username, locale) - return user_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + locale = ( + request.environ.get("HTTP_ACCEPT_LANGUAGE") + if request.environ.get("HTTP_ACCEPT_LANGUAGE") + else "en" + ) + user_dto = UserService.get_mapped_projects(username, locale) + return user_dto.to_primitive(), 200 class ProjectsQueriesSummaryAPI(Resource): @@ -911,12 +889,9 @@ def get(self, project_id: int): 500: description: Internal Server Error """ - try: - preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") - summary = ProjectService.get_project_summary(project_id, preferred_locale) - return summary.to_primitive(), 200 - except NotFound: - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 + preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") + summary = ProjectService.get_project_summary(project_id, preferred_locale) + return summary.to_primitive(), 200 class ProjectsQueriesNoGeometriesAPI(Resource): @@ -977,17 +952,8 @@ def get(self, project_id): ) return project_dto, 200 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 except ProjectServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except Exception as e: - error_msg = f"Project GET - unhandled error: {str(e)}" - current_app.logger.critical(error_msg) - return { - "Error": "Unable to fetch project", - "SubCode": "InternalServerError", - }, 500 finally: # this will try to unlock tasks that have been locked too long try: @@ -1031,16 +997,13 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - if not ProjectAdminService.is_user_action_permitted_on_project( - token_auth.current_user(), project_id - ): - return { - "Error": "User is not a manager of the project", - "SubCode": "UserPermissionError", - }, 403 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 + if not ProjectAdminService.is_user_action_permitted_on_project( + token_auth.current_user(), project_id + ): + return { + "Error": "User is not a manager of the project", + "SubCode": "UserPermissionError", + }, 403 project_dto = ProjectAdminService.get_project_dto_for_admin(project_id) return project_dto.to_primitive(), 200 @@ -1077,26 +1040,23 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - as_file = ( - strtobool(request.args.get("as_file")) - if request.args.get("as_file") - else True - ) + as_file = ( + strtobool(request.args.get("as_file")) + if request.args.get("as_file") + else True + ) - project_aoi = ProjectService.get_project_aoi(project_id) + project_aoi = ProjectService.get_project_aoi(project_id) - if as_file: - return send_file( - io.BytesIO(geojson.dumps(project_aoi).encode("utf-8")), - mimetype="application/json", - as_attachment=True, - download_name=f"{str(project_id)}.geojson", - ) + if as_file: + return send_file( + io.BytesIO(geojson.dumps(project_aoi).encode("utf-8")), + mimetype="application/json", + as_attachment=True, + download_name=f"{str(project_id)}.geojson", + ) - return project_aoi, 200 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 + return project_aoi, 200 class ProjectsQueriesPriorityAreasAPI(Resource): @@ -1128,8 +1088,6 @@ def get(self, project_id): try: priority_areas = ProjectService.get_project_priority_areas(project_id) return priority_areas, 200 - except NotFound: - return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 except ProjectServiceError: return {"Error": "Unable to fetch project"}, 403 @@ -1197,18 +1155,12 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - authenticated_user_id = ( - token_auth.current_user() if token_auth.current_user() else None - ) - limit = int(request.args.get("limit", 4)) - preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE", "en") - projects_dto = ProjectRecommendationService.get_similar_projects( - project_id, authenticated_user_id, preferred_locale, limit - ) - return projects_dto.to_primitive(), 200 - except NotFound: - return { - "Error": "Project Not Found or Project is not published", - "SubCode": "NotFound", - }, 404 + authenticated_user_id = ( + token_auth.current_user() if token_auth.current_user() else None + ) + limit = int(request.args.get("limit", 4)) + preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE", "en") + projects_dto = ProjectRecommendationService.get_similar_projects( + project_id, authenticated_user_id, preferred_locale, limit + ) + return projects_dto.to_primitive(), 200 diff --git a/backend/api/projects/statistics.py b/backend/api/projects/statistics.py index b975b1396b..afe0bd02b9 100644 --- a/backend/api/projects/statistics.py +++ b/backend/api/projects/statistics.py @@ -1,5 +1,5 @@ from flask_restful import Resource -from backend.services.stats_service import NotFound, StatsService +from backend.services.stats_service import StatsService from backend.services.project_service import ProjectService @@ -52,12 +52,9 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - # preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") - summary = ProjectService.get_project_stats(project_id) - return summary.to_primitive(), 200 - except NotFound: - return {"Error": "Project not found", "SubCode": "NotFound"}, 404 + # preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") + summary = ProjectService.get_project_stats(project_id) + return summary.to_primitive(), 200 class ProjectsStatisticsQueriesUsernameAPI(Resource): @@ -90,8 +87,5 @@ def get(self, project_id, username): 500: description: Internal Server Error """ - try: - stats_dto = ProjectService.get_project_user_stats(project_id, username) - return stats_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + stats_dto = ProjectService.get_project_user_stats(project_id, username) + return stats_dto.to_primitive(), 200 diff --git a/backend/api/projects/teams.py b/backend/api/projects/teams.py index 223986b888..3dbb12a337 100644 --- a/backend/api/projects/teams.py +++ b/backend/api/projects/teams.py @@ -1,8 +1,9 @@ from flask_restful import Resource, request, current_app from schematics.exceptions import DataError -from backend.services.team_service import TeamService, TeamServiceError, NotFound +from backend.services.team_service import TeamService, TeamServiceError from backend.services.project_admin_service import ProjectAdminService +from backend.services.project_service import ProjectService from backend.services.users.authentication_service import token_auth @@ -38,6 +39,8 @@ def get(self, project_id): 500: description: Internal Server Error """ + # Check if project exists + ProjectService.exists(project_id) teams_dto = TeamService.get_project_teams_as_dto(project_id) return teams_dto.to_primitive(), 200 @@ -180,8 +183,6 @@ def patch(self, team_id, project_id): raise ValueError() TeamService.change_team_role(team_id, project_id, role) return {"Status": "Team role updated successfully."}, 200 - except NotFound as e: - return {"Error": str(e), "SubCode": "NotFound"}, 404 except ValueError: return { "Error": "User is not a manager of the project", @@ -236,5 +237,3 @@ def delete(self, team_id, project_id): "Error": "User is not a manager of the project", "SubCode": "UserPermissionError", }, 403 - except NotFound: - return {"Error": "No team found", "SubCode": "NotFound"}, 404 diff --git a/backend/api/system/applications.py b/backend/api/system/applications.py index 42bed9292d..13820c349c 100644 --- a/backend/api/system/applications.py +++ b/backend/api/system/applications.py @@ -1,6 +1,6 @@ from flask_restful import Resource -from backend.services.application_service import ApplicationService, NotFound +from backend.services.application_service import ApplicationService from backend.services.users.authentication_service import token_auth @@ -124,12 +124,9 @@ def delete(self, application_key): 500: description: A problem occurred """ - try: - token = ApplicationService.get_token(application_key) - if token.user == token_auth.current_user(): - token.delete() - return 200 - else: - return 302 - except NotFound: - return {"Error": "Key does not exist for user", "SubCode": "NotFound"}, 404 + token = ApplicationService.get_token(application_key) + if token.user == token_auth.current_user(): + token.delete() + return 200 + else: + return 302 diff --git a/backend/api/system/authentication.py b/backend/api/system/authentication.py index 8af49e840f..58453ddad6 100644 --- a/backend/api/system/authentication.py +++ b/backend/api/system/authentication.py @@ -8,7 +8,6 @@ AuthenticationService, AuthServiceError, ) -from backend.services.users.user_service import NotFound class SystemAuthenticationLoginAPI(Resource): @@ -163,5 +162,3 @@ def get(self): except AuthServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "User not found", "SubCode": "UserNotFound"}, 404 diff --git a/backend/api/tasks/actions.py b/backend/api/tasks/actions.py index 10fc16230a..10878ecffc 100644 --- a/backend/api/tasks/actions.py +++ b/backend/api/tasks/actions.py @@ -1,11 +1,13 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError +from backend.exceptions import NotFound from backend.models.dtos.grid_dto import SplitTaskDTO -from backend.models.postgis.utils import NotFound, InvalidGeoJson +from backend.models.postgis.utils import InvalidGeoJson from backend.services.grid.split_service import SplitService, SplitServiceError from backend.services.users.user_service import UserService from backend.services.project_admin_service import ProjectAdminService +from backend.services.project_service import ProjectService from backend.services.users.authentication_service import token_auth, tm from backend.models.dtos.validator_dto import ( LockForValidationDTO, @@ -90,10 +92,9 @@ def post(self, project_id, task_id): return {"Error": "Unable to lock task", "SubCode": "InvalidData"}, 400 try: + ProjectService.exists(project_id) # Check if project exists task = MappingService.lock_task_for_mapping(lock_task_dto) return task.to_primitive(), 200 - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 except MappingServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 except UserLicenseError: @@ -177,10 +178,9 @@ def post(self, project_id, task_id): return {"Error": "Task unlock failed", "SubCode": "InvalidData"}, 400 try: + ProjectService.exists(project_id) # Check if project exists task = MappingService.stop_mapping_task(stop_task) return task.to_primitive(), 200 - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 except MappingServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 @@ -257,12 +257,13 @@ def post(self, project_id, task_id): return {"Error": "Task unlock failed", "SubCode": "InvalidData"}, 400 try: + ProjectService.exists(project_id) # Check if project exists task = MappingService.unlock_task_after_mapping(mapped_task) return task.to_primitive(), 200 - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 except MappingServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 + except NotFound as e: + return e.to_dict() except Exception as e: error_msg = f"Task Lock API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) @@ -322,12 +323,11 @@ def post(self, project_id, task_id): """ try: preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") + ProjectService.exists(project_id) # Check if project exists task = MappingService.undo_mapping( project_id, task_id, token_auth.current_user(), preferred_locale ) return task.to_primitive(), 200 - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 except MappingServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 @@ -400,12 +400,11 @@ def post(self, project_id): return {"Error": "Unable to lock task", "SubCode": "InvalidData"}, 400 try: + ProjectService.exists(project_id) # Check if project exists tasks = ValidatorService.lock_tasks_for_validation(validator_dto) return tasks.to_primitive(), 200 except ValidatorServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "Task not found", "SubCode": "NotFound"}, 404 except UserLicenseError: return { "Error": "User not accepted license terms", @@ -479,12 +478,11 @@ def post(self, project_id): return {"Error": "Task unlock failed", "SubCode": "InvalidData"}, 400 try: + ProjectService.exists(project_id) # Check if project exists tasks = ValidatorService.stop_validating_tasks(validated_dto) return tasks.to_primitive(), 200 except ValidatorServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "Task unlock failed", "SubCode": "NotFound"}, 404 class TasksActionsValidationUnlockAPI(Resource): @@ -552,12 +550,11 @@ def post(self, project_id): return {"Error": "Task unlock failed", "SubCode": "InvalidData"}, 400 try: + ProjectService.exists(project_id) # Check if project exists tasks = ValidatorService.unlock_tasks_after_validation(validated_dto) return tasks.to_primitive(), 200 except ValidatorServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "Task unlock failed", "SubCode": "NotFound"}, 404 class TasksActionsMapAllAPI(Resource): @@ -868,10 +865,9 @@ def post(self, project_id, task_id): current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Unable to split task", "SubCode": "InvalidData"}, 400 try: + ProjectService.exists(project_id) # Check if project exists tasks = SplitService.split_task(split_task_dto) return tasks.to_primitive(), 200 - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 except SplitServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 except InvalidGeoJson as e: @@ -946,12 +942,11 @@ def post(self, project_id): }, 400 try: + ProjectService.exists(project_id) # Check if project exists MappingService.extend_task_lock_time(extend_dto) return {"Success": "Successfully extended task expiry"}, 200 except MappingServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "Task not found", "SubCode": "NotFound"}, 404 class TasksActionsReverUserTaskstAPI(Resource): @@ -1016,8 +1011,6 @@ def post(self, project_id): revert_dto.user_id = user.id revert_dto.action_by = token_auth.current_user() revert_dto.validate() - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return { @@ -1029,5 +1022,3 @@ def post(self, project_id): return {"Success": "Successfully reverted tasks"}, 200 except ValidatorServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "Task not found", "SubCode": "NotFound"}, 404 diff --git a/backend/api/tasks/resources.py b/backend/api/tasks/resources.py index 504ba9e56c..90a04905cd 100644 --- a/backend/api/tasks/resources.py +++ b/backend/api/tasks/resources.py @@ -5,7 +5,7 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError -from backend.services.mapping_service import MappingService, NotFound +from backend.services.mapping_service import MappingService from backend.models.dtos.grid_dto import GridDTO from backend.services.users.authentication_service import token_auth, tm @@ -54,13 +54,10 @@ def get(self, project_id, task_id): 500: description: Internal Server Error """ - try: - preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") + preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") - task = MappingService.get_task_as_dto(task_id, project_id, preferred_locale) - return task.to_primitive(), 200 - except NotFound: - return {"Error": "Task Not Found", "SubCode": "NotFound"}, 404 + task = MappingService.get_task_as_dto(task_id, project_id, preferred_locale) + return task.to_primitive(), 200 class TasksQueriesJsonAPI(Resource): @@ -119,8 +116,6 @@ def get(self, project_id): ) return tasks_json, 200 - except NotFound: - return {"Error": "Project or Task Not Found", "SubCode": "NotFound"}, 404 except ProjectServiceError as e: return {"Error": str(e)}, 403 @@ -189,11 +184,6 @@ def delete(self, project_id): 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}", - "SubCode": "NotFound", - }, 404 except ProjectServiceError as e: return {"Error": str(e)}, 403 @@ -234,33 +224,24 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - tasks = request.args.get("tasks") if request.args.get("tasks") else None - as_file = ( - strtobool(request.args.get("as_file")) - if request.args.get("as_file") - else False + tasks = request.args.get("tasks") if request.args.get("tasks") else None + as_file = ( + strtobool(request.args.get("as_file")) + if request.args.get("as_file") + else False + ) + + xml = MappingService.generate_osm_xml(project_id, tasks) + + if as_file: + return send_file( + io.BytesIO(xml), + mimetype="text.xml", + as_attachment=True, + download_name=f"HOT-project-{project_id}.osm", ) - xml = MappingService.generate_osm_xml(project_id, tasks) - - if as_file: - return send_file( - io.BytesIO(xml), - mimetype="text.xml", - as_attachment=True, - download_name=f"HOT-project-{project_id}.osm", - ) - - return Response(xml, mimetype="text/xml", status=200) - except NotFound: - return ( - { - "Error": "Not found; please check the project and task numbers.", - "SubCode": "NotFound", - }, - 404, - ) + return Response(xml, mimetype="text/xml", status=200) class TasksQueriesGpxAPI(Resource): @@ -299,34 +280,25 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - current_app.logger.debug("GPX Called") - tasks = request.args.get("tasks") - as_file = ( - strtobool(request.args.get("as_file")) - if request.args.get("as_file") - else False + current_app.logger.debug("GPX Called") + tasks = request.args.get("tasks") + as_file = ( + strtobool(request.args.get("as_file")) + if request.args.get("as_file") + else False + ) + + xml = MappingService.generate_gpx(project_id, tasks) + + if as_file: + return send_file( + io.BytesIO(xml), + mimetype="text.xml", + as_attachment=True, + download_name=f"HOT-project-{project_id}.gpx", ) - xml = MappingService.generate_gpx(project_id, tasks) - - if as_file: - return send_file( - io.BytesIO(xml), - mimetype="text.xml", - as_attachment=True, - download_name=f"HOT-project-{project_id}.gpx", - ) - - return Response(xml, mimetype="text/xml", status=200) - except NotFound: - return ( - { - "Error": "Not found; please check the project and task numbers.", - "SubCode": "NotFound", - }, - 404, - ) + return Response(xml, mimetype="text/xml", status=200) class TasksQueriesAoiAPI(Resource): @@ -425,18 +397,9 @@ def get(self, project_id): 500: description: Internal Server Error """ - try: - ProjectService.get_project_by_id(project_id) - mapped_tasks = ValidatorService.get_mapped_tasks_by_user(project_id) - return mapped_tasks.to_primitive(), 200 - except NotFound: - return ( - { - "Error": "Not found; please check the project number.", - "SubCode": "NotFound", - }, - 404, - ) + ProjectService.get_project_by_id(project_id) + mapped_tasks = ValidatorService.get_mapped_tasks_by_user(project_id) + return mapped_tasks.to_primitive(), 200 class TasksQueriesOwnInvalidatedAPI(Resource): @@ -504,34 +467,31 @@ def get(self, username): 500: description: Internal Server Error """ - try: - sort_column = {"updatedDate": "updated_date", "projectId": "project_id"} - if request.args.get("sortBy", "updatedDate") in sort_column: - sort_column = sort_column[request.args.get("sortBy", "updatedDate")] - else: - sort_column = sort_column["updatedDate"] - # closed needs to be set to True, False, or None - closed = None - if request.args.get("closed") == "true": - closed = True - elif request.args.get("closed") == "false": - closed = False - # sort direction should only be desc or asc - if request.args.get("sortDirection") in ["asc", "desc"]: - sort_direction = request.args.get("sortDirection") - else: - sort_direction = "desc" - invalidated_tasks = ValidatorService.get_user_invalidated_tasks( - request.args.get("asValidator") == "true", - username, - request.environ.get("HTTP_ACCEPT_LANGUAGE"), - closed, - request.args.get("project", None, type=int), - request.args.get("page", None, type=int), - request.args.get("pageSize", None, type=int), - sort_column, - sort_direction, - ) - return invalidated_tasks.to_primitive(), 200 - except NotFound: - return {"Error": "No invalidated tasks", "SubCode": "NotFound"}, 404 + sort_column = {"updatedDate": "updated_date", "projectId": "project_id"} + if request.args.get("sortBy", "updatedDate") in sort_column: + sort_column = sort_column[request.args.get("sortBy", "updatedDate")] + else: + sort_column = sort_column["updatedDate"] + # closed needs to be set to True, False, or None + closed = None + if request.args.get("closed") == "true": + closed = True + elif request.args.get("closed") == "false": + closed = False + # sort direction should only be desc or asc + if request.args.get("sortDirection") in ["asc", "desc"]: + sort_direction = request.args.get("sortDirection") + else: + sort_direction = "desc" + invalidated_tasks = ValidatorService.get_user_invalidated_tasks( + request.args.get("asValidator") == "true", + username, + request.environ.get("HTTP_ACCEPT_LANGUAGE"), + closed, + request.args.get("project", None, type=int), + request.args.get("page", None, type=int), + request.args.get("pageSize", None, type=int), + sort_column, + sort_direction, + ) + return invalidated_tasks.to_primitive(), 200 diff --git a/backend/api/teams/actions.py b/backend/api/teams/actions.py index 21d35d8912..27a5799afd 100644 --- a/backend/api/teams/actions.py +++ b/backend/api/teams/actions.py @@ -5,7 +5,6 @@ from backend.models.dtos.message_dto import MessageDTO from backend.services.team_service import ( TeamService, - NotFound, TeamJoinNotAllowed, TeamServiceError, ) @@ -54,8 +53,6 @@ def post(self, team_id): return {"Success": "Join request successful"}, 200 except TeamServiceError as e: return {"Error": str(e), "SubCode": "InvalidRequest"}, 400 - except NotFound: - return {"Error": TEAM_NOT_FOUND, "SubCode": "NotFound"}, 404 @tm.pm_only(False) @token_auth.login_required @@ -124,29 +121,26 @@ def patch(self, team_id): "SubCode": "InvalidData", }, 400 - try: - authenticated_user_id = token_auth.current_user() - if request_type == "join-response": - if TeamService.is_user_team_manager(team_id, authenticated_user_id): - TeamService.accept_reject_join_request( - team_id, authenticated_user_id, username, role, action - ) - return {"Success": "True"}, 200 - else: - return ( - { - "Error": "You don't have permissions to approve this join team request", - "SubCode": "ApproveJoinError", - }, - 403, - ) - elif request_type == "invite-response": - TeamService.accept_reject_invitation_request( + authenticated_user_id = token_auth.current_user() + if request_type == "join-response": + if TeamService.is_user_team_manager(team_id, authenticated_user_id): + TeamService.accept_reject_join_request( team_id, authenticated_user_id, username, role, action ) return {"Success": "True"}, 200 - except NotFound: - return {"Error": TEAM_NOT_FOUND, "SubCode": "NotFound"}, 404 + else: + return ( + { + "Error": "You don't have permissions to approve this join team request", + "SubCode": "ApproveJoinError", + }, + 403, + ) + elif request_type == "invite-response": + TeamService.accept_reject_invitation_request( + team_id, authenticated_user_id, username, role, action + ) + return {"Success": "True"}, 200 class TeamsActionsAddAPI(Resource): @@ -257,28 +251,25 @@ def post(self, team_id): 500: description: Internal Server Error """ - try: - authenticated_user_id = token_auth.current_user() - username = request.get_json(force=True)["username"] - request_user = User.get_by_id(authenticated_user_id) - if ( - TeamService.is_user_team_manager(team_id, authenticated_user_id) - or request_user.username == username - ): - TeamService.leave_team(team_id, username) - return {"Success": "User removed from the team"}, 200 - else: - return ( - { - "Error": "You don't have permissions to remove {} from this team.".format( - username - ), - "SubCode": "RemoveUserError", - }, - 403, - ) - except NotFound: - return {"Error": "No team member found", "SubCode": "NotFound"}, 404 + authenticated_user_id = token_auth.current_user() + username = request.get_json(force=True)["username"] + request_user = User.get_by_id(authenticated_user_id) + if ( + TeamService.is_user_team_manager(team_id, authenticated_user_id) + or request_user.username == username + ): + TeamService.leave_team(team_id, username) + return {"Success": "User removed from the team"}, 200 + else: + return ( + { + "Error": "You don't have permissions to remove {} from this team.".format( + username + ), + "SubCode": "RemoveUserError", + }, + 403, + ) class TeamsActionsMessageMembersAPI(Resource): @@ -332,13 +323,7 @@ def post(self, team_id): authenticated_user_id = token_auth.current_user() message_dto = MessageDTO(request.get_json()) # Validate if team is present - try: - team = TeamService.get_team_by_id(team_id) - except NotFound: - return { - "Error": TEAM_NOT_FOUND, - "SubCode": "NotFound", - }, 404 + team = TeamService.get_team_by_id(team_id) is_manager = TeamService.is_user_team_manager( team_id, authenticated_user_id diff --git a/backend/api/teams/resources.py b/backend/api/teams/resources.py index be6dc41483..3f5c13b848 100644 --- a/backend/api/teams/resources.py +++ b/backend/api/teams/resources.py @@ -6,7 +6,7 @@ UpdateTeamDTO, TeamSearchDTO, ) -from backend.services.team_service import TeamService, TeamServiceError, NotFound +from backend.services.team_service import TeamService, TeamServiceError from backend.services.users.authentication_service import token_auth from backend.services.organisation_service import OrganisationService from backend.services.users.user_service import UserService @@ -97,8 +97,6 @@ def patch(self, team_id): try: TeamService.update_team(team_dto) return {"Status": "Updated"}, 200 - except NotFound as e: - return {"Error": str(e), "SubCode": "NotFound"}, 404 except TeamServiceError as e: return str(e), 402 @@ -132,17 +130,14 @@ def get(self, team_id): 500: description: Internal Server Error """ - try: - authenticated_user_id = token_auth.current_user() - omit_members = strtobool(request.args.get("omitMemberList", "false")) - if authenticated_user_id is None: - user_id = 0 - else: - user_id = authenticated_user_id - team_dto = TeamService.get_team_as_dto(team_id, user_id, omit_members) - return team_dto.to_primitive(), 200 - except NotFound: - return {"Error": "Team Not Found", "SubCode": "NotFound"}, 404 + authenticated_user_id = token_auth.current_user() + omit_members = strtobool(request.args.get("omitMemberList", "false")) + if authenticated_user_id is None: + user_id = 0 + else: + user_id = authenticated_user_id + team_dto = TeamService.get_team_as_dto(team_id, user_id, omit_members) + return team_dto.to_primitive(), 200 # TODO: Add delete API then do front end services and ui work @@ -180,17 +175,14 @@ def delete(self, team_id): 500: description: Internal Server Error """ - try: - if not TeamService.is_user_team_manager(team_id, token_auth.current_user()): - return { - "Error": "User is not a manager for the team", - "SubCode": "UserNotTeamManager", - }, 401 + if not TeamService.is_user_team_manager(team_id, token_auth.current_user()): + return { + "Error": "User is not a manager for the team", + "SubCode": "UserNotTeamManager", + }, 401 - TeamService.delete_team(team_id) - return {"Success": "Team deleted"}, 200 - except NotFound: - return {"Error": "Team Not Found", "SubCode": "NotFound"}, 404 + TeamService.delete_team(team_id) + return {"Success": "Team deleted"}, 200 class TeamsAllAPI(Resource): @@ -377,6 +369,3 @@ def post(self): return {"Error": error_msg, "SubCode": "CreateTeamNotPermitted"}, 403 except TeamServiceError as e: return str(e), 400 - except NotFound: - error_msg = "Team POST - Organisation does not exist" - return {"Error": error_msg, "SubCode": "NotFound"}, 404 diff --git a/backend/api/users/actions.py b/backend/api/users/actions.py index 0a7baba8e2..0bd77f8c9d 100644 --- a/backend/api/users/actions.py +++ b/backend/api/users/actions.py @@ -4,7 +4,7 @@ from backend.models.dtos.user_dto import UserDTO, UserRegisterEmailDTO from backend.services.messaging.message_service import MessageService from backend.services.users.authentication_service import token_auth, tm -from backend.services.users.user_service import UserService, UserServiceError, NotFound +from backend.services.users.user_service import UserService, UserServiceError from backend.services.interests_service import InterestService @@ -95,13 +95,10 @@ def patch(self): "SubCode": "InvalidData", }, 400 - try: - verification_sent = UserService.update_user_details( - authenticated_user_id, user_dto - ) - return verification_sent, 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + verification_sent = UserService.update_user_details( + authenticated_user_id, user_dto + ) + return verification_sent, 200 class UsersActionsSetLevelAPI(Resource): @@ -151,8 +148,6 @@ def patch(self, username, level): return {"Success": "Level set"}, 200 except UserServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 400 - except NotFound: - return {"Error": "User or mapping not found", "SubCode": "NotFound"}, 404 class UsersActionsSetRoleAPI(Resource): @@ -202,8 +197,6 @@ def patch(self, username, role): return {"Success": "Role Added"}, 200 except UserServiceError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 403 - except NotFound: - return {"Error": "User or mapping not found", "SubCode": "NotFound"}, 404 class UsersActionsSetExpertModeAPI(Resource): @@ -248,8 +241,6 @@ def patch(self, is_expert): return {"Success": "Expert mode updated"}, 200 except UserServiceError: return {"Error": "Not allowed"}, 400 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 class UsersActionsVerifyEmailAPI(Resource): @@ -378,5 +369,3 @@ def post(self): return user_interests.to_primitive(), 200 except (ValueError, KeyError) as e: return {"Error": str(e)}, 400 - except NotFound: - return {"Error": "Interest not Found", "SubCode": "NotFound"}, 404 diff --git a/backend/api/users/openstreetmap.py b/backend/api/users/openstreetmap.py index 039dc20013..7ab26c4bb7 100644 --- a/backend/api/users/openstreetmap.py +++ b/backend/api/users/openstreetmap.py @@ -1,7 +1,7 @@ from flask_restful import Resource from backend.services.users.authentication_service import token_auth -from backend.services.users.user_service import UserService, OSMServiceError, NotFound +from backend.services.users.user_service import UserService, OSMServiceError class UsersOpenStreetMapAPI(Resource): @@ -42,7 +42,5 @@ def get(self, username): try: osm_dto = UserService.get_osm_details_for_user(username) return osm_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 except OSMServiceError as e: return {"Error": str(e)}, 502 diff --git a/backend/api/users/resources.py b/backend/api/users/resources.py index bd382c4b46..ab08d78fd9 100644 --- a/backend/api/users/resources.py +++ b/backend/api/users/resources.py @@ -4,7 +4,7 @@ from backend.models.dtos.user_dto import UserSearchQuery from backend.services.users.authentication_service import token_auth -from backend.services.users.user_service import UserService, NotFound +from backend.services.users.user_service import UserService from backend.services.project_service import ProjectService @@ -41,13 +41,8 @@ def get(self, user_id): 500: description: Internal Server Error """ - try: - user_dto = UserService.get_user_dto_by_id( - user_id, token_auth.current_user() - ) - return user_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + user_dto = UserService.get_user_dto_by_id(user_id, token_auth.current_user()) + return user_dto.to_primitive(), 200 class UsersAllAPI(Resource): @@ -154,13 +149,10 @@ def get(self, username): 500: description: Internal Server Error """ - try: - user_dto = UserService.get_user_dto_by_username( - username, token_auth.current_user() - ) - return user_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + user_dto = UserService.get_user_dto_by_username( + username, token_auth.current_user() + ) + return user_dto.to_primitive(), 200 class UsersQueriesUsernameFilterAPI(Resource): @@ -203,13 +195,10 @@ def get(self, username): 500: description: Internal Server Error """ - try: - page = int(request.args.get("page")) if request.args.get("page") else 1 - project_id = request.args.get("projectId", None, int) - users_dto = UserService.filter_users(username, project_id, page) - return users_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + page = int(request.args.get("page")) if request.args.get("page") else 1 + project_id = request.args.get("projectId", None, int) + users_dto = UserService.filter_users(username, project_id, page) + return users_dto.to_primitive(), 200 class UsersQueriesOwnLockedAPI(Resource): @@ -278,14 +267,11 @@ def get(self): 500: description: Internal Server Error """ - try: - preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") - locked_tasks = ProjectService.get_task_details_for_logged_in_user( - token_auth.current_user(), preferred_locale - ) - return locked_tasks.to_primitive(), 200 - except NotFound: - return {"Error": "User has no locked tasks", "SubCode": "NotFound"}, 404 + preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") + locked_tasks = ProjectService.get_task_details_for_logged_in_user( + token_auth.current_user(), preferred_locale + ) + return locked_tasks.to_primitive(), 200 class UsersQueriesFavoritesAPI(Resource): @@ -313,11 +299,8 @@ def get(self): 500: description: Internal Server Error """ - try: - favs_dto = UserService.get_projects_favorited(token_auth.current_user()) - return favs_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + favs_dto = UserService.get_projects_favorited(token_auth.current_user()) + return favs_dto.to_primitive(), 200 class UsersQueriesInterestsAPI(Resource): @@ -350,12 +333,9 @@ def get(self, username): 500: description: Internal Server Error """ - try: - user = UserService.get_user_by_username(username) - interests_dto = UserService.get_interests(user) - return interests_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + user = UserService.get_user_by_username(username) + interests_dto = UserService.get_interests(user) + return interests_dto.to_primitive(), 200 class UsersRecommendedProjectsAPI(Resource): @@ -399,13 +379,10 @@ def get(self, username): 500: description: Internal Server Error """ - try: - locale = ( - request.environ.get("HTTP_ACCEPT_LANGUAGE") - if request.environ.get("HTTP_ACCEPT_LANGUAGE") - else "en" - ) - user_dto = UserService.get_recommended_projects(username, locale) - return user_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User or mapping not found", "SubCode": "NotFound"}, 404 + locale = ( + request.environ.get("HTTP_ACCEPT_LANGUAGE") + if request.environ.get("HTTP_ACCEPT_LANGUAGE") + else "en" + ) + user_dto = UserService.get_recommended_projects(username, locale) + return user_dto.to_primitive(), 200 diff --git a/backend/api/users/statistics.py b/backend/api/users/statistics.py index d244886e81..2838cd4831 100644 --- a/backend/api/users/statistics.py +++ b/backend/api/users/statistics.py @@ -2,7 +2,7 @@ from datetime import date, timedelta from flask_restful import Resource, request -from backend.services.users.user_service import UserService, NotFound +from backend.services.users.user_service import UserService from backend.services.stats_service import StatsService from backend.services.interests_service import InterestService from backend.services.users.authentication_service import token_auth @@ -42,11 +42,8 @@ def get(self, username): 500: description: Internal Server Error """ - try: - stats_dto = UserService.get_detailed_stats(username) - return stats_dto.to_primitive(), 200 - except NotFound: - return {"Error": "User not found", "SubCode": "NotFound"}, 404 + stats_dto = UserService.get_detailed_stats(username) + return stats_dto.to_primitive(), 200 class UsersStatisticsInterestsAPI(Resource): @@ -79,11 +76,8 @@ def get(self, user_id): 500: description: Internal Server Error """ - try: - rate = InterestService.compute_contributions_rate(user_id) - return rate.to_primitive(), 200 - except NotFound: - return {"Error": "User not Found", "SubCode": "NotFound"}, 404 + rate = InterestService.compute_contributions_rate(user_id) + return rate.to_primitive(), 200 class UsersStatisticsAllAPI(Resource): diff --git a/backend/api/users/tasks.py b/backend/api/users/tasks.py index 746b3a3582..e0375024eb 100644 --- a/backend/api/users/tasks.py +++ b/backend/api/users/tasks.py @@ -2,7 +2,7 @@ from dateutil.parser import parse as date_parse from backend.services.users.authentication_service import token_auth -from backend.services.users.user_service import UserService, NotFound +from backend.services.users.user_service import UserService class UsersTasksAPI(Resource): @@ -111,5 +111,3 @@ def get(self, user_id): return tasks.to_primitive(), 200 except ValueError: return {"tasks": [], "pagination": {"total": 0}}, 200 - except NotFound: - return {"Error": "User or tasks not found", "SubCode": "NotFound"}, 404 diff --git a/backend/error_messages.json b/backend/error_messages.json index a51888f146..f2afe220cf 100644 --- a/backend/error_messages.json +++ b/backend/error_messages.json @@ -1,8 +1,28 @@ { - "NOT_FOUND": "The requested resource was not found.", + "NOT_FOUND": "The requested resource was not found on the server.", + "APPLICATION_NOT_FOUND": "The requested application was not found on the server.", + "CAMPAIGN_NOT_FOUND": "The requested campaign was not found on the server.", + "COMMENTS_NOT_FOUND": "The comments associated with the requested resource were not found on the server.", + "INTEREST_NOT_FOUND": "The requested interest was not found on the server.", + "ISSUE_CATEGORY_NOT_FOUND": "The requested mapping issue category was not found on the server.", + "JOIN_REQUEST_NOT_FOUND": "Team join request by the user was not found on the server.", + "LICENSE_NOT_FOUND": "The requested license was not found on the server.", + "MESSAGE_NOT_FOUND": "The requested message was not found on the server.", + "MESSAGES_NOT_FOUND": "The messages associated with the requested resource were not found on the server.", + "NOTIFICATIONS_NOT_FOUND": "The notifications associated with the requested resource were not found on the server.", + "ORGANISATION_NOT_FOUND": "The requested organisation was not found on the server.", + "ORGANISATION_CAMPAIGN_NOT_FOUND": "The requested campaign associated with the requested organisation was not found on the server.", + "PROJECT_CAMPAIGN_NOT_FOUND": "The requested campaign associated with the requested project was not found on the server.", + "PROJECT_NOT_FOUND": "The requested project was not found on the server.", + "PROJECTS_NOT_FOUND": "The projects associated with the requested resource were not found on the server.", + "TASK_NOT_FOUND": "The requested task was not found on the server.", + "TASKS_NOT_FOUND": "The tasks associated with the requested resource were not found on the server.", + "TEAM_NOT_FOUND": "The requested team was not found on the server.", + "USER_NOT_IN_TEAM": "The requested user is not a member of the team.", + "USER_NOT_FOUND": "The requested user was not found on the server.", "BAD_REQUEST": "The request was invalid or cannot be otherwise served.", "UNAUTHORIZED": "Authentication credentials were missing or incorrect.", "FORBIDDEN": "The request is understood, but it has been refused or access is not allowed.", "CONFLICT": "The request could not be completed due to a conflict with the current state of the resource.", "INTERNAL_SERVER_ERROR": "An unexpected error occurred while processing the request. Please contact the system administrator." -} \ No newline at end of file +} diff --git a/backend/models/postgis/interests.py b/backend/models/postgis/interests.py index ba282e9fe2..d9c6e0213c 100644 --- a/backend/models/postgis/interests.py +++ b/backend/models/postgis/interests.py @@ -1,6 +1,6 @@ from backend import db +from backend.exceptions import NotFound from backend.models.dtos.interests_dto import InterestDTO, InterestsListDTO -from backend.models.postgis.utils import NotFound # Secondary table defining many-to-many join for interests of a user. user_interests = db.Table( @@ -32,7 +32,7 @@ def get_by_id(interest_id: int): """Get interest by id""" interest = db.session.get(Interest, interest_id) if interest is None: - raise NotFound(f"Interest id {interest_id} not found") + raise NotFound(sub_code="INTEREST_NOT_FOUND", interest_id=interest_id) return interest @@ -41,7 +41,7 @@ def get_by_name(name: str): """Get interest by name""" interest = Interest.query.filter(Interest.name == name).first() if interest is None: - raise NotFound(f"Interest name {name} not found") + raise NotFound(sub_code="INTEREST_NOT_FOUND", interest_name=name) return interest diff --git a/backend/models/postgis/licenses.py b/backend/models/postgis/licenses.py index 3c65de60f8..091e2767cb 100644 --- a/backend/models/postgis/licenses.py +++ b/backend/models/postgis/licenses.py @@ -1,6 +1,6 @@ from backend import db +from backend.exceptions import NotFound from backend.models.dtos.licenses_dto import LicenseDTO, LicenseListDTO -from backend.models.postgis.utils import NotFound # Secondary table defining the many-to-many join user_licenses_table = db.Table( @@ -32,7 +32,7 @@ def get_by_id(license_id: int): map_license = db.session.get(License, license_id) if map_license is None: - raise NotFound() + raise NotFound(sub_code="LICENSE_NOT_FOUND", license_id=license_id) return map_license diff --git a/backend/models/postgis/message.py b/backend/models/postgis/message.py index 12651e7273..32516d5a57 100644 --- a/backend/models/postgis/message.py +++ b/backend/models/postgis/message.py @@ -3,11 +3,13 @@ from backend import db from flask import current_app from enum import Enum + +from backend.exceptions import NotFound from backend.models.dtos.message_dto import MessageDTO, MessagesDTO from backend.models.postgis.user import User from backend.models.postgis.task import Task, TaskHistory, TaskAction from backend.models.postgis.project import Project -from backend.models.postgis.utils import timestamp, NotFound +from backend.models.postgis.utils import timestamp class MessageType(Enum): @@ -156,7 +158,10 @@ def get_all_messages(user_id: int) -> MessagesDTO: user_messages = Message.query.filter(Message.to_user_id == user_id).all() if len(user_messages) == 0: - raise NotFound() + raise NotFound( + sub_code="MESSAGES_NOT_FOUND", + user_id=user_id, + ) messages_dto = MessagesDTO() for message in user_messages: diff --git a/backend/models/postgis/organisation.py b/backend/models/postgis/organisation.py index dff50df8ad..c19a54a4d8 100644 --- a/backend/models/postgis/organisation.py +++ b/backend/models/postgis/organisation.py @@ -1,6 +1,7 @@ from slugify import slugify from backend import db +from backend.exceptions import NotFound from backend.models.dtos.organisation_dto import ( OrganisationDTO, NewOrganisationDTO, @@ -8,7 +9,6 @@ ) from backend.models.postgis.user import User from backend.models.postgis.campaign import Campaign, campaign_organisations -from backend.models.postgis.utils import NotFound from backend.models.postgis.statuses import OrganisationType @@ -77,7 +77,7 @@ def create_from_dto(cls, new_organisation_dto: NewOrganisationDTO): user = User.get_by_username(manager) if user is None: - raise NotFound(f"User {manager} Not Found") + raise NotFound(sub_code="USER_NOT_FOUND", username=manager) new_org.managers.append(user) @@ -109,7 +109,7 @@ def update(self, organisation_dto: OrganisationDTO): new_manager = User.get_by_username(manager) if new_manager is None: - raise NotFound(f"User {manager} Not Found") + raise NotFound(sub_code="USER_NOT_FOUND", username=manager) self.managers.append(new_manager) diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py index cc004c3b02..5c37ccc13d 100644 --- a/backend/models/postgis/project.py +++ b/backend/models/postgis/project.py @@ -15,6 +15,7 @@ import requests from backend import db +from backend.exceptions import NotFound from backend.models.dtos.campaign_dto import CampaignDTO from backend.models.dtos.project_dto import ( ProjectDTO, @@ -57,7 +58,6 @@ ST_GeomFromGeoJSON, timestamp, ST_Centroid, - NotFound, ) from backend.services.grid.grid_service import GridService from backend.models.postgis.interests import Interest, project_interests @@ -297,7 +297,7 @@ def clone(project_id: int, author_id: int): orig = db.session.get(Project, project_id) if orig is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) # Transform into dictionary. orig_metadata = orig.__dict__.copy() @@ -412,7 +412,10 @@ def update(self, project_dto: ProjectDTO): if project_dto.organisation: org = Organisation.get(project_dto.organisation) if org is None: - raise NotFound("Organisation does not exist") + raise NotFound( + sub_code="ORGANISATION_NOT_FOUND", + organisation_id=project_dto.organisation, + ) self.organisation = org # Cast MappingType strings to int array @@ -446,7 +449,7 @@ def update(self, project_dto: ProjectDTO): team = Team.get(team_dto.team_id) if team is None: - raise NotFound("Team not found") + raise NotFound(sub_code="TEAM_NOT_FOUND", team_id=team_dto.team_id) role = TeamRoles[team_dto.role].value project_team = ProjectTeams(project=self, team=team, role=role) @@ -588,7 +591,7 @@ def get_projects_for_admin( admins_projects = query.all() if admins_projects is None: - raise NotFound("No projects found for admin") + raise NotFound(sub_code="PROJECTS_NOT_FOUND") admin_projects_dto = PMDashboardDTO() for project in admins_projects: diff --git a/backend/models/postgis/task.py b/backend/models/postgis/task.py index 6bd682c8b0..1f0433577f 100644 --- a/backend/models/postgis/task.py +++ b/backend/models/postgis/task.py @@ -9,8 +9,10 @@ from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound from sqlalchemy.orm.session import make_transient from geoalchemy2 import Geometry -from backend import db from typing import List + +from backend import db +from backend.exceptions import NotFound from backend.models.dtos.mapping_dto import TaskDTO, TaskHistoryDTO from backend.models.dtos.validator_dto import MappedTasksByUser, MappedTasks from backend.models.dtos.project_dto import ( @@ -28,7 +30,6 @@ ST_SetSRID, timestamp, parse_duration, - NotFound, ) from backend.models.postgis.task_annotation import TaskAnnotation @@ -892,17 +893,19 @@ def get_tasks_as_geojson_feature_collection( filters = [Task.project_id == project_id] if task_ids_str: - task_ids = map(int, task_ids_str.split(",")) + task_ids = list(map(int, task_ids_str.split(","))) tasks = Task.get_tasks(project_id, task_ids) if not tasks or len(tasks) == 0: - raise NotFound() + raise NotFound( + sub_code="TASKS_NOT_FOUND", tasks=task_ids, project_id=project_id + ) else: tasks_filters = [task.id for task in tasks] filters = [Task.project_id == project_id, Task.id.in_(tasks_filters)] else: tasks = Task.get_all_tasks(project_id) if not tasks or len(tasks) == 0: - raise NotFound() + raise NotFound(sub_code="TASKS_NOT_FOUND", project_id=project_id) if status: filters.append(Task.task_status == status) @@ -1038,7 +1041,7 @@ def get_max_task_id_for_project(project_id: int): .group_by(Task.project_id) ) if result.count() == 0: - raise NotFound() + raise NotFound(sub_code="TASKS_NOT_FOUND", project_id=project_id) for row in result: return row[0] diff --git a/backend/models/postgis/team.py b/backend/models/postgis/team.py index f224965c56..ca9ac2a8f9 100644 --- a/backend/models/postgis/team.py +++ b/backend/models/postgis/team.py @@ -1,4 +1,5 @@ from backend import db +from backend.exceptions import NotFound from backend.models.dtos.team_dto import ( TeamDTO, NewTeamDTO, @@ -14,7 +15,6 @@ TeamRoles, ) from backend.models.postgis.user import User -from backend.models.postgis.utils import NotFound class TeamMembers(db.Model): @@ -144,7 +144,9 @@ def update(self, team_dto: TeamDTO): for member in team_dto.members: user = User.get_by_username(member["username"]) if user is None: - raise NotFound("User not found") + raise NotFound( + sub_code="USER_NOT_FOUND", username=member["username"] + ) team_member = TeamMembers.get(self.id, user.id) if team_member: team_member.join_request_notifications = member[ diff --git a/backend/models/postgis/user.py b/backend/models/postgis/user.py index 8541f82062..1d6a649f52 100644 --- a/backend/models/postgis/user.py +++ b/backend/models/postgis/user.py @@ -2,6 +2,8 @@ from backend import db from sqlalchemy import desc, func from geoalchemy2 import functions + +from backend.exceptions import NotFound from backend.models.dtos.user_dto import ( UserDTO, UserMappedProjectsDTO, @@ -21,7 +23,7 @@ UserRole, UserGender, ) -from backend.models.postgis.utils import NotFound, timestamp +from backend.models.postgis.utils import timestamp from backend.models.postgis.interests import Interest, user_interests @@ -203,7 +205,7 @@ def filter_users(user_filter: str, project_id: int, page: int) -> UserFilterDTO: results = query.paginate(page=page, per_page=20, error_out=True) if results.total == 0: - raise NotFound() + raise NotFound(sub_code="USER_NOT_FOUND", username=user_filter) dto = UserFilterDTO() for result in results.items: diff --git a/backend/services/application_service.py b/backend/services/application_service.py index f7a649f58f..b29eb2a4df 100644 --- a/backend/services/application_service.py +++ b/backend/services/application_service.py @@ -1,5 +1,5 @@ +from backend.exceptions import NotFound from backend.models.postgis.application import Application -from backend.models.postgis.utils import NotFound from backend.services.users.authentication_service import AuthenticationService @@ -15,7 +15,7 @@ def get_token(token: str): application = Application.get_token(token) if application is None: - raise NotFound() + raise NotFound(sub_code="APPLICATION_NOT_FOUND") return application diff --git a/backend/services/campaign_service.py b/backend/services/campaign_service.py index 6090981aab..9f00f6dded 100644 --- a/backend/services/campaign_service.py +++ b/backend/services/campaign_service.py @@ -2,6 +2,8 @@ from flask import current_app from sqlalchemy.exc import IntegrityError from psycopg2.errors import UniqueViolation, NotNullViolation + +from backend.exceptions import NotFound from backend.models.dtos.campaign_dto import ( CampaignDTO, NewCampaignDTO, @@ -13,7 +15,6 @@ campaign_projects, campaign_organisations, ) -from backend.models.postgis.utils import NotFound from backend.models.postgis.organisation import Organisation from backend.services.organisation_service import OrganisationService from backend.services.project_service import ProjectService @@ -26,7 +27,7 @@ def get_campaign(campaign_id: int) -> Campaign: campaign = db.session.get(Campaign, campaign_id) if campaign is None: - raise NotFound() + raise NotFound(sub_code="CAMPAIGN_NOT_FOUND", campaign_id=campaign_id) return campaign @@ -35,7 +36,7 @@ def get_campaign_by_name(campaign_name: str) -> Campaign: campaign = Campaign.query.filter_by(name=campaign_name).first() if campaign is None: - raise NotFound() + raise NotFound(sub_code="CAMPAIGN_NOT_FOUND", campaign_name=campaign_name) return campaign @@ -80,7 +81,11 @@ def delete_project_campaign(project_id: int, campaign_id: int): project = ProjectService.get_project_by_id(project_id) project_campaigns = CampaignService.get_project_campaigns_as_dto(project_id) if campaign.id not in [i["id"] for i in project_campaigns["campaigns"]]: - raise NotFound() + raise NotFound( + sub_code="PROJECT_CAMPAIGN_NOT_FOUND", + campaign_id=campaign_id, + project_id=project_id, + ) project.campaign.remove(campaign) db.session.commit() new_campaigns = CampaignService.get_project_campaigns_as_dto(project_id) @@ -128,6 +133,11 @@ def create_campaign_project(dto: CampaignProjectDTO): @staticmethod def create_campaign_organisation(organisation_id: int, campaign_id: int): """Creates new campaign from DTO""" + # Check if campaign exists + CampaignService.get_campaign(campaign_id) + # Check if organisation exists + OrganisationService.get_organisation_by_id(organisation_id) + statement = campaign_organisations.insert().values( campaign_id=campaign_id, organisation_id=organisation_id ) @@ -141,6 +151,8 @@ def create_campaign_organisation(organisation_id: int, campaign_id: int): @staticmethod def get_organisation_campaigns_as_dto(organisation_id: int) -> CampaignListDTO: """Gets all the campaigns for a specified project""" + # Check if organisation exists + OrganisationService.get_organisation_by_id(organisation_id) query = ( Campaign.query.join(campaign_organisations) .filter(campaign_organisations.c.organisation_id == organisation_id) @@ -163,11 +175,22 @@ def campaign_organisation_exists(campaign_id: int, org_id: int): def delete_organisation_campaign(organisation_id: int, campaign_id: int): """Delete campaign for a organisation""" campaign = db.session.get(Campaign, campaign_id) + if not campaign: + raise NotFound(sub_code="CAMPAIGN_NOT_FOUND", campaign_id=campaign_id) org = db.session.get(Organisation, organisation_id) - try: - org.campaign.remove(campaign) - except ValueError: - raise NotFound() + if not org: + raise NotFound( + sub_code="ORGANISATION_NOT_FOUND", organisation_id=organisation_id + ) + if not CampaignService.campaign_organisation_exists( + campaign_id, organisation_id + ): + raise NotFound( + sub_code="ORGANISATION_CAMPAIGN_NOT_FOUND", + organisation_id=organisation_id, + campaign_id=campaign_id, + ) + org.campaign.remove(campaign) db.session.commit() new_campaigns = CampaignService.get_organisation_campaigns_as_dto( organisation_id @@ -178,7 +201,7 @@ def delete_organisation_campaign(organisation_id: int, campaign_id: int): def update_campaign(campaign_dto: CampaignDTO, campaign_id: int): campaign = db.session.get(Campaign, campaign_id) if not campaign: - raise NotFound(f"Campaign id {campaign_id} not found") + raise NotFound(sub_code="CAMPAIGN_NOT_FOUND", campaign_id=campaign_id) try: campaign.update(campaign_dto) except IntegrityError as e: diff --git a/backend/services/grid/split_service.py b/backend/services/grid/split_service.py index e39252e286..fe57b04015 100644 --- a/backend/services/grid/split_service.py +++ b/backend/services/grid/split_service.py @@ -4,12 +4,14 @@ from backend import db from flask import current_app from geoalchemy2 import shape + +from backend.exceptions import NotFound from backend.models.dtos.grid_dto import SplitTaskDTO from backend.models.dtos.mapping_dto import TaskDTOs from backend.models.postgis.utils import ST_Transform, ST_Area, ST_GeogFromWKB from backend.models.postgis.task import Task, TaskStatus, TaskAction from backend.models.postgis.project import Project -from backend.models.postgis.utils import NotFound, InvalidGeoJson +from backend.models.postgis.utils import InvalidGeoJson class SplitServiceError(Exception): @@ -171,7 +173,7 @@ def split_task(split_task_dto: SplitTaskDTO) -> TaskDTOs: # get the task to be split original_task = Task.get(split_task_dto.task_id, split_task_dto.project_id) if original_task is None: - raise NotFound() + raise NotFound(sub_code="TASK_NOT_FOUND", task_id=split_task_dto.task_id) original_geometry = shape.to_shape(original_task.geometry) diff --git a/backend/services/mapping_issues_service.py b/backend/services/mapping_issues_service.py index 01e5d6e07c..1644976f93 100644 --- a/backend/services/mapping_issues_service.py +++ b/backend/services/mapping_issues_service.py @@ -1,6 +1,6 @@ +from backend.exceptions import NotFound from backend.models.postgis.mapping_issues import MappingIssueCategory from backend.models.dtos.mapping_issues_dto import MappingIssueCategoryDTO -from backend.models.postgis.utils import NotFound class MappingIssueCategoryService: @@ -13,7 +13,7 @@ def get_mapping_issue_category(category_id: int) -> MappingIssueCategory: category = MappingIssueCategory.get_by_id(category_id) if category is None: - raise NotFound() + raise NotFound(sub_code="ISSUE_CATEGORY_NOT_FOUND", category_id=category_id) return category diff --git a/backend/services/mapping_service.py b/backend/services/mapping_service.py index 7178fa681e..ca8c1a47bd 100644 --- a/backend/services/mapping_service.py +++ b/backend/services/mapping_service.py @@ -4,6 +4,7 @@ from flask import current_app from geoalchemy2 import shape +from backend.exceptions import NotFound from backend.models.dtos.mapping_dto import ( ExtendLockTimeDTO, TaskDTO, @@ -14,7 +15,7 @@ ) from backend.models.postgis.statuses import MappingNotAllowed from backend.models.postgis.task import Task, TaskStatus, TaskHistory, TaskAction -from backend.models.postgis.utils import NotFound, UserLicenseError +from backend.models.postgis.utils import UserLicenseError from backend.services.messaging.message_service import MessageService from backend.services.project_service import ProjectService from backend.services.stats_service import StatsService @@ -38,7 +39,9 @@ def get_task(task_id: int, project_id: int) -> Task: task = Task.get(task_id, project_id) if task is None: - raise NotFound() + raise NotFound( + sub_code="TASK_NOT_FOUND", project_id=project_id, task_id=task_id + ) return task @@ -178,7 +181,9 @@ def get_task_locked_by_user(project_id: int, task_id: int, user_id: int) -> Task """ task = MappingService.get_task(task_id, project_id) if task is None: - raise NotFound(f"Task {task_id} not found") + raise NotFound( + sub_code="TASK_NOT_FOUND", project_id=project_id, task_id=task_id + ) current_state = TaskStatus(task.task_status) if current_state != TaskStatus.LOCKED_FOR_MAPPING: raise MappingServiceError( @@ -193,9 +198,16 @@ def get_task_locked_by_user(project_id: int, task_id: int, user_id: int) -> Task @staticmethod def add_task_comment(task_comment: TaskCommentDTO) -> TaskDTO: """Adds the comment to the task history""" + # Check if project exists + ProjectService.exists(task_comment.project_id) + task = Task.get(task_comment.task_id, task_comment.project_id) if task is None: - raise NotFound(f"Task {task_comment.task_id} not found") + raise NotFound( + sub_code="TASK_NOT_FOUND", + project_id=task_comment.project_id, + task_id=task_comment.task_id, + ) task.set_task_history( TaskAction.COMMENT, task_comment.user_id, task_comment.comment @@ -250,11 +262,13 @@ def generate_gpx(project_id: int, task_ids_str: str, timestamp=None): task_ids = list(map(int, task_ids_str.split(","))) tasks = Task.get_tasks(project_id, task_ids) if not tasks or len(tasks) == 0: - raise NotFound() + raise NotFound( + sub_code="TASKS_NOT_FOUND", project_id=project_id, task_ids=task_ids + ) else: tasks = Task.get_all_tasks(project_id) if not tasks or len(tasks) == 0: - raise NotFound() + raise NotFound(sub_code="TASKS_NOT_FOUND", project_id=project_id) for task in tasks: task_geom = shape.to_shape(task.geometry) @@ -290,11 +304,13 @@ def generate_osm_xml(project_id: int, task_ids_str: str) -> str: task_ids = list(map(int, task_ids_str.split(","))) tasks = Task.get_tasks(project_id, task_ids) if not tasks or len(tasks) == 0: - raise NotFound() + raise NotFound( + sub_code="TASKS_NOT_FOUND", project_id=project_id, task_ids=task_ids + ) else: tasks = Task.get_all_tasks(project_id) if not tasks or len(tasks) == 0: - raise NotFound() + raise NotFound(sub_code="TASKS_NOT_FOUND", project_id=project_id) fake_id = -1 # We use fake-ids to ensure XML will not be validated by OSM for task in tasks: @@ -418,7 +434,9 @@ def reset_all_badimagery(project_id: int, user_id: int): def lock_time_can_be_extended(project_id, task_id, user_id): task = Task.get(task_id, project_id) if task is None: - raise NotFound(f"Task {task_id} not found") + raise NotFound( + sub_code="TASK_NOT_FOUND", project_id=project_id, task_id=task_id + ) if TaskStatus(task.task_status) not in [ TaskStatus.LOCKED_FOR_MAPPING, diff --git a/backend/services/messaging/chat_service.py b/backend/services/messaging/chat_service.py index 8a1c72be75..825be2123f 100644 --- a/backend/services/messaging/chat_service.py +++ b/backend/services/messaging/chat_service.py @@ -2,7 +2,7 @@ from flask import current_app from backend import db -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound from backend.models.dtos.message_dto import ChatMessageDTO, ProjectChatDTO from backend.models.postgis.project_chat import ProjectChat from backend.models.postgis.project_info import ProjectInfo @@ -100,7 +100,11 @@ def get_project_chat_by_id(project_id: int, comment_id: int) -> ProjectChat: ProjectChat.id == comment_id, ).one_or_none() if chat_message is None: - raise NotFound("Message not found") + raise NotFound( + sub_code="MESSAGE_NOT_FOUND", + message_id=comment_id, + project_id=project_id, + ) return chat_message @@ -117,12 +121,19 @@ def delete_project_chat_by_id(project_id: int, comment_id: int, user_id: int): ---------------------------------------- returns: None """ + # Check if project exists + ProjectService.exists(project_id) + chat_message = ProjectChat.query.filter( ProjectChat.project_id == project_id, ProjectChat.id == comment_id, ).one_or_none() if chat_message is None: - raise NotFound("Message not found") + raise NotFound( + sub_code="MESSAGE_NOT_FOUND", + message_id=comment_id, + project_id=project_id, + ) is_user_allowed = ( chat_message.user_id == user_id diff --git a/backend/services/messaging/message_service.py b/backend/services/messaging/message_service.py index 05102691e3..a74dba6167 100644 --- a/backend/services/messaging/message_service.py +++ b/backend/services/messaging/message_service.py @@ -9,9 +9,10 @@ from markdown import markdown from backend import db, create_app +from backend.exceptions import NotFound from backend.models.dtos.message_dto import MessageDTO, MessagesDTO from backend.models.dtos.stats_dto import Pagination -from backend.models.postgis.message import Message, MessageType, NotFound +from backend.models.postgis.message import Message, MessageType from backend.models.postgis.notification import Notification from backend.models.postgis.project import Project, ProjectInfo from backend.models.postgis.task import TaskStatus, TaskAction, TaskHistory @@ -750,7 +751,7 @@ def get_message(message_id: int, user_id: int) -> Message: message = db.session.get(Message, message_id) if message is None: - raise NotFound() + raise NotFound(sub_code="MESSAGE_NOT_FOUND", message_id=message_id) if message.to_user_id != int(user_id): raise MessageServiceError( diff --git a/backend/services/notification_service.py b/backend/services/notification_service.py index 748607c6e5..e4c5aeab04 100644 --- a/backend/services/notification_service.py +++ b/backend/services/notification_service.py @@ -1,5 +1,5 @@ from backend.models.postgis.notification import Notification -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound class NotificationService: @@ -10,7 +10,7 @@ def update(user_id: int): ).first() if notifications is None: - raise NotFound() + raise NotFound(sub_code="NOTIFICATIONS_NOT_FOUND", user_id=user_id) notifications.update() return notifications.unread_count diff --git a/backend/services/organisation_service.py b/backend/services/organisation_service.py index 2b000bb018..e1eef65c9e 100644 --- a/backend/services/organisation_service.py +++ b/backend/services/organisation_service.py @@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta from backend import db +from backend.exceptions import NotFound from backend.models.dtos.organisation_dto import ( OrganisationDTO, NewOrganisationDTO, @@ -23,7 +24,6 @@ from backend.models.postgis.task import Task from backend.models.postgis.team import TeamVisibility from backend.models.postgis.statuses import ProjectStatus, TaskStatus -from backend.models.postgis.utils import NotFound from backend.services.users.user_service import UserService @@ -41,7 +41,9 @@ def get_organisation_by_id(organisation_id: int) -> Organisation: org = Organisation.get(organisation_id) if org is None: - raise NotFound() + raise NotFound( + sub_code="ORGANISATION_NOT_FOUND", organisation_id=organisation_id + ) return org @@ -49,18 +51,20 @@ def get_organisation_by_id(organisation_id: int) -> Organisation: def get_organisation_by_id_as_dto( organisation_id: int, user_id: int, abbreviated: bool ): - org = Organisation.get(organisation_id) + org = OrganisationService.get_organisation_by_id(organisation_id) return OrganisationService.get_organisation_dto(org, user_id, abbreviated) @staticmethod def get_organisation_by_slug_as_dto(slug: str, user_id: int, abbreviated: bool): org = Organisation.query.filter_by(slug=slug).first() + if org is None: + raise NotFound(sub_code="ORGANISATION_NOT_FOUND", slug=slug) return OrganisationService.get_organisation_dto(org, user_id, abbreviated) @staticmethod def get_organisation_dto(org, user_id: int, abbreviated: bool): if org is None: - raise NotFound() + raise NotFound(sub_code="ORGANISATION_NOT_FOUND") organisation_dto = org.as_dto(abbreviated) if user_id != 0: @@ -89,7 +93,9 @@ def get_organisation_by_name(organisation_name: str) -> Organisation: organisation = Organisation.get_organisation_by_name(organisation_name) if organisation is None: - raise NotFound() + raise NotFound( + sub_code="ORGANISATION_NOT_FOUND", organisation_name=organisation_name + ) return organisation @@ -192,7 +198,9 @@ def get_projects_by_organisation_id(organisation_id: int) -> Organisation: ) if projects is None: - raise NotFound() + raise NotFound( + sub_code="PROJECTS_NOT_FOUND", organisation_id=organisation_id + ) return projects @@ -286,7 +294,7 @@ def assert_validate_users(organisation_dto: OrganisationDTO): try: admin = UserService.get_user_by_username(user) except NotFound: - raise NotFound(f"User {user} does not exist") + raise NotFound(sub_code="USER_NOT_FOUND", username=user) managers.append(admin.username) @@ -307,7 +315,9 @@ def is_user_an_org_manager(organisation_id: int, user_id: int): org = Organisation.get(organisation_id) if org is None: - raise NotFound() + raise NotFound( + sub_code="ORGANISATION_NOT_FOUND", organisation_id=organisation_id + ) user = UserService.get_user_by_id(user_id) return user in org.managers diff --git a/backend/services/project_admin_service.py b/backend/services/project_admin_service.py index a0df1c8837..13e34bb6fb 100644 --- a/backend/services/project_admin_service.py +++ b/backend/services/project_admin_service.py @@ -3,6 +3,7 @@ import geojson from flask import current_app +from backend.exceptions import NotFound from backend.models.dtos.project_dto import ( DraftProjectDTO, ProjectDTO, @@ -13,7 +14,7 @@ from backend.models.postgis.statuses import TaskCreationMode, TeamRoles from backend.models.postgis.task import TaskHistory, TaskStatus, TaskAction from backend.models.postgis.user import User -from backend.models.postgis.utils import NotFound, InvalidData, InvalidGeoJson +from backend.models.postgis.utils import InvalidData, InvalidGeoJson from backend.services.grid.grid_service import GridService from backend.services.license_service import LicenseService from backend.services.messaging.message_service import MessageService @@ -107,7 +108,7 @@ def _get_project_by_id(project_id: int) -> Project: project = Project.get(project_id) if project is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) return project @@ -203,7 +204,7 @@ def get_all_comments(project_id: int) -> ProjectCommentsDTO: comments = TaskHistory.get_all_comments(project_id) if len(comments.comments) == 0: - raise NotFound("No comments found on project") + raise NotFound(sub_code="COMMENTS_NOT_FOUND", project_id=project_id) return comments @@ -283,7 +284,7 @@ def get_projects_for_admin( @staticmethod def transfer_project_to(project_id: int, transfering_user_id: int, username: str): """Transfers project from old owner (transfering_user_id) to new owner (username)""" - project = Project.get(project_id) + project = ProjectAdminService._get_project_by_id(project_id) new_owner = UserService.get_user_by_username(username) # No operation is required if the new owner is same as old owner if username == project.author.username: @@ -330,7 +331,7 @@ def is_user_action_permitted_on_project( """Is user action permitted on project""" project = Project.get(project_id) if project is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) author_id = project.author_id allowed_roles = [TeamRoles.PROJECT_MANAGER.value] diff --git a/backend/services/project_search_service.py b/backend/services/project_search_service.py index 6f69da1737..ea33b59e78 100644 --- a/backend/services/project_search_service.py +++ b/backend/services/project_search_service.py @@ -7,6 +7,7 @@ from cachetools import TTLCache, cached from backend import db +from backend.exceptions import NotFound from backend.api.utils import validate_date_input from backend.models.dtos.project_dto import ( ProjectSearchDTO, @@ -31,7 +32,6 @@ from backend.models.postgis.organisation import Organisation from backend.models.postgis.task import TaskHistory from backend.models.postgis.utils import ( - NotFound, ST_Intersects, ST_MakeEnvelope, ST_Transform, @@ -177,7 +177,7 @@ def search_projects(search_dto: ProjectSearchDTO, user) -> ProjectSearchResultsD search_dto, user ) if paginated_results.total == 0: - raise NotFound() + raise NotFound(sub_code="PROJECTS_NOT_FOUND") dto = ProjectSearchResultsDTO() dto.results = [ diff --git a/backend/services/project_service.py b/backend/services/project_service.py index b081edfcdb..7d6dd1f7e3 100644 --- a/backend/services/project_service.py +++ b/backend/services/project_service.py @@ -2,6 +2,7 @@ from cachetools import TTLCache, cached from flask import current_app +from backend.exceptions import NotFound from backend.models.dtos.mapping_dto import TaskDTOs from backend.models.dtos.project_dto import ( ProjectDTO, @@ -25,7 +26,6 @@ MappingLevel, ) from backend.models.postgis.task import Task, TaskHistory -from backend.models.postgis.utils import NotFound from backend.services.messaging.smtp_service import SMTPService from backend.services.users.user_service import UserService from backend.services.project_search_service import ProjectSearchService @@ -50,7 +50,7 @@ class ProjectService: def get_project_by_id(project_id: int) -> Project: project = Project.get(project_id) if project is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) return project @@ -58,14 +58,14 @@ def get_project_by_id(project_id: int) -> Project: def exists(project_id: int) -> bool: project = Project.exists(project_id) if project is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) return True @staticmethod def get_project_by_name(project_id: int) -> Project: project = Project.get(project_id) if project is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) return project @@ -78,14 +78,14 @@ def delete_tasks(project_id: int, tasks_ids): # Validate project exists. project = Project.get(project_id) if project is None: - raise NotFound({"project": project_id}) + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) tasks = [{"id": i, "obj": Task.get(i, project_id)} for i in tasks_ids] # In case a task is not found. not_found = [t["id"] for t in tasks if t["obj"] is None] if len(not_found) > 0: - raise NotFound({"tasks": not_found}) + raise NotFound(sub_code="TASK_NOT_FOUND", tasks=not_found) # Delete task one by one. [t["obj"].delete() for t in tasks] @@ -292,7 +292,7 @@ def get_task_details_for_logged_in_user(user_id: int, preferred_locale: str): tasks = Task.get_locked_tasks_details_for_user(user_id) if len(tasks) == 0: - raise NotFound() + raise NotFound(sub_code="TASK_NOT_FOUND") # TODO put the task details in to a DTO dtos = [] @@ -572,7 +572,7 @@ def get_project_teams(project_id: int): project = ProjectService.get_project_by_id(project_id) if project is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) return project.teams @@ -581,7 +581,7 @@ def get_project_organisation(project_id: int) -> Organisation: project = ProjectService.get_project_by_id(project_id) if project is None: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) return project.organisation diff --git a/backend/services/recommendation_service.py b/backend/services/recommendation_service.py index 6714a4431a..a53289744c 100644 --- a/backend/services/recommendation_service.py +++ b/backend/services/recommendation_service.py @@ -6,11 +6,11 @@ from cachetools import TTLCache, cached from backend import db +from backend.exceptions import NotFound from backend.models.postgis.project import Project, Interest, project_interests from backend.models.postgis.statuses import ProjectStatus from backend.models.dtos.project_dto import ProjectSearchResultsDTO from backend.services.project_search_service import ProjectSearchService -from backend.models.postgis.utils import NotFound from backend.services.users.user_service import UserService similar_projects_cache = TTLCache(maxsize=1000, ttl=60 * 60 * 24) # 24 hours @@ -200,7 +200,7 @@ def get_similar_projects( target_project and target_project.status == ProjectStatus.PUBLISHED.value ) if not project_is_published: - raise NotFound() + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) projects_df = ProjectRecommendationService.create_project_matrix() target_project_df = projects_df[projects_df["id"] == project_id] diff --git a/backend/services/stats_service.py b/backend/services/stats_service.py index 74a2a28996..900ab0d2f9 100644 --- a/backend/services/stats_service.py +++ b/backend/services/stats_service.py @@ -5,6 +5,7 @@ from sqlalchemy.types import Time from backend import db +from backend.exceptions import NotFound from backend.models.dtos.stats_dto import ( ProjectContributionsDTO, UserContribution, @@ -28,7 +29,7 @@ from backend.models.postgis.project import Project from backend.models.postgis.statuses import TaskStatus, MappingLevel, UserGender from backend.models.postgis.task import TaskHistory, User, Task, TaskAction -from backend.models.postgis.utils import timestamp, NotFound # noqa: F401 +from backend.models.postgis.utils import timestamp # noqa: F401 from backend.services.project_service import ProjectService from backend.services.project_search_service import ProjectSearchService from backend.services.users.user_service import UserService @@ -119,7 +120,7 @@ def get_latest_activity(project_id: int, page: int) -> ProjectActivityDTO: """Gets all the activity on a project""" if not ProjectService.exists(project_id): - raise NotFound + raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id) results = ( db.session.query( diff --git a/backend/services/team_service.py b/backend/services/team_service.py index b1df1f3329..292360e3b8 100644 --- a/backend/services/team_service.py +++ b/backend/services/team_service.py @@ -3,6 +3,7 @@ from markdown import markdown from backend import create_app, db +from backend.exceptions import NotFound from backend.models.dtos.team_dto import ( TeamDTO, NewTeamDTO, @@ -18,7 +19,6 @@ from backend.models.postgis.team import Team, TeamMembers from backend.models.postgis.project import ProjectTeams from backend.models.postgis.project_info import ProjectInfo -from backend.models.postgis.utils import NotFound from backend.models.postgis.statuses import ( TeamJoinMethod, TeamMemberFunctions, @@ -145,7 +145,7 @@ def accept_reject_join_request(team_id, from_user_id, username, function, action team = TeamService.get_team_by_id(team_id) if not TeamService.is_user_team_member(team_id, to_user_id): - raise NotFound("Join request not found") + raise NotFound(sub_code="JOIN_REQUEST_NOT_FOUND", username=username) if action not in ["accept", "reject"]: raise TeamServiceError("Invalid action type") @@ -187,7 +187,11 @@ def leave_team(team_id, username): user = UserService.get_user_by_username(username) team_member = TeamMembers.query.filter( TeamMembers.team_id == team_id, TeamMembers.user_id == user.id - ).one() + ).one_or_none() + if not team_member: + raise NotFound( + sub_code="USER_NOT_IN_TEAM", username=username, team_id=team_id + ) team_member.delete() @staticmethod @@ -333,7 +337,7 @@ def get_team_as_dto( team = TeamService.get_team_by_id(team_id) if team is None: - raise NotFound() + raise NotFound(sub_code="TEAM_NOT_FOUND", team_id=team_id) team_dto = TeamDetailsDTO() team_dto.team_id = team.id @@ -383,7 +387,7 @@ def get_projects_by_team_id(team_id: int): ) if projects is None: - raise NotFound() + raise NotFound(sub_code="PROJECTS_NOT_FOUND", team_id=team_id) return projects @@ -425,7 +429,7 @@ def get_team_by_id(team_id: int) -> Team: team = Team.get(team_id) if team is None: - raise NotFound() + raise NotFound(sub_code="TEAM_NOT_FOUND", team_id=team_id) return team @@ -434,7 +438,7 @@ def get_team_by_name(team_name: str) -> Team: team = Team.get_team_by_name(team_name) if team is None: - raise NotFound() + raise NotFound(sub_code="TEAM_NOT_FOUND", team_name=team_name) return team @@ -482,7 +486,7 @@ def assert_validate_members(team_dto: TeamDTO): try: UserService.get_user_by_username(member["name"]) except NotFound: - raise NotFound(f'User {member["name"]} does not exist') + raise NotFound(sub_code="USER_NOT_FOUND", username=member["name"]) if member["function"] == TeamMemberFunctions.MANAGER.name: managers += 1 diff --git a/backend/services/users/user_service.py b/backend/services/users/user_service.py index 88f4122bfc..a2c2550874 100644 --- a/backend/services/users/user_service.py +++ b/backend/services/users/user_service.py @@ -3,6 +3,8 @@ import datetime from sqlalchemy.sql.expression import literal from sqlalchemy import func, or_, desc, and_, distinct, cast, Time, column + +from backend.exceptions import NotFound from backend import db from backend.models.dtos.project_dto import ProjectFavoritesDTO, ProjectSearchResultsDTO from backend.models.dtos.user_dto import ( @@ -26,7 +28,6 @@ from backend.models.dtos.user_dto import UserTaskDTOs from backend.models.dtos.stats_dto import Pagination from backend.models.postgis.statuses import TaskStatus, ProjectStatus -from backend.models.postgis.utils import NotFound from backend.services.users.osm_service import OSMService, OSMServiceError from backend.services.messaging.smtp_service import SMTPService from backend.services.messaging.template_service import ( @@ -52,7 +53,7 @@ def get_user_by_id(user_id: int) -> User: user = User.get_by_id(user_id) if user is None: - raise NotFound() + raise NotFound(sub_code="USER_NOT_FOUND", user_id=user_id) return user @@ -61,7 +62,7 @@ def get_user_by_username(username: str) -> User: user = User.get_by_username(username) if user is None: - raise NotFound() + raise NotFound(sub_code="USER_NOT_FOUND", username=username) return user @@ -94,7 +95,7 @@ def get_project_managers() -> User: users = User.query.filter(User.role == 2).all() if users is None: - raise NotFound() + raise NotFound(sub_code="USER_NOT_FOUND") return users @@ -103,7 +104,7 @@ def get_general_admins() -> User: users = User.query.filter(User.role == 1).all() if users is None: - raise NotFound() + raise NotFound(sub_code="USER_NOT_FOUND") return users @@ -625,7 +626,7 @@ def get_recommended_projects(user_name: str, preferred_locale: str): .one_or_none() ) if user is None: - raise NotFound() + raise NotFound(sub_code="USER_NOT_FOUND", username=user_name) # Get all projects that the user has contributed sq = ( diff --git a/backend/services/validator_service.py b/backend/services/validator_service.py index f91d393304..598839c993 100644 --- a/backend/services/validator_service.py +++ b/backend/services/validator_service.py @@ -1,6 +1,7 @@ from flask import current_app from sqlalchemy import text +from backend.exceptions import NotFound from backend.models.dtos.mapping_dto import TaskDTOs from backend.models.dtos.stats_dto import Pagination from backend.models.dtos.validator_dto import ( @@ -20,7 +21,7 @@ TaskInvalidationHistory, TaskMappingIssue, ) -from backend.models.postgis.utils import NotFound, UserLicenseError, timestamp +from backend.models.postgis.utils import UserLicenseError, timestamp from backend.models.postgis.project_info import ProjectInfo from backend.services.messaging.message_service import MessageService from backend.services.project_service import ProjectService, ProjectAdminService @@ -50,7 +51,11 @@ def lock_tasks_for_validation(validation_dto: LockForValidationDTO) -> TaskDTOs: task = Task.get(task_id, validation_dto.project_id) if task is None: - raise NotFound(f"Task {task_id} not found") + raise NotFound( + sub_code="TASK_NOT_FOUND", + task_id=task_id, + project_id=validation_dto.project_id, + ) if not ( task.locked_by == validation_dto.user_id and TaskStatus(task.task_status) == TaskStatus.LOCKED_FOR_VALIDATION @@ -254,7 +259,11 @@ def get_tasks_locked_by_user(project_id: int, unlock_tasks, user_id: int): task = Task.get(unlock_task.task_id, project_id) if task is None: - raise NotFound(f"Task {unlock_task.task_id} not found") + raise NotFound( + sub_code="TASK_NOT_FOUND", + task_id=unlock_task.task_id, + project_id=project_id, + ) current_state = TaskStatus(task.task_status) if current_state != TaskStatus.LOCKED_FOR_VALIDATION: diff --git a/manage.py b/manage.py index 6a3366945f..acd5cae448 100644 --- a/manage.py +++ b/manage.py @@ -3,16 +3,15 @@ import base64 import csv import datetime - import click from flask_migrate import Migrate from dotenv import load_dotenv + from backend import create_app, initialise_counters, db from backend.services.users.authentication_service import AuthenticationService from backend.services.users.user_service import UserService from backend.services.stats_service import StatsService from backend.services.interests_service import InterestService -from backend.models.postgis.utils import NotFound from backend.models.postgis.task import Task, TaskHistory from sqlalchemy import func @@ -110,6 +109,8 @@ def refresh_project_stats(): @application.cli.command("update_project_categories") @click.argument("filename") def update_project_categories(filename): + from backend.exceptions import NotFound + with open(filename, "r", encoding="ISO-8859-1", newline="") as csvfile: reader = csv.DictReader(csvfile) for row in reader: diff --git a/tests/backend/integration/api/campaigns/test_resources.py b/tests/backend/integration/api/campaigns/test_resources.py index 8a9b89296c..0e2cfbf154 100644 --- a/tests/backend/integration/api/campaigns/test_resources.py +++ b/tests/backend/integration/api/campaigns/test_resources.py @@ -7,10 +7,13 @@ return_canned_campaign, ) from backend.models.postgis.statuses import UserRole +from backend.exceptions import get_message_from_sub_code CAMPAIGN_NAME = "Test Campaign" CAMPAIGN_ID = 1 NEW_CAMPAIGN_NAME = "New Campaign" +CAMPAIGN_NOT_FOUND_SUB_CODE = "CAMPAIGN_NOT_FOUND" +CAMPAIGN_NOT_FOUND_MESSAGE = get_message_from_sub_code(CAMPAIGN_NOT_FOUND_SUB_CODE) class TestCampaignsRestAPI(BaseTestCase): @@ -43,9 +46,11 @@ def test_get_non_existent_campaign_by_id_fails(self): """ response = self.client.get(f"{self.endpoint_url}99/") response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "No campaign found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], CAMPAIGN_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], CAMPAIGN_NOT_FOUND_SUB_CODE) + self.assertEqual(error_details["details"], {"campaign_id": 99}) # patch def test_update_existent_campaign_by_admin_passes(self): @@ -133,9 +138,11 @@ def test_update_non_existent_campaign_by_id_fails(self): headers={"Authorization": self.admin_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "Campaign not found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], CAMPAIGN_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], CAMPAIGN_NOT_FOUND_SUB_CODE) + self.assertEqual(error_details["details"], {"campaign_id": 99}) # delete def test_delete_campaign_by_admin_passes(self): @@ -180,11 +187,10 @@ def test_delete_non_existent_campaign_fails(self): f"{self.endpoint_url}99/", headers={"Authorization": self.admin_token} ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual( - response_body, {"Error": "Campaign not found", "SubCode": "NotFound"} - ) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], CAMPAIGN_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], CAMPAIGN_NOT_FOUND_SUB_CODE) class TestCampaignsAllAPI(BaseTestCase): diff --git a/tests/backend/integration/api/comments/test_resources.py b/tests/backend/integration/api/comments/test_resources.py index 0815384210..4bb5603b00 100644 --- a/tests/backend/integration/api/comments/test_resources.py +++ b/tests/backend/integration/api/comments/test_resources.py @@ -6,13 +6,19 @@ generate_encoded_token, return_canned_user, ) -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound, get_message_from_sub_code from backend.models.postgis.statuses import UserRole from backend.services.messaging.chat_service import ChatService, ChatMessageDTO from backend.services.messaging.message_service import MessageService TEST_MESSAGE = "Test comment" +PROJECT_NOT_FOUND_SUB_CODE = "PROJECT_NOT_FOUND" +TASK_NOT_FOUND_SUB_CODE = "TASK_NOT_FOUND" +MESSAGE_NOT_FOUND_SUB_CODE = "MESSAGE_NOT_FOUND" +PROJECT_NOT_FOUND_MESSAGE = get_message_from_sub_code(PROJECT_NOT_FOUND_SUB_CODE) +TASK_NOT_FOUND_MESSAGE = get_message_from_sub_code(TASK_NOT_FOUND_SUB_CODE) +MESSAGE_NOT_FOUND_MESSAGE = get_message_from_sub_code(MESSAGE_NOT_FOUND_SUB_CODE) class TestCommentsProjectsAllAPI(BaseTestCase): @@ -104,9 +110,10 @@ def test_get_chat_messages_of_non_existent_project_fails(self): """ response = self.client.get(self.non_existent_url) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "Project not found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], PROJECT_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_get_project_chat_messages_passes(self): """ @@ -168,10 +175,11 @@ def test_delete_non_existent_comment_fails(self): self.non_existent_url, headers={"Authorization": self.test_author_token} ) response_body = response.get_json() + error_details = response_body["error"] # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "Comment not found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], MESSAGE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], MESSAGE_NOT_FOUND_SUB_CODE) def test_returns_403_if_user_not_allowed_to_delete_comment(self): """ @@ -288,9 +296,10 @@ def test_post_comment_to_task_chat_of_non_existent_project_fails(self): json={"comment": TEST_MESSAGE}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "Task Not Found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], PROJECT_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_post_comment_to_task_chat_passes(self): """ diff --git a/tests/backend/integration/api/interests/test_resources.py b/tests/backend/integration/api/interests/test_resources.py index 814b8ca2f0..2fd7c4d885 100644 --- a/tests/backend/integration/api/interests/test_resources.py +++ b/tests/backend/integration/api/interests/test_resources.py @@ -6,10 +6,12 @@ generate_encoded_token, create_canned_user, ) -from backend.api.interests.resources import INTEREST_NOT_FOUND +from backend.exceptions import get_message_from_sub_code TEST_INTEREST_NAME = "test_interest" NEW_INTEREST_NAME = "New Interest" +INTEREST_NOT_FOUND_SUB_CODE = "INTEREST_NOT_FOUND" +INTEREST_NOT_FOUND_MESSAGE = get_message_from_sub_code(INTEREST_NOT_FOUND_SUB_CODE) class TestInterestsAllAPI(BaseTestCase): @@ -168,9 +170,10 @@ def test_get_a_non_existent_interest_fails(self): headers={"Authorization": self.session_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], INTEREST_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], INTEREST_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], INTEREST_NOT_FOUND_SUB_CODE) def test_get_an_existing_interest_by_organisation_admin_passes(self): """ @@ -226,9 +229,10 @@ def test_update_a_non_existent_interest_fails(self): json={"name": NEW_INTEREST_NAME}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], INTEREST_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], INTEREST_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], INTEREST_NOT_FOUND_SUB_CODE) def test_update_an_existent_interest_with_invalid_data_fails(self): """ @@ -311,9 +315,10 @@ def test_delete_a_non_existent_interest_fails(self): headers={"Authorization": self.session_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], INTEREST_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], INTEREST_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], INTEREST_NOT_FOUND_SUB_CODE) def test_delete_an_existing_interest_by_organisation_admin_passes(self): """ diff --git a/tests/backend/integration/api/issues/test_resources.py b/tests/backend/integration/api/issues/test_resources.py index 7c11eb2780..99e6e57d60 100644 --- a/tests/backend/integration/api/issues/test_resources.py +++ b/tests/backend/integration/api/issues/test_resources.py @@ -5,9 +5,12 @@ create_canned_mapping_issue, ) +from backend.exceptions import get_message_from_sub_code + TEST_ISSUE_NAME = "Test Issue" TEST_ISSUE_DESCRIPTION = "Test issue description" -ISSUE_NOT_FOUND = "Mapping-issue category not found" +ISSUE_NOT_FOUND_SUB_CODE = "ISSUE_CATEGORY_NOT_FOUND" +ISSUE_NOT_FOUND_MESSAGE = get_message_from_sub_code(ISSUE_NOT_FOUND_SUB_CODE) class TestIssuesRestAPI(BaseTestCase): @@ -35,9 +38,10 @@ def test_get_non_existent_issue_fails(self): """ response = self.client.get(self.non_existent_url) response_json = response.get_json() + error_details = response_json["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_json["Error"], ISSUE_NOT_FOUND) - self.assertEqual(response_json["SubCode"], "NotFound") + self.assertEqual(error_details["message"], ISSUE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], ISSUE_NOT_FOUND_SUB_CODE) # patch def test_update_issue_by_unauthenticated_user_fails(self): @@ -78,9 +82,10 @@ def test_update_non_existent_issue_fails(self): json={"description": TEST_ISSUE_DESCRIPTION, "name": TEST_ISSUE_NAME}, ) response_json = response.get_json() + error_details = response_json["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_json["Error"], ISSUE_NOT_FOUND) - self.assertEqual(response_json["SubCode"], "NotFound") + self.assertEqual(error_details["message"], ISSUE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], ISSUE_NOT_FOUND_SUB_CODE) def test_update_mapping_issue_passes(self): """ @@ -120,9 +125,10 @@ def test_delete_non_existent_issue_fails(self): self.non_existent_url, headers={"Authorization": self.test_user_token} ) response_json = response.get_json() + error_details = response_json["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_json["Error"], ISSUE_NOT_FOUND) - self.assertEqual(response_json["SubCode"], "NotFound") + self.assertEqual(error_details["message"], ISSUE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], ISSUE_NOT_FOUND_SUB_CODE) def test_delete_mapping_issue_passes(self): """ diff --git a/tests/backend/integration/api/licenses/test_actions.py b/tests/backend/integration/api/licenses/test_actions.py index 8cf313fa2a..472ade7c40 100644 --- a/tests/backend/integration/api/licenses/test_actions.py +++ b/tests/backend/integration/api/licenses/test_actions.py @@ -5,6 +5,12 @@ create_canned_license, ) +from backend.exceptions import get_message_from_sub_code + + +LICENSE_NOT_FOUND_SUB_CODE = "LICENSE_NOT_FOUND" +LICENSE_NOT_FOUND_MESSAGE = get_message_from_sub_code(LICENSE_NOT_FOUND_SUB_CODE) + class TestLicensesActionsAcceptAPI(BaseTestCase): def setUp(self): @@ -34,9 +40,10 @@ def test_accept_terms_of_non_existent_license_fails(self): headers={"Authorization": self.test_user_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "User or License not found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], LICENSE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], LICENSE_NOT_FOUND_SUB_CODE) def test_accept_license_terms_by_authenticated_user_passes(self): """ diff --git a/tests/backend/integration/api/licenses/test_resources.py b/tests/backend/integration/api/licenses/test_resources.py index c51772bc4f..f46639837f 100644 --- a/tests/backend/integration/api/licenses/test_resources.py +++ b/tests/backend/integration/api/licenses/test_resources.py @@ -5,12 +5,15 @@ create_canned_license, ) +from backend.exceptions import get_message_from_sub_code + TEST_LICENSE_NAME = "test_license" TEST_LICENSE_DESCRIPTION = "test license" TEST_LICENSE_PLAINTEXT = "test license" NEW_LICENSE_DESCRIPTION = "A new test license" NEW_LICENSE_NAME = "New License" -LICENSE_NOT_FOUND = "License Not Found" +LICENSE_NOT_FOUND_SUB_CODE = "LICENSE_NOT_FOUND" +LICENSE_NOT_FOUND_MESSAGE = get_message_from_sub_code(LICENSE_NOT_FOUND_SUB_CODE) class TestLicensesRestAPI(BaseTestCase): @@ -81,9 +84,10 @@ def test_get_non_existent_license_fails(self): """ response = self.client.get(self.non_existent_url) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], LICENSE_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], LICENSE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], LICENSE_NOT_FOUND_SUB_CODE) def test_get_license_passes(self): """ @@ -143,9 +147,10 @@ def test_update_non_existent_license_by_authenticated_user_fails(self): }, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], LICENSE_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], LICENSE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], LICENSE_NOT_FOUND_SUB_CODE) def test_update_license_by_authenticated_user_passes(self): """ @@ -189,9 +194,10 @@ def test_delete_non_existent_license_by_authenticated_user_fails(self): headers={"Authorization": self.test_user_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], LICENSE_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], LICENSE_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], LICENSE_NOT_FOUND_SUB_CODE) def test_delete_license_by_authenticated_user_passes(self): """ diff --git a/tests/backend/integration/api/notifications/test_resources.py b/tests/backend/integration/api/notifications/test_resources.py index 01a9817c7d..07f0d55417 100644 --- a/tests/backend/integration/api/notifications/test_resources.py +++ b/tests/backend/integration/api/notifications/test_resources.py @@ -11,10 +11,12 @@ create_canned_project, ) +from backend.exceptions import get_message_from_sub_code + TEST_SUBJECT = "Test subject" TEST_MESSAGE = "This is a test message" -MESSAGES_NOT_FOUND = "No messages found" -NOT_FOUND = "NotFound" +NOT_FOUND_SUB_CODE = "MESSAGE_NOT_FOUND" +NOT_FOUND_MESSAGE = get_message_from_sub_code(NOT_FOUND_SUB_CODE) OLDER_TEST_SUBJECT = "Older Test Subject" OLDER_TEST_MESSAGE = "This is an older test message" @@ -66,9 +68,10 @@ def test_get_message_returns_404(self): headers={"Authorization": self.test_sender_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], MESSAGES_NOT_FOUND) - self.assertEqual(response_body["SubCode"], NOT_FOUND) + self.assertEqual(error_details["message"], NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], NOT_FOUND_SUB_CODE) def test_get_message_returns_200(self): """ @@ -114,9 +117,10 @@ def test_delete_message_returns_404(self): headers={"Authorization": self.test_sender_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], MESSAGES_NOT_FOUND) - self.assertEqual(response_body["SubCode"], NOT_FOUND) + self.assertEqual(error_details["message"], NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], NOT_FOUND_SUB_CODE) def test_delete_message_returns_200(self): """ diff --git a/tests/backend/integration/api/organisations/test_campaigns.py b/tests/backend/integration/api/organisations/test_campaigns.py index 4a90eb263b..b0e1aeb861 100644 --- a/tests/backend/integration/api/organisations/test_campaigns.py +++ b/tests/backend/integration/api/organisations/test_campaigns.py @@ -6,6 +6,10 @@ return_canned_user, return_canned_campaign, ) +from tests.backend.integration.api.campaigns.test_resources import ( + CAMPAIGN_NOT_FOUND_MESSAGE, + CAMPAIGN_NOT_FOUND_SUB_CODE, +) CAMPAIGN_NAME = "New Campaign" CAMPAIGN_ID = 2 @@ -152,6 +156,7 @@ def test_delete_non_existent_organisation_campaign_fails(self): headers={"Authorization": self.test_author_session_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "Organisation Campaign Not Found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], CAMPAIGN_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], CAMPAIGN_NOT_FOUND_SUB_CODE) diff --git a/tests/backend/integration/api/organisations/test_resources.py b/tests/backend/integration/api/organisations/test_resources.py index 148b046f16..8aec9f9f65 100644 --- a/tests/backend/integration/api/organisations/test_resources.py +++ b/tests/backend/integration/api/organisations/test_resources.py @@ -9,12 +9,15 @@ generate_encoded_token, return_canned_user, ) + +from backend.exceptions import get_message_from_sub_code from backend.services.users.authentication_service import AuthenticationService TEST_USER_ID = 777777 TEST_USERNAME = "Thinkwhere Test" -ORG_NOT_FOUND = "Organisation Not Found" +ORG_NOT_FOUND_SUB_CODE = "ORGANISATION_NOT_FOUND" +ORG_NOT_FOUND_MESSAGE = get_message_from_sub_code(ORG_NOT_FOUND_SUB_CODE) class TestOrganisationAllAPI(BaseTestCase): @@ -164,10 +167,10 @@ def test_get_non_existent_org_by_slug_fails(self): """ response = self.client.get("/api/v2/organisations/random-organisation/") response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(len(response_body), 2) - self.assertEqual(response_body["Error"], ORG_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], ORG_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], ORG_NOT_FOUND_SUB_CODE) class TestOrganisationsRestAPI(BaseTestCase): @@ -239,9 +242,10 @@ def test_get_non_existent_org_fails(self): headers={"Authorization": self.session_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], ORG_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], ORG_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], ORG_NOT_FOUND_SUB_CODE) # delete method tests def test_delete_org_by_admin_user_passes(self): @@ -383,7 +387,8 @@ def test_get_non_existent_org_statistics_fails(self): Tests that endpoint returns 404 when retrieving a non existent organisation's statistics """ response = self.client.get("/api/v2/organisations/99/statistics/") - self.assertEqual(response.status_code, 404) response_body = response.get_json() - self.assertEqual(response_body["Error"], ORG_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + error_details = response_body["error"] + self.assertEqual(response.status_code, 404) + self.assertEqual(error_details["message"], ORG_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], ORG_NOT_FOUND_SUB_CODE) diff --git a/tests/backend/integration/api/projects/test_resources.py b/tests/backend/integration/api/projects/test_resources.py index 55628457b1..5cbf1b267d 100644 --- a/tests/backend/integration/api/projects/test_resources.py +++ b/tests/backend/integration/api/projects/test_resources.py @@ -19,7 +19,7 @@ create_canned_interest, update_project_with_info, ) -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound from backend.models.postgis.project import Project, ProjectDTO from backend.models.postgis.task import Task from backend.services.campaign_service import CampaignService diff --git a/tests/backend/integration/api/system/test_authentication.py b/tests/backend/integration/api/system/test_authentication.py index 08ce11b5cb..62ff06316c 100644 --- a/tests/backend/integration/api/system/test_authentication.py +++ b/tests/backend/integration/api/system/test_authentication.py @@ -10,7 +10,10 @@ from backend.services.messaging.smtp_service import SMTPService from backend.services.users.authentication_service import AuthenticationService from tests.backend.helpers.test_helpers import return_canned_user - +from tests.backend.integration.api.users.test_resources import ( + USER_NOT_FOUND_MESSAGE, + USER_NOT_FOUND_SUB_CODE, +) USERNAME = "test_user" USER_ID = 1234 @@ -201,10 +204,11 @@ def test_returns_404_if_user_does_not_exist(self): response = self.client.get( self.url, query_string={"username": "non_existent_user"} ) + error_details = response.json["error"] # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "UserNotFound") - self.assertEqual(response.json["Error"], "User not found") + self.assertEqual(error_details["sub_code"], USER_NOT_FOUND_SUB_CODE) + self.assertEqual(error_details["message"], USER_NOT_FOUND_MESSAGE) def test_returns_403_if_invalid_email_token(self): # Arrange diff --git a/tests/backend/integration/api/tasks/test_actions.py b/tests/backend/integration/api/tasks/test_actions.py index 9c0bf3454e..4d7bb034aa 100644 --- a/tests/backend/integration/api/tasks/test_actions.py +++ b/tests/backend/integration/api/tasks/test_actions.py @@ -18,9 +18,17 @@ generate_encoded_token, create_canned_license, ) +from tests.backend.integration.api.users.test_resources import ( + USER_NOT_FOUND_SUB_CODE, + USER_NOT_FOUND_MESSAGE, +) from backend.models.postgis.task import Task, TaskAction +PROJECT_NOT_FOUND_SUB_CODE = "PROJECT_NOT_FOUND" +TASK_NOT_FOUND_SUB_CODE = "TASK_NOT_FOUND" + + class TasksActionsMapAllAPI(BaseTestCase): def setUp(self): super().setUp() @@ -344,7 +352,7 @@ def test_mapping_lock_returns_404_for_invalid_project_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_mapping_lock_returns_404_for_invalid_task_id(self): """Test returns 404 on request with invalid task id.""" @@ -355,7 +363,7 @@ def test_mapping_lock_returns_404_for_invalid_task_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) @patch.object(ProjectService, "is_user_permitted_to_map") def test_mapping_lock_returns_403_for_if_user_not_allowed_to_map( @@ -489,7 +497,7 @@ def test_mapping_unlock_returns_404_for_invalid_project_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_mapping_unlock_returns_404_for_invalid_task_id(self): """Test returns 404 on request with invalid task id.""" @@ -501,7 +509,7 @@ def test_mapping_unlock_returns_404_for_invalid_task_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_mapping_unlock_returns_403_if_task_not_locked_for_mapping(self): """Test returns 403 if task is not locked for mapping.""" @@ -632,7 +640,7 @@ def test_mapping_stop_returns_404_for_invalid_project_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_mapping_stop_returns_404_for_invalid_task_id(self): """Test returns 404 on request with invalid task id.""" @@ -643,7 +651,7 @@ def test_mapping_stop_returns_404_for_invalid_task_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_mapping_stop_returns_403_if_task_not_locked_for_mapping(self): """Test returns 403 if task not locked for mapping.""" @@ -756,7 +764,7 @@ def test_validation_lock_returns_404_for_invalid_project_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_validation_lock_returns_404_for_invalid_task_id(self): """Test returns 404 on request with invalid task id.""" @@ -768,7 +776,7 @@ def test_validation_lock_returns_404_for_invalid_task_id(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_validation_lock_returns_403_if_task_not_ready_for_validation(self): """Test returns 403 if task not ready for validation.""" @@ -954,7 +962,7 @@ def test_validation_unlock_returns_404_if_project_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_validation_unlock_returns_404_if_task_not_found(self): """Test returns 404 if task not found.""" @@ -966,7 +974,7 @@ def test_validation_unlock_returns_404_if_task_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_validation_unlock_returns_403_if_task_not_locked_for_validation(self): """Test returns 403 if task not locked for validation.""" @@ -1138,7 +1146,7 @@ def test_validation_stop_returns_404_if_project_not_found(self): # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_validation_stop_returns_404_if_task_not_found(self): """Test returns 404 if task not found.""" @@ -1150,7 +1158,7 @@ def test_validation_stop_returns_404_if_task_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_validation_stop_returns_403_if_task_not_locked_for_validation(self): """Test returns 403 if task not locked for validation.""" @@ -1262,7 +1270,7 @@ def test_returns_404_if_project_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_returns_404_if_task_not_found(self): """Test returns 404 if task not found.""" @@ -1273,7 +1281,7 @@ def test_returns_404_if_task_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_returns_403_if_task_too_small_to_split(self): """Test returns 403 if task too small to split.""" @@ -1363,7 +1371,7 @@ def test_returns_404_if_project_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_returns_404_if_task_not_found(self): """Test returns 404 if task not found.""" @@ -1374,7 +1382,7 @@ def test_returns_404_if_task_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_returns_403_if_task_in_invalid_state_for_undo(self): """Test returns 403 if task in invalid state for undo.""" @@ -1523,7 +1531,7 @@ def test_returns_404_if_project_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_returns_404_if_task_not_found(self): """Test returns 404 if task not found.""" @@ -1535,7 +1543,7 @@ def test_returns_404_if_task_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], TASK_NOT_FOUND_SUB_CODE) def test_returns_403_if_task_not_locked(self): """Test returns 403 if task not locked.""" @@ -1612,10 +1620,11 @@ def test_returns_404_if_user_not_found(self): headers={"Authorization": self.author_access_token}, query_string={"username": "invalid_user", "action": "VALIDATED"}, ) + error_details = response.json["error"] # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") - self.assertEqual(response.json["Error"], "User not found") + self.assertEqual(error_details["sub_code"], USER_NOT_FOUND_SUB_CODE) + self.assertEqual(error_details["message"], USER_NOT_FOUND_MESSAGE) def test_returns_400_if_action_not_valid(self): """Test returns 400 if action not valid.""" @@ -1639,7 +1648,7 @@ def test_returns_404_if_project_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], PROJECT_NOT_FOUND_SUB_CODE) def test_returns_403_if_user_doesnot_have_PM_permission(self): """Test returns 403 if user doesnot have PM permission.""" diff --git a/tests/backend/integration/api/teams/test_actions.py b/tests/backend/integration/api/teams/test_actions.py index 30d3256050..931ba0412c 100644 --- a/tests/backend/integration/api/teams/test_actions.py +++ b/tests/backend/integration/api/teams/test_actions.py @@ -13,13 +13,18 @@ TeamJoinMethod, TeamMemberFunctions, ) -from backend.api.teams.actions import TEAM_NOT_FOUND +from backend.exceptions import get_message_from_sub_code TEST_ADMIN_USERNAME = "Test Admin" TEST_MESSAGE = "This is a test message" TEST_SUBJECT = "Test Subject" NON_EXISTENT_USER = "Random User" +TEAM_NOT_FOUND_SUB_CODE = "TEAM_NOT_FOUND" +USER_NOT_FOUND_SUB_CODE = "USER_NOT_FOUND" +TEAM_NOT_FOUND_MESSAGE = get_message_from_sub_code(TEAM_NOT_FOUND_SUB_CODE) +USER_NOT_FOUND_MESSAGE = get_message_from_sub_code(USER_NOT_FOUND_SUB_CODE) + class TestTeamsActionsJoinAPI(BaseTestCase): def setUp(self): @@ -63,7 +68,7 @@ def test_request_to_join_non_existent_team_fails(self): ) response_body = response.get_json() self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], TEAM_NOT_FOUND) + self.assertEqual(response_body["error"]["message"], TEAM_NOT_FOUND_MESSAGE) def test_request_to_join_team_with_invite_only_request_fails(self): """ @@ -154,9 +159,10 @@ def test_handle_join_request_to_non_existent_team_fails(self): }, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], TEAM_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], TEAM_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], TEAM_NOT_FOUND_SUB_CODE) class TestTeamsActionsAddAPI(BaseTestCase): @@ -252,9 +258,9 @@ def test_add_non_existent_members_to_team_fails(self): headers={"Authorization": self.admin_token}, ) response_body = response.get_json() - self.assertEqual(response.status_code, 500) + self.assertEqual(response.status_code, 404) error_resp = response_body["error"] - self.assertEqual(error_resp["sub_code"], "INTERNAL_SERVER_ERROR") + self.assertEqual(error_resp["sub_code"], "USER_NOT_FOUND") def test_add_members_to_non_existent_team_fails(self): """ @@ -269,10 +275,9 @@ def test_add_members_to_non_existent_team_fails(self): headers={"Authorization": self.admin_token}, ) response_body = response.get_json() - print(response_body) - self.assertEqual(response.status_code, 500) + self.assertEqual(response.status_code, 404) error_resp = response_body["error"] - self.assertEqual(error_resp["sub_code"], "INTERNAL_SERVER_ERROR") + self.assertEqual(error_resp["sub_code"], "TEAM_NOT_FOUND") class TestTeamsActionsLeaveAPI(BaseTestCase): @@ -357,9 +362,10 @@ def test_remove_non_existent_members_from_team_fails(self): headers={"Authorization": self.admin_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "No team member found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], USER_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], USER_NOT_FOUND_SUB_CODE) def test_remove_members_from_non_existent_team_fails(self): """ @@ -373,9 +379,10 @@ def test_remove_members_from_non_existent_team_fails(self): headers={"Authorization": self.admin_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "No team member found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], TEAM_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], TEAM_NOT_FOUND_SUB_CODE) class TestTeamsActionsMessageMembersAPI(BaseTestCase): @@ -403,9 +410,10 @@ def test_message_members_non_existent_team_fails(self): headers={"Authorization": self.admin_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], TEAM_NOT_FOUND) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], TEAM_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], TEAM_NOT_FOUND_SUB_CODE) def test_message_team_members_by_unauthenticated_user_fails(self): """ diff --git a/tests/backend/integration/api/teams/test_resources.py b/tests/backend/integration/api/teams/test_resources.py index a410ea11bb..5ff3baeaa5 100644 --- a/tests/backend/integration/api/teams/test_resources.py +++ b/tests/backend/integration/api/teams/test_resources.py @@ -7,6 +7,16 @@ return_canned_team, create_canned_team, ) +from tests.backend.integration.api.teams.test_actions import ( + TEAM_NOT_FOUND_SUB_CODE, + TEAM_NOT_FOUND_MESSAGE, +) +from tests.backend.integration.api.organisations.test_resources import ( + ORG_NOT_FOUND_MESSAGE, + ORG_NOT_FOUND_SUB_CODE, +) + + from backend.models.postgis.statuses import UserRole TEST_ORGANISATION_NAME = "Kathmandu Living Labs" @@ -32,9 +42,10 @@ def test_get_non_existent_team_by_id_fails(self): """ response = self.client.get("/api/v2/teams/99/") response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "Team Not Found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], TEAM_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], TEAM_NOT_FOUND_SUB_CODE) def test_get_team_by_id_passes(self): """ @@ -143,9 +154,10 @@ def test_delete_non_existent_team_by_id_fails(self): "/api/v2/teams/99/", headers={"Authorization": self.test_user_token} ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual(response_body["Error"], "Team Not Found") - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], TEAM_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], TEAM_NOT_FOUND_SUB_CODE) class TestTeamsAllPI(BaseTestCase): @@ -177,11 +189,10 @@ def test_create_new_team_for_non_existent_org_fails(self): headers={"Authorization": self.admin_token}, ) response_body = response.get_json() + error_details = response_body["error"] self.assertEqual(response.status_code, 404) - self.assertEqual( - response_body["Error"], "Team POST - Organisation does not exist" - ) - self.assertEqual(response_body["SubCode"], "NotFound") + self.assertEqual(error_details["message"], ORG_NOT_FOUND_MESSAGE) + self.assertEqual(error_details["sub_code"], ORG_NOT_FOUND_SUB_CODE) def test_create_new_team_non_admin_fails(self): """ diff --git a/tests/backend/integration/api/users/test_actions.py b/tests/backend/integration/api/users/test_actions.py index a9e9f06e92..0dcc76cfba 100644 --- a/tests/backend/integration/api/users/test_actions.py +++ b/tests/backend/integration/api/users/test_actions.py @@ -279,7 +279,7 @@ def test_returns_404_if_user_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], "USER_NOT_FOUND") def test_returns_200_if_user_role_set(self): """Test that the API returns 200 if user role is set""" diff --git a/tests/backend/integration/api/users/test_resources.py b/tests/backend/integration/api/users/test_resources.py index 5d9f34e9f3..fadd26e264 100644 --- a/tests/backend/integration/api/users/test_resources.py +++ b/tests/backend/integration/api/users/test_resources.py @@ -1,5 +1,7 @@ from backend.models.postgis.task import Task, TaskStatus from backend.models.postgis.statuses import UserGender, UserRole, MappingLevel +from backend.exceptions import get_message_from_sub_code + from tests.backend.base import BaseTestCase from tests.backend.helpers.test_helpers import ( @@ -13,6 +15,8 @@ TEST_USERNAME = "test_user" TEST_USER_ID = 1111111 TEST_EMAIL = "test@hotmail.com" +USER_NOT_FOUND_SUB_CODE = "USER_NOT_FOUND" +USER_NOT_FOUND_MESSAGE = get_message_from_sub_code(USER_NOT_FOUND_SUB_CODE) class TestUsersQueriesOwnLockedDetailsAPI(BaseTestCase): @@ -39,7 +43,7 @@ def test_returns_404_if_no_tasks_locked(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], "TASK_NOT_FOUND") def test_returns_200_if_tasks_locked(self): """Test that the API returns 200 if a task is locked by user""" @@ -84,7 +88,7 @@ def test_returns_404_if_user_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], USER_NOT_FOUND_SUB_CODE) @staticmethod def assert_user_detail_response( @@ -233,7 +237,7 @@ def test_returns_404_if_user_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], USER_NOT_FOUND_SUB_CODE) def test_returns_user_interests_if_interest_found(self): """Test that the API returns user interests if user has interests""" @@ -284,7 +288,7 @@ def test_returns_404_if_no_users_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], USER_NOT_FOUND_SUB_CODE) def test_returns_users_if_users_found(self): """Test that the API returns users if users found""" diff --git a/tests/backend/integration/api/users/test_statistics.py b/tests/backend/integration/api/users/test_statistics.py index 63fc046ac4..d7a095ac5e 100644 --- a/tests/backend/integration/api/users/test_statistics.py +++ b/tests/backend/integration/api/users/test_statistics.py @@ -9,6 +9,7 @@ generate_encoded_token, return_canned_user, ) +from tests.backend.integration.api.users.test_resources import USER_NOT_FOUND_SUB_CODE class TestUsersStatisticsAPI(BaseTestCase): @@ -34,7 +35,7 @@ def test_return_404_if_user_not_found(self): ) # Assert self.assertEqual(response.status_code, 404) - self.assertEqual(response.json["SubCode"], "NotFound") + self.assertEqual(response.json["error"]["sub_code"], USER_NOT_FOUND_SUB_CODE) def test_return_200_if_user_found(self): """Test returns 200 if user found.""" diff --git a/tests/backend/integration/services/messaging/test_chat_service.py b/tests/backend/integration/services/messaging/test_chat_service.py index 2ef1ebf9d0..1a52724718 100644 --- a/tests/backend/integration/services/messaging/test_chat_service.py +++ b/tests/backend/integration/services/messaging/test_chat_service.py @@ -1,7 +1,7 @@ import threading from unittest.mock import patch -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound from backend.models.postgis.statuses import ProjectStatus from backend.models.postgis.team import TeamRoles, TeamMemberFunctions from tests.backend.base import BaseTestCase diff --git a/tests/backend/integration/services/test_favorite_service.py b/tests/backend/integration/services/test_favorite_service.py index 0d1a269f12..ac0813bd79 100644 --- a/tests/backend/integration/services/test_favorite_service.py +++ b/tests/backend/integration/services/test_favorite_service.py @@ -1,4 +1,4 @@ -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound from backend.services.project_service import ProjectService from backend.services.users.user_service import UserService from tests.backend.helpers.test_helpers import create_canned_project diff --git a/tests/backend/integration/services/test_featured_projects_services.py b/tests/backend/integration/services/test_featured_projects_services.py index de40460037..b1333a0f06 100644 --- a/tests/backend/integration/services/test_featured_projects_services.py +++ b/tests/backend/integration/services/test_featured_projects_services.py @@ -1,5 +1,5 @@ +from backend.exceptions import NotFound from backend.services.project_service import ProjectService -from backend.models.postgis.utils import NotFound from tests.backend.helpers.test_helpers import create_canned_project from tests.backend.base import BaseTestCase diff --git a/tests/backend/integration/services/test_interests_service.py b/tests/backend/integration/services/test_interests_service.py index cc03d38e3a..2685b57a8e 100644 --- a/tests/backend/integration/services/test_interests_service.py +++ b/tests/backend/integration/services/test_interests_service.py @@ -1,7 +1,7 @@ +from backend.exceptions import NotFound from backend.services.interests_service import InterestService from tests.backend.base import BaseTestCase from tests.backend.helpers.test_helpers import create_canned_project -from backend.models.postgis.utils import NotFound class TestInterestService(BaseTestCase): diff --git a/tests/backend/integration/services/test_license_service.py b/tests/backend/integration/services/test_license_service.py index 6c4b07dfc6..b5930df167 100644 --- a/tests/backend/integration/services/test_license_service.py +++ b/tests/backend/integration/services/test_license_service.py @@ -1,5 +1,5 @@ +from backend.exceptions import NotFound from backend.services.license_service import LicenseService, LicenseDTO -from backend.models.postgis.utils import NotFound from tests.backend.base import BaseTestCase diff --git a/tests/backend/unit/models/postgis/test_project.py b/tests/backend/unit/models/postgis/test_project.py index 5698852725..c390815b29 100644 --- a/tests/backend/unit/models/postgis/test_project.py +++ b/tests/backend/unit/models/postgis/test_project.py @@ -1,8 +1,9 @@ from unittest.mock import MagicMock from flask import current_app + +from backend.exceptions import NotFound from backend.models.dtos.project_dto import DraftProjectDTO from backend.models.postgis.project import Project -from backend.models.postgis.utils import NotFound from tests.backend.base import BaseTestCase from tests.backend.helpers.test_helpers import ( create_canned_project, diff --git a/tests/backend/unit/services/test_team_service.py b/tests/backend/unit/services/test_team_service.py index 6073943d6e..a45f4ad875 100644 --- a/tests/backend/unit/services/test_team_service.py +++ b/tests/backend/unit/services/test_team_service.py @@ -1,4 +1,4 @@ -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound from backend.services.team_service import TeamService from backend.models.dtos.team_dto import TeamSearchDTO from tests.backend.base import BaseTestCase