diff --git a/src/backend/app/models/enums.py b/src/backend/app/models/enums.py index 2a3d3ddd..2282f5ca 100644 --- a/src/backend/app/models/enums.py +++ b/src/backend/app/models/enums.py @@ -161,6 +161,7 @@ class EventType(str, Enum): - ``split`` -- Set the state *unlocked done* then generate additional subdivided task areas. - ``assign`` -- For a requester user to assign a task to another user. Set the state *locked for mapping* passing in the required user id. - ``comment`` -- Keep the state the same, but simply add a comment. + - ``unlock`` -- Unlock a task state by unlocking it if it's locked. Note that ``task_id`` must be specified in the endpoint too. """ @@ -175,3 +176,4 @@ class EventType(str, Enum): SPLIT = "split" ASSIGN = "assign" COMMENT = "comment" + UNLOCK = "unlock" diff --git a/src/backend/app/tasks/task_logic.py b/src/backend/app/tasks/task_logic.py index 68ec2210..c44c11b0 100644 --- a/src/backend/app/tasks/task_logic.py +++ b/src/backend/app/tasks/task_logic.py @@ -128,3 +128,41 @@ async def request_mapping( ) result = await cur.fetchone() return result + + +async def get_task_state( + db: Connection, project_id: uuid.UUID, task_id: uuid.UUID +) -> dict: + """ + Retrieve the latest state of a task by querying the task_events table. + + Args: + db (Connection): The database connection. + project_id (uuid.UUID): The project ID. + task_id (uuid.UUID): The task ID. + + Returns: + dict: A dictionary containing the task's state and associated metadata. + """ + try: + async with db.cursor(row_factory=dict_row) as cur: + await cur.execute( + """ + SELECT state, user_id, created_at, comment + FROM task_events + WHERE project_id = %(project_id)s AND task_id = %(task_id)s + ORDER BY created_at DESC + LIMIT 1; + """, + { + "project_id": str(project_id), + "task_id": str(task_id), + }, + ) + result = await cur.fetchone() + return result + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"An error occurred while retrieving the task state: {str(e)}", + ) diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index c7323d46..890ad7fa 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -368,4 +368,36 @@ async def new_event( State.UNFLYABLE_TASK, ) + case EventType.UNLOCK: + # Fetch the task state + current_task_state = await task_logic.get_task_state( + db, project_id, task_id + ) + + state = current_task_state.get("state") + locked_user_id = current_task_state.get("user_id") + + # Determine error conditions + if state != State.LOCKED_FOR_MAPPING.name: + raise HTTPException( + status_code=400, + detail="Task state does not match expected state for unlock operation.", + ) + if user_id != locked_user_id: + raise HTTPException( + status_code=403, + detail="You cannot unlock this task as it is locked by another user.", + ) + + # Proceed with unlocking the task + return await task_logic.update_task_state( + db, + project_id, + task_id, + user_id, + f"Task has been unlock by user {user_data.name}.", + State.LOCKED_FOR_MAPPING, + State.UNLOCKED_TO_MAP, + ) + return True