Create the remaining task api methods on the server. (#3)

Create the remaining task api methods along with hurl tests for each.

Reviewed-on: #3
Co-authored-by: Drew Galbraith <drew@tiramisu.one>
Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
Drew 2025-09-20 19:23:19 +00:00 committed by Drew
parent d32f6be813
commit ef247e6e29
5 changed files with 457 additions and 3 deletions

View file

@ -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 {

View file

@ -10,14 +10,17 @@ 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<Pool<Sqlite>> {
Router::new()
.route("/", post(create_task))
.route("/{task_id}", get(get_task))
.route("/", post(create_task).get(list_tasks))
.route(
"/{task_id}",
get(get_task).put(update_task).delete(delete_task),
)
}
#[derive(Deserialize)]
@ -37,6 +40,14 @@ pub async fn create_task(
Ok((StatusCode::CREATED, Json(model)))
}
pub async fn list_tasks(
State(pool): State<Pool<Sqlite>>,
) -> Result<(StatusCode, Json<Vec<TaskModel>>), AppError> {
let tasks = TaskModel::list_all(&pool).await?;
Ok((StatusCode::OK, Json(tasks)))
}
pub async fn get_task(
State(pool): State<Pool<Sqlite>>,
WithRejection(Path(task_id), _): WithRejection<Path<Uuid>, AppError>,
@ -45,3 +56,51 @@ pub async fn get_task(
Ok((StatusCode::OK, Json(model)))
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpdateTaskRequest {
title: Option<String>,
description: Option<String>,
status: Option<TaskStatus>,
}
pub async fn update_task(
State(pool): State<Pool<Sqlite>>,
WithRejection(Path(task_id), _): WithRejection<Path<Uuid>, AppError>,
WithRejection(Json(input), _): WithRejection<Json<UpdateTaskRequest>, AppError>,
) -> Result<(StatusCode, Json<TaskModel>), 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)))
}
pub async fn delete_task(
State(pool): State<Pool<Sqlite>>,
WithRejection(Path(task_id), _): WithRejection<Path<Uuid>, AppError>,
) -> Result<StatusCode, AppError> {
// Ensure that the task exists.
TaskModel::get_by_id(&pool, task_id).await?;
TaskModel::delete(&pool, task_id).await?;
Ok(StatusCode::NO_CONTENT)
}