backend/api/projects/actions.py (142 lines of code) (raw):
import threading
from flask_restful import Resource, request, current_app
from schematics.exceptions import DataError
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_admin_service import ProjectAdminService
from backend.services.grid.grid_service import GridService
from backend.services.messaging.message_service import MessageService
from backend.services.users.authentication_service import token_auth, tm
from backend.services.interests_service import InterestService
from backend.models.postgis.utils import InvalidGeoJson
class ProjectsActionsTransferAPI(Resource):
@token_auth.login_required
def post(self, project_id):
"""
Transfers a project to a new user
---
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: username of the new owner
schema:
properties:
username:
type: string
responses:
200:
description: Project ownership transferred successfully
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
500:
description: Internal Server Error
"""
try:
username = request.get_json()["username"]
authenticated_user_id = token_auth.current_user()
ProjectAdminService.transfer_project_to(
project_id, authenticated_user_id, username
)
return {"Success": "Project Transferred"}, 200
except ValueError as e:
return {"Error": str(e)}, 403
except Exception as e:
error_msg = f"ProjectsActionsTransferAPI POST - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to transfer project"}, 500
class ProjectsActionsMessageContributorsAPI(Resource):
@token_auth.login_required
def post(self, project_id):
"""
Send message to all contributors of a project
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
- in: body
name: body
required: true
description: JSON object for creating message
schema:
properties:
subject:
type: string
default: Thanks
required: true
message:
type: string
default: Thanks for your contribution
required: true
responses:
200:
description: Message sent successfully
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
message_dto = MessageDTO(request.get_json())
message_dto.from_user_id = authenticated_user_id
message_dto.validate()
except DataError as e:
current_app.logger.error(f"Error validating request: {str(e)}")
return {"Error": "Unable to send message to mappers"}, 400
try:
ProjectAdminService.is_user_action_permitted_on_project(
authenticated_user_id, project_id
)
threading.Thread(
target=MessageService.send_message_to_all_contributors,
args=(project_id, message_dto),
).start()
return {"Success": "Messages started"}, 200
except ValueError as e:
return {"Error": str(e)}, 403
except Exception as e:
error_msg = f"Send message all - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to send messages to mappers"}, 500
class ProjectsActionsFeatureAPI(Resource):
@token_auth.login_required
def post(self, project_id):
"""
Set a project as featured
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
responses:
200:
description: Featured projects
400:
description: Bad request
403:
description: Forbidden
404:
description: Project not found
500:
description: Internal Server Error
"""
try:
authenticated_user_id = token_auth.current_user()
ProjectAdminService.is_user_action_permitted_on_project(
authenticated_user_id, project_id
)
except ValueError as e:
error_msg = f"FeaturedProjects POST: {str(e)}"
return {"Error": error_msg}, 403
try:
ProjectService.set_project_as_featured(project_id)
return {"Success": True}, 200
except NotFound:
return {"Error": "Project Not Found"}, 404
except ValueError as e:
error_msg = f"FeaturedProjects POST: {str(e)}"
return {"Error": error_msg}, 400
except Exception as e:
error_msg = f"FeaturedProjects POST - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class ProjectsActionsUnFeatureAPI(Resource):
@token_auth.login_required
def post(self, project_id):
"""
Unset a project as featured
---
tags:
- projects
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
responses:
200:
description: Project is no longer featured
400:
description: Bad request
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"FeaturedProjects POST: {str(e)}"
return {"Error": error_msg}, 403
try:
ProjectService.unset_project_as_featured(project_id)
return {"Success": True}, 200
except NotFound:
return {"Error": "Project Not Found"}, 404
except ValueError as e:
error_msg = f"FeaturedProjects DELETE: {str(e)}"
return {"Error": error_msg}, 400
except Exception as e:
error_msg = f"FeaturedProjects DELETE - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class ProjectsActionsSetInterestsAPI(Resource):
@token_auth.login_required
def post(self, project_id):
"""
Creates a relationship between project and interests
---
tags:
- interests
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
- in: body
name: body
required: true
description: JSON object for creating/updating project and interests relationships
schema:
properties:
interests:
type: array
items:
type: integer
responses:
200:
description: New project interest relationship created
400:
description: Invalid Request
401:
description: Unauthorized - Invalid credentials
403:
description: Forbidden
500:
description: Internal Server Error
"""
try:
ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), project_id
)
except ValueError as e:
error_msg = f"ProjectsActionsSetInterestsAPI POST: {str(e)}"
return {"Error": error_msg}, 403
try:
data = request.get_json()
project_interests = InterestService.create_or_update_project_interests(
project_id, data["interests"]
)
return project_interests.to_primitive(), 200
except NotFound:
return {"Error": "Project not Found"}, 404
except Exception as e:
error_msg = (
f"ProjectsActionsSetInterestsAPI POST - unhandled error: {str(e)}"
)
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500
class ProjectActionsIntersectingTilesAPI(Resource):
@tm.pm_only()
@token_auth.login_required
def post(self):
"""
Gets the tiles intersecting the aoi
---
tags:
- grid
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 containing aoi and tasks and bool flag for controlling clip grid to aoi
schema:
properties:
clipToAoi:
type: boolean
default: true
areaOfInterest:
schema:
properties:
type:
type: string
default: FeatureCollection
features:
type: array
items:
schema:
$ref: "#/definitions/GeoJsonFeature"
grid:
schema:
properties:
type:
type: string
default: FeatureCollection
features:
type: array
items:
schema:
$ref: "#/definitions/GeoJsonFeature"
responses:
200:
description: Intersecting tasks found successfully
400:
description: Client Error - Invalid Request
500:
description: Internal Server Error
"""
try:
grid_dto = GridDTO(request.get_json())
grid_dto.validate()
except DataError as e:
current_app.logger.error(f"error validating request: {str(e)}")
return str(e), 400
try:
grid = GridService.trim_grid_to_aoi(grid_dto)
return grid, 200
except InvalidGeoJson as e:
return {"error": f"{str(e)}"}, 400
except Exception as e:
error_msg = f"IntersectingTiles GET API - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"error": error_msg}, 500