backend/api/tasks/actions.py (310 lines of code) (raw):

from flask_restful import Resource, current_app, request from schematics.exceptions import DataError from backend.models.dtos.grid_dto import SplitTaskDTO from backend.models.postgis.utils import NotFound, 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.users.authentication_service import token_auth, tm from backend.models.dtos.validator_dto import ( LockForValidationDTO, UnlockAfterValidationDTO, StopValidationDTO, ) from backend.services.validator_service import ( ValidatorService, ValidatorServiceError, UserLicenseError, ) from backend.models.dtos.mapping_dto import ( LockTaskDTO, StopMappingTaskDTO, MappedTaskDTO, ) from backend.services.mapping_service import MappingService, MappingServiceError class TasksActionsMappingLockAPI(Resource): @token_auth.login_required def post(self, project_id, task_id): """ Locks a task for mapping --- tags: - tasks 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 - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - name: task_id in: path description: Unique task ID required: true type: integer default: 1 responses: 200: description: Task locked 400: description: Client Error 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Task not found 409: description: User has not accepted license terms of project 500: description: Internal Server Error """ try: lock_task_dto = LockTaskDTO() lock_task_dto.user_id = token_auth.current_user() lock_task_dto.project_id = project_id lock_task_dto.task_id = task_id lock_task_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") lock_task_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Unable to lock task"}, 400 try: task = MappingService.lock_task_for_mapping(lock_task_dto) return task.to_primitive(), 200 except NotFound: return {"Error": "Task Not Found"}, 404 except MappingServiceError as e: return {"Error": str(e)}, 403 except UserLicenseError: return {"Error": "User not accepted license terms"}, 409 except Exception as e: error_msg = f"Task Lock API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to lock task"}, 500 class TasksActionsMappingStopAPI(Resource): @token_auth.login_required def post(self, project_id, task_id): """ Unlock a task that is locked for mapping resetting it to its last status --- tags: - tasks 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 - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - name: task_id in: path description: Unique task ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object for unlocking a task schema: id: TaskUpdateStop properties: comment: type: string description: Optional user comment about the task default: Comment about mapping done before stop responses: 200: description: Task unlocked 400: description: Client Error 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Task not found 500: description: Internal Server Error """ try: stop_task = StopMappingTaskDTO(request.get_json()) stop_task.user_id = token_auth.current_user() stop_task.task_id = task_id stop_task.project_id = project_id stop_task.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") stop_task.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Task unlock failed"}, 400 try: task = MappingService.stop_mapping_task(stop_task) return task.to_primitive(), 200 except NotFound: return {"Error": "Task Not Found"}, 404 except MappingServiceError: return {"Error": "Task unlock failed"}, 403 except Exception as e: error_msg = f"Task Lock API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Task unlock failed"}, 500 class TasksActionsMappingUnlockAPI(Resource): @token_auth.login_required def post(self, project_id, task_id): """ Set a task as mapped --- tags: - tasks produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - name: task_id in: path description: Unique task ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object for unlocking a task schema: id: TaskUpdateUnlock required: - status properties: status: type: string description: The new status for the task default: MAPPED comment: type: string description: Optional user comment about the task default: Comment about the mapping responses: 200: description: Task unlocked 400: description: Client Error 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Task not found 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() mapped_task = MappedTaskDTO(request.get_json()) mapped_task.user_id = authenticated_user_id mapped_task.task_id = task_id mapped_task.project_id = project_id mapped_task.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Task unlock failed"}, 400 try: task = MappingService.unlock_task_after_mapping(mapped_task) return task.to_primitive(), 200 except NotFound: return {"Error": "Task Not Found"}, 404 except MappingServiceError: return {"Error": "Task unlock failed"}, 403 except Exception as e: error_msg = f"Task Lock API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Task unlock failed"}, 500 finally: # Refresh mapper level after mapping UserService.check_and_update_mapper_level(authenticated_user_id) class TasksActionsMappingUndoAPI(Resource): @token_auth.login_required def post(self, project_id, task_id): """ Undo a task's mapping status --- tags: - tasks 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 - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - name: task_id in: path description: Unique task ID required: true type: integer default: 1 responses: 200: description: Task found 403: description: Forbidden 404: description: Task not found 500: description: Internal Server Error """ try: preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") task = MappingService.undo_mapping( project_id, task_id, token_auth.current_user(), preferred_locale ) return task.to_primitive(), 200 except NotFound: return {"Error": "Task Not Found"}, 404 except MappingServiceError: return {"Error": "User not permitted to undo task"}, 403 except Exception as e: error_msg = f"Task GET API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to lock task"}, 500 class TasksActionsValidationLockAPI(Resource): @token_auth.login_required def post(self, project_id): """ Lock tasks for validation --- tags: - tasks 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 - name: project_id in: path description: Project ID the tasks are associated with required: true type: integer default: 1 - in: body name: body required: true description: JSON object for locking task(s) schema: properties: taskIds: type: array items: type: integer description: Array of taskIds for locking default: [1,2] responses: 200: description: Task(s) locked for validation 400: description: Client Error 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Task not found 409: description: User has not accepted license terms of project 500: description: Internal Server Error """ try: validator_dto = LockForValidationDTO(request.get_json()) validator_dto.project_id = project_id validator_dto.user_id = token_auth.current_user() validator_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") validator_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Unable to lock task"}, 400 try: tasks = ValidatorService.lock_tasks_for_validation(validator_dto) return tasks.to_primitive(), 200 except ValidatorServiceError as e: error_msg = f"Validator Lock API - {str(e)}" return {"Error": error_msg}, 403 except NotFound: return {"Error": "Task not found"}, 404 except UserLicenseError: return {"Error": "User not accepted license terms"}, 409 except Exception as e: error_msg = f"Validator Lock API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to lock task"}, 500 class TasksActionsValidationStopAPI(Resource): @tm.pm_only(False) @token_auth.login_required def post(self, project_id): """ Unlock tasks that are locked for validation resetting them to their last status --- tags: - tasks 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 - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - in: body name: body required: true description: JSON object for unlocking a task schema: properties: resetTasks: type: array items: schema: $ref: "#/definitions/ResetTask" responses: 200: description: Task unlocked 400: description: Client Error 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Task not found 500: description: Internal Server Error """ try: validated_dto = StopValidationDTO(request.get_json()) validated_dto.project_id = project_id validated_dto.user_id = token_auth.current_user() validated_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") validated_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Task unlock failed"}, 400 try: tasks = ValidatorService.stop_validating_tasks(validated_dto) return tasks.to_primitive(), 200 except ValidatorServiceError: return {"Error": "Task unlock failed"}, 403 except NotFound: return {"Error": "Task unlock failed"}, 404 except Exception as e: error_msg = f"Stop Validating API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Task unlock failed"}, 500 class TasksActionsValidationUnlockAPI(Resource): @token_auth.login_required def post(self, project_id): """ Set tasks as validated --- tags: - tasks 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 - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - in: body name: body required: true description: JSON object for unlocking a task schema: properties: validatedTasks: type: array items: schema: $ref: "#/definitions/ValidatedTask" responses: 200: description: Task unlocked 400: description: Client Error 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Task not found 500: description: Internal Server Error """ try: validated_dto = UnlockAfterValidationDTO(request.get_json()) validated_dto.project_id = project_id validated_dto.user_id = token_auth.current_user() validated_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") validated_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Task unlock failed"}, 400 try: tasks = ValidatorService.unlock_tasks_after_validation(validated_dto) return tasks.to_primitive(), 200 except ValidatorServiceError: return {"Error": "Task unlock failed"}, 403 except NotFound: return {"Error": "Task unlock failed"}, 404 except Exception as e: error_msg = f"Validator Lock API - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Task unlock failed"}, 500 class TasksActionsMapAllAPI(Resource): @token_auth.login_required def post(self, project_id): """ Map all tasks on a project --- tags: - tasks produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Unique project ID required: true type: integer default: 1 responses: 200: description: All tasks mapped 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsMapAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: MappingService.map_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks mapped"}, 200 except Exception as e: error_msg = f"TasksActionsMapAllAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to map all the tasks"}, 500 class TasksActionsValidateAllAPI(Resource): @token_auth.login_required def post(self, project_id): """ Validate all mapped tasks on a project --- tags: - tasks produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Unique project ID required: true type: integer default: 1 responses: 200: description: All mapped tasks validated 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsValidateAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: ValidatorService.validate_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks validated"}, 200 except Exception as e: error_msg = f"TasksActionsValidateAllAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to validate all tasks"}, 500 class TasksActionsInvalidateAllAPI(Resource): @token_auth.login_required def post(self, project_id): """ Invalidate all mapped tasks on a project --- tags: - tasks produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Unique project ID required: true type: integer default: 1 responses: 200: description: All mapped tasks invalidated 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsInvalidateAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: ValidatorService.invalidate_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks invalidated"}, 200 except Exception as e: error_msg = f"TasksActionsInvalidateAllAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to invalidate all tasks"}, 500 class TasksActionsResetBadImageryAllAPI(Resource): @token_auth.login_required def post(self, project_id): """ Set all bad imagery tasks as ready for mapping --- tags: - tasks produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Unique project ID required: true type: integer default: 1 responses: 200: description: All bad imagery tasks marked ready for mapping 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsResetBadImageryAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: MappingService.reset_all_badimagery(project_id, authenticated_user_id) return {"Success": "All bad imagery tasks marked ready for mapping"}, 200 except Exception as e: error_msg = ( f"TasksActionsResetBadImageryAllAPI POST - unhandled error: {str(e)}" ) current_app.logger.critical(error_msg) return {"Error": "Unable to reset tasks"}, 500 class TasksActionsResetAllAPI(Resource): @token_auth.login_required def post(self, project_id): """ Reset all tasks on project back to ready, preserving history --- tags: - tasks produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: Unique project ID required: true type: integer default: 1 responses: 200: description: All tasks reset 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 500: description: Internal Server Error """ try: 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"TasksActionsResetAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: ProjectAdminService.reset_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks reset"}, 200 except Exception as e: error_msg = f"TasksActionsResetAllAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to reset tasks"}, 500 class TasksActionsSplitAPI(Resource): @token_auth.login_required def post(self, project_id, task_id): """ Split a task --- tags: - tasks 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 - name: project_id in: path description: Project ID the task is associated with required: true type: integer default: 1 - name: task_id in: path description: Unique task ID required: true type: integer default: 1 responses: 200: description: Task split OK 400: description: Client Error 401: description: Unauthorized - Invalid credentials 403: description: Forbidden 404: description: Task not found 500: description: Internal Server Error """ try: split_task_dto = SplitTaskDTO() split_task_dto.user_id = token_auth.current_user() split_task_dto.project_id = project_id split_task_dto.task_id = task_id split_task_dto.preferred_locale = request.environ.get( "HTTP_ACCEPT_LANGUAGE" ) split_task_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return {"Error": "Unable to split task"}, 400 try: tasks = SplitService.split_task(split_task_dto) return tasks.to_primitive(), 200 except NotFound: return {"Error": "Task Not Found"}, 404 except SplitServiceError as e: return {"Error": str(e)}, 403 except InvalidGeoJson as e: return {"Error": str(e)}, 500 except Exception as e: error_msg = f"TasksActionsSplitAPI POST - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": "Unable to split task"}, 500