From 026a8be70750decbc271868cd510cc1f32443adb Mon Sep 17 00:00:00 2001 From: Drew Galbraith Date: Sat, 20 Sep 2025 11:56:30 -0700 Subject: [PATCH] Add put for updating a task. --- backend/src/services/mod.rs | 6 +++++ backend/src/services/tasks.rs | 41 +++++++++++++++++++++++++++-- backend/tests/api/update_tasks.hurl | 12 ++------- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index 50b7492..d29b0fe 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -13,6 +13,7 @@ pub enum AppError { InternalError(anyhow::Error), JsonExtractError(JsonRejection), PathError(PathRejection), + Unprocessable(String), NotFound, } @@ -36,6 +37,11 @@ impl IntoResponse for AppError { }), ) .into_response(), + Self::Unprocessable(msg) => ( + StatusCode::UNPROCESSABLE_ENTITY, + Json(ErrorJson { error: msg }), + ) + .into_response(), Self::PathError(rej) => ( StatusCode::BAD_REQUEST, Json(ErrorJson { diff --git a/backend/src/services/tasks.rs b/backend/src/services/tasks.rs index ebf7f3e..9409fcd 100644 --- a/backend/src/services/tasks.rs +++ b/backend/src/services/tasks.rs @@ -10,14 +10,14 @@ use serde::Deserialize; use sqlx::{Pool, Sqlite}; use uuid::Uuid; -use crate::models::TaskModel; +use crate::models::{TaskModel, TaskStatus}; use super::AppError; pub fn create_task_router() -> Router> { Router::new() .route("/", post(create_task)) - .route("/{task_id}", get(get_task)) + .route("/{task_id}", get(get_task).put(update_task)) } #[derive(Deserialize)] @@ -45,3 +45,40 @@ pub async fn get_task( Ok((StatusCode::OK, Json(model))) } + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct UpdateTaskRequest { + title: Option, + description: Option, + status: Option, +} + +pub async fn update_task( + State(pool): State>, + WithRejection(Path(task_id), _): WithRejection, AppError>, + WithRejection(Json(input), _): WithRejection, AppError>, +) -> Result<(StatusCode, Json), AppError> { + let mut model = TaskModel::get_by_id(&pool, task_id).await?; + + if let Some(title) = input.title { + if title.len() == 0 { + return Err(AppError::Unprocessable( + "Title must not be empty".to_string(), + )); + } + model.title = title; + } + + if let Some(description) = input.description { + model.description = Some(description); + } + + if let Some(status) = input.status { + model.status = status; + } + + let model = model.update(&pool).await?; + + Ok((StatusCode::OK, Json(model))) +} diff --git a/backend/tests/api/update_tasks.hurl b/backend/tests/api/update_tasks.hurl index 77d9a8e..550f0cd 100644 --- a/backend/tests/api/update_tasks.hurl +++ b/backend/tests/api/update_tasks.hurl @@ -89,13 +89,13 @@ jsonpath "$.completed_at" exists PUT {{host}}/api/tasks/{{task_id}} Content-Type: application/json { - "description": null + "description": "" } HTTP 200 [Asserts] jsonpath "$.id" == "{{task_id}}" -jsonpath "$.description" == null +jsonpath "$.description" == "" # Setup: Create another task for error tests POST {{host}}/api/tasks @@ -152,11 +152,3 @@ HTTP 422 [Asserts] jsonpath "$.error" exists -# Test: Update with no fields (empty object) -PUT {{host}}/api/tasks/{{error_test_task_id}} -Content-Type: application/json -{} - -HTTP 422 -[Asserts] -jsonpath "$.error" exists \ No newline at end of file