backend/api/projects/resources.py (437 lines of code) (raw):
import geojson
import io
from flask import send_file
from flask_restful import Resource, current_app, request
from schematics.exceptions import DataError
from distutils.util import strtobool
from backend.models.dtos.project_dto import (
DraftProjectDTO,
ProjectDTO,
ProjectSearchDTO,
ProjectSearchBBoxDTO,
)
from backend.services.project_search_service import (
ProjectSearchService,
ProjectSearchServiceError,
BBoxTooBigError,
)
from backend.services.project_service import (
ProjectService,
ProjectServiceError,
NotFound,
)
from backend.services.users.user_service import UserService
from backend.services.organisation_service import OrganisationService
from backend.services.users.authentication_service import token_auth
from backend.services.project_admin_service import (
ProjectAdminService,
ProjectAdminServiceError,
InvalidGeoJson,
InvalidData,
)
class ProjectsRestAPI(Resource):
@token_auth.login_required(optional=True)
def get(self, project_id):
"""
Get a specified project including it's area
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: false
type: string
default: Token sessionTokenHere==
- in: header
name: Accept-Language
description: Language user is requesting
type: string
required: true
default: en
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
- in: query
name: as_file
type: boolean
description: Set to true if file download is preferred
default: False
- in: query
name: abbreviated
type: boolean
description: Set to true if only state information is desired
default: False
responses:
200:
description: Project found
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
as_file = (
strtobool(request.args.get("as_file"))
if request.args.get("as_file")
else False
)
abbreviated = (
strtobool(request.args.get("abbreviated"))
if request.args.get("abbreviated")
else False
)
project_dto = ProjectService.get_project_dto_for_mapper(
project_id,
authenticated_user_id,
request.environ.get("HTTP_ACCEPT_LANGUAGE"),
abbreviated,
)
if project_dto:
project_dto = project_dto.to_primitive()
if as_file:
return send_file(
io.BytesIO(geojson.dumps(project_dto).encode("utf-8")),
mimetype="application/json",
as_attachment=True,
attachment_filename=f"project_{str(project_id)}.json",
)
return project_dto, 200
else:
return {"Error": "Private Project"}, 403
except NotFound:
return {"Error": "Project Not Found"}, 404
except ProjectServiceError as e:
return {"Error": str(e)}, 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"}, 500
finally:
# this will try to unlock tasks that have been locked too long
try:
ProjectService.auto_unlock_tasks(project_id)
except Exception as e:
current_app.logger.critical(str(e))
@token_auth.login_required
def post(self):
"""
Creates a tasking-manager project
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- in: body
name: body
required: true
description: JSON object for creating draft project
schema:
properties:
cloneFromProjectId:
type: int
default: 1
description: Specify this value if you want to clone a project, otherwise avoid information
projectName:
type: string
default: HOT Project
areaOfInterest:
schema:
properties:
type:
type: string
default: FeatureCollection
features:
type: array
items:
schema:
$ref: "#/definitions/GeoJsonFeature"
tasks:
schema:
properties:
type:
type: string
default: FeatureCollection
features:
type: array
items:
schema:
$ref: "#/definitions/GeoJsonFeature"
arbitraryTasks:
type: boolean
default: false
responses:
201:
description: Draft project created successfully
400:
description: Client Error - Invalid Request
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
500:
description: Internal Server Error
"""
try:
draft_project_dto = DraftProjectDTO(request.get_json())
draft_project_dto.user_id = token_auth.current_user()
draft_project_dto.validate()
except DataError as e:
current_app.logger.error(f"error validating request: {str(e)}")
return {"Error": "Unable to create project"}, 400
try:
draft_project_id = ProjectAdminService.create_draft_project(
draft_project_dto
)
return {"projectId": draft_project_id}, 201
except ProjectAdminServiceError as e:
return {"Error": str(e)}, 403
except (InvalidGeoJson, InvalidData):
return {"Error": "Invalid GeoJson"}, 400
except Exception as e:
error_msg = f"Project PUT - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to create project"}, 500
@token_auth.login_required
def head(self, project_id):
"""
Retrieves a Tasking-Manager project
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
responses:
200:
description: Project found
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), project_id
)
except ValueError as e:
error_msg = f"ProjectsRestAPI HEAD: {str(e)}"
return {"Error": error_msg}, 403
try:
project_dto = ProjectAdminService.get_project_dto_for_admin(project_id)
return project_dto.to_primitive(), 200
except NotFound:
return {"Error": "Project Not Found"}, 404
except Exception as e:
error_msg = f"ProjectsRestAPI HEAD - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch project"}, 500
@token_auth.login_required
def patch(self, project_id):
"""
Updates a Tasking-Manager project
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
- in: body
name: body
required: true
description: JSON object for updating an existing project
schema:
properties:
projectStatus:
type: string
default: DRAFT
projectPriority:
type: string
default: MEDIUM
defaultLocale:
type: string
default: en
mapperLevel:
type: string
default: BEGINNER
validation_permission:
type: string
default: ANY
mapping_permission:
type: string
default: ANY
private:
type: boolean
default: false
changesetComment:
type: string
default: hotosm-project-1
dueDate:
type: date
default: "2017-04-11T12:38:49"
imagery:
type: string
default: http//www.bing.com/maps/
josmPreset:
type: string
default: josm preset goes here
mappingTypes:
type: array
items:
type: string
default: [BUILDINGS, ROADS]
mappingEditors:
type: array
items:
type: string
default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS]
validationEditors:
type: array
items:
type: string
default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS]
campaign:
type: string
default: malaria
organisation:
type: integer
default: 1
countryTag:
type: array
items:
type: string
default: []
licenseId:
type: integer
default: 1
description: Id of imagery license associated with the project
allowedUsernames:
type: array
items:
type: string
default: ["Iain Hunter", LindaA1]
priorityAreas:
type: array
items:
schema:
$ref: "#/definitions/GeoJsonPolygon"
projectInfoLocales:
type: array
items:
schema:
$ref: "#/definitions/ProjectInfo"
taskCreationMode:
type: integer
default: GRID
responses:
200:
description: Project updated
400:
description: Client Error - Invalid Request
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
authenticated_user_id = token_auth.current_user()
try:
ProjectAdminService.is_user_action_permitted_on_project(
authenticated_user_id, project_id
)
except ValueError as e:
error_msg = f"ProjectsRestAPI PATCH: {str(e)}"
return {"Error": error_msg}, 403
try:
project_dto = ProjectDTO(request.get_json())
project_dto.project_id = project_id
project_dto.validate()
except DataError as e:
current_app.logger.error(f"Error validating request: {str(e)}")
return {"Error": "Unable to update project"}, 400
try:
ProjectAdminService.update_project(project_dto, authenticated_user_id)
return {"Status": "Updated"}, 200
except InvalidGeoJson as e:
return {"Invalid GeoJson": str(e)}, 400
except NotFound as e:
return {"Error": str(e) or "Project Not Found"}, 404
except ProjectAdminServiceError as e:
return {"Error": str(e)}, 400
except Exception as e:
error_msg = f"ProjectsRestAPI PATCH - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to update project"}, 500
@token_auth.login_required
def delete(self, project_id):
"""
Deletes a Tasking-Manager project
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
responses:
200:
description: Project deleted
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
ProjectAdminService.is_user_action_permitted_on_project(
authenticated_user_id, project_id
)
except ValueError as e:
error_msg = f"ProjectsRestAPI DELETE: {str(e)}"
return {"Error": error_msg}, 403
try:
ProjectAdminService.delete_project(project_id, authenticated_user_id)
return {"Success": "Project deleted"}, 200
except ProjectAdminServiceError:
return {"Error": "Project has some mapping"}, 403
except NotFound:
return {"Error": "Project Not Found"}, 404
except Exception as e:
error_msg = f"ProjectsRestAPI DELETE - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to delete project"}, 500
class ProjectSearchBase(Resource):
@token_auth.login_required(optional=True)
def setup_search_dto(self) -> ProjectSearchDTO:
search_dto = ProjectSearchDTO()
search_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
search_dto.mapper_level = request.args.get("mapperLevel")
search_dto.action = request.args.get("action")
search_dto.organisation_name = request.args.get("organisationName")
search_dto.organisation_id = request.args.get("organisationId")
search_dto.team_id = request.args.get("teamId")
search_dto.campaign = request.args.get("campaign")
search_dto.order_by = request.args.get("orderBy", "priority")
search_dto.country = request.args.get("country")
search_dto.order_by_type = request.args.get("orderByType", "ASC")
search_dto.page = (
int(request.args.get("page")) if request.args.get("page") else 1
)
search_dto.text_search = request.args.get("textSearch")
search_dto.omit_map_results = strtobool(
request.args.get("omitMapResults", "false")
)
search_dto.last_updated_gte = request.args.get("lastUpdatedFrom")
search_dto.last_updated_lte = request.args.get("lastUpdatedTo")
search_dto.created_gte = request.args.get("createdFrom")
search_dto.created_lte = request.args.get("createdTo")
# See https://github.com/hotosm/tasking-manager/pull/922 for more info
try:
authenticated_user_id = token_auth.current_user()
if request.args.get("createdByMe") == "true":
search_dto.created_by = authenticated_user_id
if request.args.get("mappedByMe") == "true":
search_dto.mapped_by = authenticated_user_id
if request.args.get("favoritedByMe") == "true":
search_dto.favorited_by = authenticated_user_id
if request.args.get("managedByMe") == "true":
search_dto.managed_by = authenticated_user_id
except Exception:
pass
mapping_types_str = request.args.get("mappingTypes")
if mapping_types_str:
search_dto.mapping_types = map(
str, mapping_types_str.split(",")
) # Extract list from string
search_dto.mapping_types_exact = strtobool(
request.args.get("mappingTypesExact", "false")
)
project_statuses_str = request.args.get("projectStatuses")
if project_statuses_str:
search_dto.project_statuses = map(str, project_statuses_str.split(","))
interests_str = request.args.get("interests")
if interests_str:
search_dto.interests = map(int, interests_str.split(","))
search_dto.validate()
return search_dto
class ProjectsAllAPI(ProjectSearchBase):
@token_auth.login_required(optional=True)
def get(self):
"""
List and search for projects
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
type: string
default: Token sessionTokenHere==
- in: header
name: Accept-Language
description: Language user is requesting
type: string
required: true
default: en
- in: query
name: mapperLevel
type: string
- in: query
name: orderBy
type: string
default: priority
enum: [id,mapper_level,priority,status,last_updated,due_date]
- in: query
name: orderByType
type: string
default: ASC
enum: [ASC, DESC]
- in: query
name: mappingTypes
type: string
- in: query
name: mappingTypesExact
type: boolean
default: false
description: if true, limits projects to match the exact mapping types requested
- in: query
name: organisationName
description: Organisation name to search for
type: string
- in: query
name: organisationId
description: Organisation ID to search for
type: integer
- in: query
name: campaign
description: Campaign name to search for
type: string
- in: query
name: page
description: Page of results user requested
type: integer
default: 1
- in: query
name: textSearch
description: Text to search
type: string
- in: query
name: country
description: Project country
type: string
- in: query
name: action
description: Filter projects by possible actions
enum: [map, validate, any]
type: string
- in: query
name: projectStatuses
description: Authenticated PMs can search for archived or draft statuses
type: string
- in: query
name: lastUpdatedFrom
description: Filter projects whose last update date is equal or greater than a date
type: string
- in: query
name: lastUpdatedTo
description: Filter projects whose last update date is equal or lower than a date
type: string
- in: query
name: createdFrom
description: Filter projects whose creation date is equal or greater than a date
type: string
- in: query
name: createdTo
description: Filter projects whose creation date is equal or lower than a date
type: string
- in: query
name: interests
type: string
description: Filter by interest on project
default: null
- in: query
name: createdByMe
description: Limit to projects created by the authenticated user
type: boolean
default: false
- in: query
name: mappedByMe
description: Limit to projects mapped/validated by the authenticated user
type: boolean
default: false
- in: query
name: favoritedByMe
description: Limit to projects favorited by the authenticated user
type: boolean
default: false
- in: query
name: managedByMe
description:
Limit to projects that can be managed by the authenticated user,
excluding the ones created by them
type: boolean
default: false
- in: query
name: teamId
type: string
description: Filter by team on project
default: null
name: omitMapResults
type: boolean
description: If true, it will not return the project centroid's geometries.
default: false
responses:
200:
description: Projects found
404:
description: No projects found
500:
description: Internal Server Error
"""
try:
user = None
user_id = token_auth.current_user()
if user_id:
user = UserService.get_user_by_id(user_id)
search_dto = self.setup_search_dto()
results_dto = ProjectSearchService.search_projects(search_dto, user)
return results_dto.to_primitive(), 200
except NotFound:
return {"mapResults": {}, "results": []}, 200
except (KeyError, ValueError) as e:
error_msg = f"Projects GET - {str(e)}"
return {"Error": error_msg}, 400
except Exception as e:
error_msg = f"Projects GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch projects"}, 500
class ProjectsQueriesBboxAPI(Resource):
@token_auth.login_required
def get(self):
"""
List and search projects by bounding box
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- in: header
name: Accept-Language
description: Language user is requesting
type: string
default: en
- in: query
name: bbox
description: comma separated list xmin, ymin, xmax, ymax
type: string
required: true
default: 34.404,-1.034, 34.717,-0.624
- in: query
name: srid
description: srid of bbox coords
type: integer
default: 4326
- in: query
name: createdByMe
description: limit to projects created by authenticated user
type: boolean
required: true
default: false
responses:
200:
description: ok
400:
description: Client Error - Invalid Request
403:
description: Forbidden
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto(
authenticated_user_id
)
if len(orgs_dto.organisations) < 1:
raise ValueError("User not a project manager")
except ValueError as e:
error_msg = f"ProjectsQueriesBboxAPI GET: {str(e)}"
return {"Error": error_msg}, 403
try:
search_dto = ProjectSearchBBoxDTO()
search_dto.bbox = map(float, request.args.get("bbox").split(","))
search_dto.input_srid = request.args.get("srid")
search_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
created_by_me = (
strtobool(request.args.get("createdByMe"))
if request.args.get("createdByMe")
else False
)
if created_by_me:
search_dto.project_author = authenticated_user_id
search_dto.validate()
except Exception as e:
current_app.logger.error(f"Error validating request: {str(e)}")
return {"Error": "Unable to fetch projects"}, 400
try:
geojson = ProjectSearchService.get_projects_geojson(search_dto)
return geojson, 200
except BBoxTooBigError:
return {"Error": "Bounding Box too large"}, 403
except ProjectSearchServiceError:
return {"Error": "Unable to fetch projects"}, 400
except Exception as e:
error_msg = f"ProjectsQueriesBboxAPI GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch projects"}, 500
class ProjectsQueriesOwnerAPI(ProjectSearchBase):
@token_auth.login_required
def get(self):
"""
Get all projects for logged in admin
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- in: header
name: Accept-Language
description: Language user is requesting
type: string
required: true
default: en
responses:
200:
description: All mapped tasks validated
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
404:
description: Admin has no projects
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto(
authenticated_user_id
)
if len(orgs_dto.organisations) < 1:
raise ValueError("User not a project manager")
except ValueError as e:
error_msg = f"ProjectsQueriesOwnerAPI GET: {str(e)}"
return {"Error": error_msg}, 403
try:
search_dto = self.setup_search_dto()
admin_projects = ProjectAdminService.get_projects_for_admin(
authenticated_user_id,
request.environ.get("HTTP_ACCEPT_LANGUAGE"),
search_dto,
)
return admin_projects.to_primitive(), 200
except NotFound:
return {"Error": "No comments found"}, 404
except Exception as e:
error_msg = f"Project GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class ProjectsQueriesTouchedAPI(Resource):
def get(self, username):
"""
Gets projects user has mapped
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Accept-Language
description: Language user is requesting
type: string
required: true
default: en
- name: username
in: path
description: The users username
required: true
type: string
default: Thinkwhere
responses:
200:
description: Mapped projects found
404:
description: User not found
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"}, 404
except Exception as e:
error_msg = f"User GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch projects"}, 500
class ProjectsQueriesSummaryAPI(Resource):
def get(self, project_id: int):
"""
Gets project summary
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Accept-Language
description: Language user is requesting
type: string
required: true
default: en
- name: project_id
in: path
description: The ID of the project
required: true
type: integer
default: 1
responses:
200:
description: Project Summary
404:
description: Project not found
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"}, 404
except Exception as e:
error_msg = f"Project Summary GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch project summary"}, 500
class ProjectsQueriesNoGeometriesAPI(Resource):
def get(self, project_id):
"""
Get HOT Project for mapping
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Accept-Language
description: Language user is requesting
type: string
required: true
default: en
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
- in: query
name: as_file
type: boolean
description: Set to true if file download is preferred
default: False
responses:
200:
description: Project found
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
as_file = (
strtobool(request.args.get("as_file"))
if request.args.get("as_file")
else False
)
locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
project_dto = ProjectService.get_project_dto_for_mapper(
project_id, None, locale, True
)
project_dto = project_dto.to_primitive()
if as_file:
return send_file(
io.BytesIO(geojson.dumps(project_dto).encode("utf-8")),
mimetype="application/json",
as_attachment=True,
attachment_filename=f"project_{str(project_id)}.json",
)
return project_dto, 200
except NotFound:
return {"Error": "Project Not Found"}, 404
except ProjectServiceError:
return {"Error": "Unable to fetch project"}, 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"}, 500
finally:
# this will try to unlock tasks that have been locked too long
try:
ProjectService.auto_unlock_tasks(project_id)
except Exception as e:
current_app.logger.critical(str(e))
class ProjectsQueriesNoTasksAPI(Resource):
@token_auth.login_required
def get(self, project_id):
"""
Retrieves a Tasking-Manager project
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
responses:
200:
description: Project found
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), project_id
)
except ValueError as e:
error_msg = f"ProjectsQueriesNoTasksAPI GET: {str(e)}"
return {"Error": error_msg}, 403
try:
project_dto = ProjectAdminService.get_project_dto_for_admin(project_id)
return project_dto.to_primitive(), 200
except NotFound:
return {"Error": "Project Not Found"}, 404
except Exception as e:
error_msg = f"Project GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class ProjectsQueriesAoiAPI(Resource):
def get(self, project_id):
"""
Get AOI of Project
---
tags:
- projects
produces:
- application/json
parameters:
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
- in: query
name: as_file
type: boolean
description: Set to false if file download not preferred
default: True
responses:
200:
description: Project found
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
as_file = (
strtobool(request.args.get("as_file"))
if request.args.get("as_file")
else True
)
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,
attachment_filename=f"{str(project_id)}.geojson",
)
return project_aoi, 200
except NotFound:
return {"Error": "Project Not Found"}, 404
except ProjectServiceError:
return {"Error": "Unable to fetch project"}, 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"}, 500
class ProjectsQueriesPriorityAreasAPI(Resource):
def get(self, project_id):
"""
Get Priority Areas of a project
---
tags:
- projects
produces:
- application/json
parameters:
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
responses:
200:
description: Project found
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
priority_areas = ProjectService.get_project_priority_areas(project_id)
return priority_areas, 200
except NotFound:
return {"Error": "Project Not Found"}, 404
except ProjectServiceError:
return {"Error": "Unable to fetch project"}, 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"}, 500
class ProjectsQueriesFeaturedAPI(Resource):
def get(self):
"""
Get featured projects
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: false
type: string
default: Token sessionTokenHere==
responses:
200:
description: Featured projects
500:
description: Internal Server Error
"""
try:
preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
projects_dto = ProjectService.get_featured_projects(preferred_locale)
return projects_dto.to_primitive(), 200
except Exception as e:
error_msg = f"FeaturedProjects GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500