Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Production Release #213

Merged
merged 8 commits into from
Sep 11, 2024
2 changes: 2 additions & 0 deletions src/backend/app/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -175,3 +176,4 @@ class EventType(str, Enum):
SPLIT = "split"
ASSIGN = "assign"
COMMENT = "comment"
UNLOCK = "unlock"
15 changes: 14 additions & 1 deletion src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,25 @@ async def generate_presigned_url(
async def read_projects(
db: Annotated[Connection, Depends(database.get_db)],
user_data: Annotated[AuthUser, Depends(login_required)],
filter_by_owner: Optional[bool] = Query(
False, description="Filter projects by authenticated user (creator)"
),
skip: int = 0,
limit: int = 100,
):
"Get all projects with task count."

try:
return await project_schemas.DbProject.all(db, skip, limit)
user_id = user_data.id if filter_by_owner else None
projects = await project_schemas.DbProject.all(
db, user_id=user_id, skip=skip, limit=limit
)
if not projects:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="No projects found."
)

return projects
except KeyError as e:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND) from e

Expand Down
23 changes: 15 additions & 8 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,20 +278,27 @@ async def one(db: Connection, project_id: uuid.UUID):

async def all(
db: Connection,
user_id: Optional[str] = None,
skip: int = 0,
limit: int = 100,
):
"""Get all projects."""
"""
Get all projects. Optionally filter by the project creator (user).
"""
async with db.cursor(row_factory=dict_row) as cur:
await cur.execute(
"""
SELECT id, slug, name, description, per_task_instructions, ST_AsGeoJSON(outline)::jsonb AS outline, requires_approval_from_manager_for_locking
FROM projects
ORDER BY created_at DESC
OFFSET %(skip)s
LIMIT %(limit)s
""",
{"skip": skip, "limit": limit},
SELECT
id, slug, name, description, per_task_instructions,
ST_AsGeoJSON(outline)::jsonb AS outline,
requires_approval_from_manager_for_locking
FROM projects
WHERE (author_id = COALESCE(%(user_id)s, author_id))
ORDER BY created_at DESC
OFFSET %(skip)s
LIMIT %(limit)s
""",
{"skip": skip, "limit": limit, "user_id": user_id},
)
db_projects = await cur.fetchall()
return db_projects
Expand Down
38 changes: 38 additions & 0 deletions src/backend/app/tasks/task_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}",
)
32 changes: 32 additions & 0 deletions src/backend/app/tasks/task_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading