From 4118cc9f3519eb6607631b91600c6e3c9155d0df Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Mon, 19 Jun 2023 16:15:06 +0545 Subject: [PATCH 1/7] Apply new NotFound exception replacing the prev impelementation. ------------------------------------ We defined custom HttpExceptions on #5899 to better handle errors but these exceptions were not used anywhere. This commit has replaced the previous NotFound custom exception which was simply a basic exceptio class with the new implementation. --- backend/api/campaigns/resources.py | 2 +- backend/api/comments/resources.py | 2 +- backend/api/interests/resources.py | 2 +- backend/api/issues/resources.py | 2 +- backend/api/licenses/resources.py | 2 +- backend/api/organisations/campaigns.py | 2 +- backend/api/projects/activities.py | 3 +- backend/api/projects/campaigns.py | 2 +- backend/api/projects/favorites.py | 2 +- backend/api/projects/resources.py | 14 -------- backend/api/tasks/actions.py | 3 +- backend/error_messages.json | 21 ++++++++++-- backend/models/postgis/interests.py | 6 ++-- backend/models/postgis/licenses.py | 4 +-- backend/models/postgis/message.py | 9 +++-- backend/models/postgis/organisation.py | 6 ++-- backend/models/postgis/project.py | 13 +++++--- backend/models/postgis/task.py | 15 +++++---- backend/models/postgis/team.py | 6 ++-- backend/models/postgis/user.py | 6 ++-- backend/services/application_service.py | 4 +-- backend/services/campaign_service.py | 27 ++++++++++----- backend/services/grid/split_service.py | 6 ++-- backend/services/mapping_issues_service.py | 4 +-- backend/services/mapping_service.py | 33 ++++++++++++++----- backend/services/messaging/chat_service.py | 14 ++++++-- backend/services/messaging/message_service.py | 5 +-- backend/services/notification_service.py | 4 +-- backend/services/organisation_service.py | 22 +++++++++---- backend/services/project_admin_service.py | 9 ++--- backend/services/project_search_service.py | 4 +-- backend/services/project_service.py | 18 +++++----- backend/services/recommendation_service.py | 4 +-- backend/services/stats_service.py | 5 +-- backend/services/team_service.py | 14 ++++---- backend/services/users/user_service.py | 13 ++++---- backend/services/validator_service.py | 15 +++++++-- manage.py | 5 +-- .../api/comments/test_resources.py | 2 +- .../api/projects/test_resources.py | 2 +- .../services/messaging/test_chat_service.py | 2 +- .../services/test_favorite_service.py | 2 +- .../test_featured_projects_services.py | 2 +- .../services/test_interests_service.py | 2 +- .../services/test_license_service.py | 2 +- .../unit/models/postgis/test_project.py | 3 +- .../unit/services/test_team_service.py | 2 +- 47 files changed, 212 insertions(+), 135 deletions(-) diff --git a/backend/api/campaigns/resources.py b/backend/api/campaigns/resources.py index a7e82bcd27..9a528c3013 100644 --- a/backend/api/campaigns/resources.py +++ b/backend/api/campaigns/resources.py @@ -1,10 +1,10 @@ from flask_restful import Resource, request, current_app from schematics.exceptions import DataError +from backend.exceptions import NotFound 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 diff --git a/backend/api/comments/resources.py b/backend/api/comments/resources.py index 616ae85733..0f45698803 100644 --- a/backend/api/comments/resources.py +++ b/backend/api/comments/resources.py @@ -1,9 +1,9 @@ from flask_restful import Resource, request, current_app from schematics.exceptions import DataError +from backend.exceptions import NotFound 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 diff --git a/backend/api/interests/resources.py b/backend/api/interests/resources.py index 27bbb86b64..8eb5b2ad51 100644 --- a/backend/api/interests/resources.py +++ b/backend/api/interests/resources.py @@ -1,8 +1,8 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError +from backend.exceptions import NotFound 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 diff --git a/backend/api/issues/resources.py b/backend/api/issues/resources.py index b6cb97dc0c..3286399d92 100644 --- a/backend/api/issues/resources.py +++ b/backend/api/issues/resources.py @@ -1,8 +1,8 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError +from backend.exceptions import NotFound 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 diff --git a/backend/api/licenses/resources.py b/backend/api/licenses/resources.py index 79cdc74715..f64d3ca46a 100644 --- a/backend/api/licenses/resources.py +++ b/backend/api/licenses/resources.py @@ -1,8 +1,8 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError +from backend.exceptions import NotFound 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 diff --git a/backend/api/organisations/campaigns.py b/backend/api/organisations/campaigns.py index 1a4ff8dcf7..b68e80aac8 100644 --- a/backend/api/organisations/campaigns.py +++ b/backend/api/organisations/campaigns.py @@ -1,8 +1,8 @@ from flask_restful import Resource +from backend.exceptions import NotFound 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 diff --git a/backend/api/projects/activities.py b/backend/api/projects/activities.py index 6966508c0e..0011d4bbcb 100644 --- a/backend/api/projects/activities.py +++ b/backend/api/projects/activities.py @@ -1,7 +1,8 @@ from flask_restful import Resource, current_app, request + +from backend.exceptions import NotFound from backend.services.stats_service import StatsService from backend.services.project_service import ProjectService -from backend.models.postgis.utils import NotFound class ProjectsActivitiesAPI(Resource): diff --git a/backend/api/projects/campaigns.py b/backend/api/projects/campaigns.py index 584cebb026..4c377f999f 100644 --- a/backend/api/projects/campaigns.py +++ b/backend/api/projects/campaigns.py @@ -1,10 +1,10 @@ from flask_restful import Resource, current_app from schematics.exceptions import DataError +from backend.exceptions import NotFound 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 diff --git a/backend/api/projects/favorites.py b/backend/api/projects/favorites.py index f9f13b4dc7..c6945a2c15 100644 --- a/backend/api/projects/favorites.py +++ b/backend/api/projects/favorites.py @@ -1,6 +1,6 @@ from flask_restful import Resource -from backend.models.postgis.utils import NotFound +from backend.exceptions 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 diff --git a/backend/api/projects/resources.py b/backend/api/projects/resources.py index ab439a2202..0a908536e8 100644 --- a/backend/api/projects/resources.py +++ b/backend/api/projects/resources.py @@ -120,13 +120,6 @@ def get(self, project_id): 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: @@ -981,13 +974,6 @@ def get(self, project_id): 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: diff --git a/backend/api/tasks/actions.py b/backend/api/tasks/actions.py index 10fc16230a..864f3569c3 100644 --- a/backend/api/tasks/actions.py +++ b/backend/api/tasks/actions.py @@ -1,8 +1,9 @@ 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 diff --git a/backend/error_messages.json b/backend/error_messages.json index a51888f146..edabb03679 100644 --- a/backend/error_messages.json +++ b/backend/error_messages.json @@ -1,8 +1,25 @@ { - "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.", + "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_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..a70e95c3a1 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) @@ -163,11 +168,15 @@ 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 + ) + + org.campaign.remove(campaign) db.session.commit() new_campaigns = CampaignService.get_organisation_campaigns_as_dto( organisation_id @@ -178,7 +187,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..5cd79e1c28 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( @@ -195,7 +200,11 @@ def add_task_comment(task_comment: TaskCommentDTO) -> TaskDTO: """Adds the comment to the task history""" task = Task.get(task_comment.task_id, task_comment.project_id) if task is None: - raise 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 +259,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 +301,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 +431,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..924200ce0e 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 @@ -122,7 +126,11 @@ def delete_project_chat_by_id(project_id: int, comment_id: int, user_id: int): 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..a937abb20c 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 @@ -60,7 +62,7 @@ def get_organisation_by_slug_as_dto(slug: str, user_id: int, abbreviated: bool): @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 +91,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 +196,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 +292,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 +313,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..89ba141e7c 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 @@ -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..c697ad9811 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") @@ -333,7 +333,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 +383,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 +425,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 +434,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 +482,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/comments/test_resources.py b/tests/backend/integration/api/comments/test_resources.py index 0815384210..99012dc0e6 100644 --- a/tests/backend/integration/api/comments/test_resources.py +++ b/tests/backend/integration/api/comments/test_resources.py @@ -6,7 +6,7 @@ generate_encoded_token, return_canned_user, ) -from backend.models.postgis.utils import NotFound +from backend.exceptions import NotFound from backend.models.postgis.statuses import UserRole from backend.services.messaging.chat_service import ChatService, ChatMessageDTO from backend.services.messaging.message_service import MessageService 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/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 From 8b2f8feafb2ddc9669816daeea3d6b29318bf548 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Thu, 22 Jun 2023 09:27:59 +0545 Subject: [PATCH 2/7] Remove NotFound exception from try except block. ---------------------------------------- We are now using our new NotFound exception, which derives from the werkzeug HTTPException class. We no longer need to use it in the try except block to catch and return error message because Flask will handle this HTTPException automatically. --- backend/api/annotations/resources.py | 42 ++---- backend/api/campaigns/resources.py | 31 ++--- backend/api/comments/resources.py | 25 +--- backend/api/interests/resources.py | 22 +--- backend/api/issues/resources.py | 41 ++---- backend/api/licenses/actions.py | 9 +- backend/api/licenses/resources.py | 29 ++-- backend/api/notifications/resources.py | 5 - backend/api/organisations/campaigns.py | 42 ++---- backend/api/organisations/resources.py | 85 +++++------- backend/api/projects/actions.py | 48 +++---- backend/api/projects/activities.py | 17 +-- backend/api/projects/campaigns.py | 62 ++++----- backend/api/projects/contributions.py | 18 +-- backend/api/projects/favorites.py | 21 +-- backend/api/projects/resources.py | 132 +++++++------------ backend/api/projects/statistics.py | 18 +-- backend/api/projects/teams.py | 6 +- backend/api/system/applications.py | 17 +-- backend/api/system/authentication.py | 3 - backend/api/tasks/actions.py | 23 ---- backend/api/tasks/resources.py | 176 ++++++++++--------------- backend/api/teams/actions.py | 89 ++++++------- backend/api/teams/resources.py | 43 +++--- backend/api/users/actions.py | 21 +-- backend/api/users/openstreetmap.py | 4 +- backend/api/users/resources.py | 79 ++++------- backend/api/users/statistics.py | 16 +-- backend/api/users/tasks.py | 4 +- 29 files changed, 382 insertions(+), 746 deletions(-) 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 9a528c3013..466090ce00 100644 --- a/backend/api/campaigns/resources.py +++ b/backend/api/campaigns/resources.py @@ -1,7 +1,6 @@ from flask_restful import Resource, request, current_app from schematics.exceptions import DataError -from backend.exceptions import NotFound from backend.models.dtos.campaign_dto import CampaignDTO, NewCampaignDTO from backend.services.campaign_service import CampaignService from backend.services.organisation_service import OrganisationService @@ -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 0f45698803..0e031360ef 100644 --- a/backend/api/comments/resources.py +++ b/backend/api/comments/resources.py @@ -1,7 +1,6 @@ from flask_restful import Resource, request, current_app from schematics.exceptions import DataError -from backend.exceptions import NotFound from backend.models.dtos.message_dto import ChatMessageDTO from backend.models.dtos.mapping_dto import TaskCommentDTO from backend.services.messaging.chat_service import ChatService @@ -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 8eb5b2ad51..d1c0a0e1c6 100644 --- a/backend/api/interests/resources.py +++ b/backend/api/interests/resources.py @@ -1,7 +1,6 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError -from backend.exceptions import NotFound from backend.models.dtos.interests_dto import InterestDTO from backend.services.interests_service import InterestService from backend.services.organisation_service import OrganisationService @@ -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 3286399d92..3a60deadf6 100644 --- a/backend/api/issues/resources.py +++ b/backend/api/issues/resources.py @@ -1,7 +1,6 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError -from backend.exceptions import NotFound from backend.models.dtos.mapping_issues_dto import MappingIssueCategoryDTO 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 f64d3ca46a..4b3e78066d 100644 --- a/backend/api/licenses/resources.py +++ b/backend/api/licenses/resources.py @@ -1,7 +1,6 @@ from flask_restful import Resource, current_app, request from schematics.exceptions import DataError -from backend.exceptions import NotFound from backend.models.dtos.licenses_dto import LicenseDTO 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 b68e80aac8..9abaf40440 100644 --- a/backend/api/organisations/campaigns.py +++ b/backend/api/organisations/campaigns.py @@ -1,6 +1,5 @@ from flask_restful import Resource -from backend.exceptions import NotFound from backend.services.campaign_service import CampaignService from backend.services.organisation_service import OrganisationService 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 0011d4bbcb..9d829dfd76 100644 --- a/backend/api/projects/activities.py +++ b/backend/api/projects/activities.py @@ -1,6 +1,5 @@ -from flask_restful import Resource, current_app, request +from flask_restful import Resource, request -from backend.exceptions import NotFound from backend.services.stats_service import StatsService from backend.services.project_service import ProjectService @@ -33,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 @@ -67,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 4c377f999f..6d29087970 100644 --- a/backend/api/projects/campaigns.py +++ b/backend/api/projects/campaigns.py @@ -1,7 +1,6 @@ from flask_restful import Resource, current_app from schematics.exceptions import DataError -from backend.exceptions import NotFound from backend.models.dtos.campaign_dto import CampaignProjectDTO from backend.services.campaign_service import CampaignService from backend.services.project_admin_service import ProjectAdminService @@ -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 c6945a2c15..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.exceptions 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 0a908536e8..3831ccb071 100644 --- a/backend/api/projects/resources.py +++ b/backend/api/projects/resources.py @@ -116,8 +116,6 @@ 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 finally: @@ -255,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): @@ -385,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 @@ -408,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 @@ -464,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): @@ -862,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): @@ -904,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): @@ -970,8 +952,6 @@ 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 finally: @@ -1017,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 @@ -1063,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): @@ -1114,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 @@ -1183,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..8a4da18dc4 100644 --- a/backend/api/projects/teams.py +++ b/backend/api/projects/teams.py @@ -1,7 +1,7 @@ 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.users.authentication_service import token_auth @@ -180,8 +180,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 +234,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 864f3569c3..98fbadeb2d 100644 --- a/backend/api/tasks/actions.py +++ b/backend/api/tasks/actions.py @@ -1,7 +1,6 @@ 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 InvalidGeoJson from backend.services.grid.split_service import SplitService, SplitServiceError @@ -93,8 +92,6 @@ def post(self, project_id, task_id): try: 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: @@ -180,8 +177,6 @@ def post(self, project_id, task_id): try: 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 @@ -260,8 +255,6 @@ def post(self, project_id, task_id): try: 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 Exception as e: @@ -327,8 +320,6 @@ def post(self, project_id, task_id): 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 @@ -405,8 +396,6 @@ def post(self, project_id): 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", @@ -484,8 +473,6 @@ def post(self, project_id): 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): @@ -557,8 +544,6 @@ def post(self, project_id): 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): @@ -871,8 +856,6 @@ def post(self, project_id, task_id): try: 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: @@ -951,8 +934,6 @@ def post(self, project_id): 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): @@ -1017,8 +998,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 { @@ -1030,5 +1009,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 From 3c14211f98da00af637b8eb5f8c7da323da92cb0 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Tue, 18 Jul 2023 11:17:59 +0545 Subject: [PATCH 3/7] Check if project exists on task action endpoints so that we can return appropriate error messages In the prev implementation even if the project id is non existent error message was returned as project_id and task_id are primary keys for tasks. THis addition will check if the project exists before checking for task so we would already know if project exists and return error. --- backend/api/tasks/actions.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/api/tasks/actions.py b/backend/api/tasks/actions.py index 98fbadeb2d..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 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,6 +92,7 @@ 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 MappingServiceError as e: @@ -175,6 +178,7 @@ 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 MappingServiceError as e: @@ -253,10 +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 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) @@ -316,6 +323,7 @@ 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 ) @@ -392,6 +400,7 @@ 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: @@ -469,6 +478,7 @@ 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: @@ -540,6 +550,7 @@ 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: @@ -854,6 +865,7 @@ 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 SplitServiceError as e: @@ -930,6 +942,7 @@ 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: From 92b5536e3a779e05c83392c3a98067f51f47eea4 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Mon, 7 Aug 2023 10:43:51 +0545 Subject: [PATCH 4/7] Check if the project exists in various services so that NotFound can be raised --- backend/api/projects/teams.py | 3 +++ backend/services/mapping_service.py | 3 +++ backend/services/messaging/chat_service.py | 3 +++ backend/services/project_admin_service.py | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/api/projects/teams.py b/backend/api/projects/teams.py index 8a4da18dc4..3dbb12a337 100644 --- a/backend/api/projects/teams.py +++ b/backend/api/projects/teams.py @@ -3,6 +3,7 @@ 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 diff --git a/backend/services/mapping_service.py b/backend/services/mapping_service.py index 5cd79e1c28..ca8c1a47bd 100644 --- a/backend/services/mapping_service.py +++ b/backend/services/mapping_service.py @@ -198,6 +198,9 @@ 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( diff --git a/backend/services/messaging/chat_service.py b/backend/services/messaging/chat_service.py index 924200ce0e..825be2123f 100644 --- a/backend/services/messaging/chat_service.py +++ b/backend/services/messaging/chat_service.py @@ -121,6 +121,9 @@ 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, diff --git a/backend/services/project_admin_service.py b/backend/services/project_admin_service.py index 89ba141e7c..13e34bb6fb 100644 --- a/backend/services/project_admin_service.py +++ b/backend/services/project_admin_service.py @@ -284,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: From ad108293f7dec0cb72d0a62b7d5a697492425385 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Mon, 7 Aug 2023 10:46:32 +0545 Subject: [PATCH 5/7] Add checks for existence of requested Organisation and Campaign to raise NotFound exception --- backend/error_messages.json | 2 ++ backend/services/campaign_service.py | 16 +++++++++++++++- backend/services/organisation_service.py | 4 +++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/error_messages.json b/backend/error_messages.json index edabb03679..bc4660b35e 100644 --- a/backend/error_messages.json +++ b/backend/error_messages.json @@ -11,6 +11,8 @@ "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.", diff --git a/backend/services/campaign_service.py b/backend/services/campaign_service.py index a70e95c3a1..9f00f6dded 100644 --- a/backend/services/campaign_service.py +++ b/backend/services/campaign_service.py @@ -133,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 ) @@ -146,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) @@ -175,7 +182,14 @@ def delete_organisation_campaign(organisation_id: int, campaign_id: int): 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( diff --git a/backend/services/organisation_service.py b/backend/services/organisation_service.py index a937abb20c..e1eef65c9e 100644 --- a/backend/services/organisation_service.py +++ b/backend/services/organisation_service.py @@ -51,12 +51,14 @@ 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 From 9d289695026076cce6951f851ba68d90bf7c8ba3 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Mon, 7 Aug 2023 10:49:23 +0545 Subject: [PATCH 6/7] Raise NotFound exception if user is not member of team on team leave request --- backend/error_messages.json | 1 + backend/services/team_service.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/error_messages.json b/backend/error_messages.json index bc4660b35e..f2afe220cf 100644 --- a/backend/error_messages.json +++ b/backend/error_messages.json @@ -18,6 +18,7 @@ "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.", diff --git a/backend/services/team_service.py b/backend/services/team_service.py index c697ad9811..292360e3b8 100644 --- a/backend/services/team_service.py +++ b/backend/services/team_service.py @@ -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 From 4e009cd53aa8731f5024172b0dd7a20996f522dd Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Tue, 18 Jul 2023 11:19:08 +0545 Subject: [PATCH 7/7] Update test cases after migraton to new NotFound exception --- .../api/campaigns/test_resources.py | 22 +++++--- .../api/comments/test_resources.py | 23 ++++++--- .../api/interests/test_resources.py | 19 ++++--- .../integration/api/issues/test_resources.py | 20 +++++--- .../integration/api/licenses/test_actions.py | 11 +++- .../api/licenses/test_resources.py | 20 +++++--- .../api/notifications/test_resources.py | 16 +++--- .../api/organisations/test_campaigns.py | 9 +++- .../api/organisations/test_resources.py | 23 +++++---- .../api/system/test_authentication.py | 10 ++-- .../integration/api/tasks/test_actions.py | 51 +++++++++++-------- .../integration/api/teams/test_actions.py | 38 ++++++++------ .../integration/api/teams/test_resources.py | 27 +++++++--- .../integration/api/users/test_actions.py | 2 +- .../integration/api/users/test_resources.py | 12 +++-- .../integration/api/users/test_statistics.py | 3 +- 16 files changed, 198 insertions(+), 108 deletions(-) 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 99012dc0e6..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.exceptions 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/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."""