diff options
author | Toby Vincent <tobyv@tobyvin.dev> | 2024-05-07 12:37:30 -0500 |
---|---|---|
committer | Toby Vincent <tobyv@tobyvin.dev> | 2024-05-07 12:37:44 -0500 |
commit | c55c5b3ccf9f06775fb00a2bcb2912f218691758 (patch) | |
tree | 3535e4eda3f25a168b0af3997b0744214a5c67b9 | |
parent | 1dbe3776c682f469d1497247fac22f0aa233a598 (diff) |
feat(api,wip): impl tasks CRUD endpoint
-rw-r--r-- | migrations/20240417203222_create_task.down.sql | 1 | ||||
-rw-r--r-- | migrations/20240417203222_create_task.up.sql | 12 | ||||
-rw-r--r-- | src/api.rs | 1 | ||||
-rw-r--r-- | src/api/error.rs | 7 | ||||
-rw-r--r-- | src/api/tasks.rs | 104 |
5 files changed, 124 insertions, 1 deletions
diff --git a/migrations/20240417203222_create_task.down.sql b/migrations/20240417203222_create_task.down.sql new file mode 100644 index 0000000..36cf80d --- /dev/null +++ b/migrations/20240417203222_create_task.down.sql @@ -0,0 +1 @@ +DROP TABLE task; diff --git a/migrations/20240417203222_create_task.up.sql b/migrations/20240417203222_create_task.up.sql new file mode 100644 index 0000000..fd8eb9c --- /dev/null +++ b/migrations/20240417203222_create_task.up.sql @@ -0,0 +1,12 @@ +CREATE TABLE task ( + id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID REFERENCES user_(id) ON DELETE SET NULL, + title TEXT NOT NULL, + description TEXT, + remote BOOLEAN NOT NULL, + location TEXT, + start_at TIMESTAMP WITH TIME ZONE, + end_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); @@ -2,6 +2,7 @@ use crate::state::AppState; pub mod account; pub mod error; +pub mod tasks; pub mod users; pub fn router() -> axum::Router<AppState> { diff --git a/src/api/error.rs b/src/api/error.rs index 3733d50..2af7228 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -9,6 +9,9 @@ pub enum Error { #[error("User not found")] UserNotFound, + #[error("Task not found")] + TaskNotFound, + #[error("Required header not found: {0}")] HeaderNotFound(axum::http::HeaderName), @@ -47,7 +50,9 @@ impl axum::response::IntoResponse for Error { use axum::http::StatusCode; let status = match self { - Self::RouteNotFound(_) | Self::UserNotFound => StatusCode::NOT_FOUND, + Self::RouteNotFound(_) | Self::UserNotFound | Self::TaskNotFound => { + StatusCode::NOT_FOUND + } Self::EmailExists => StatusCode::CONFLICT, Self::InvalidToken => StatusCode::UNAUTHORIZED, Self::HeaderNotFound(ref h) if h == AUTHORIZATION => StatusCode::UNAUTHORIZED, diff --git a/src/api/tasks.rs b/src/api/tasks.rs new file mode 100644 index 0000000..9a3e1ab --- /dev/null +++ b/src/api/tasks.rs @@ -0,0 +1,104 @@ +use axum::{ + extract::{Path, State}, + http::StatusCode, + Json, +}; +use axum_extra::routing::Resource; +use serde::{Deserialize, Serialize}; +use sqlx::PgPool; +use time::OffsetDateTime; +use uuid::Uuid; + +use crate::{auth::AccessClaims, state::AppState}; + +use super::error::Error; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Task { + pub id: Uuid, + pub user_id: Option<Uuid>, + pub title: String, + pub description: Option<String>, + pub remote: bool, + pub location: Option<String>, + pub start_at: Option<OffsetDateTime>, + pub end_at: Option<OffsetDateTime>, + pub created_at: OffsetDateTime, + pub updated_at: OffsetDateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CreateTaskSchema { + pub user_id: Uuid, + pub title: String, + pub description: Option<String>, + pub remote: bool, + pub location: Option<String>, + pub start_at: Option<OffsetDateTime>, + pub end_at: Option<OffsetDateTime>, +} + +pub fn router() -> Resource<AppState> { + Resource::named("tasks") + .create(create) + .show(show) + .destroy(destroy) +} + +pub async fn create( + State(pool): State<PgPool>, + _: AccessClaims, + Json(CreateTaskSchema { + user_id, + title, + description, + remote, + location, + start_at, + end_at, + }): Json<CreateTaskSchema>, +) -> Result<(StatusCode, Json<Task>), Error> { + let task = sqlx::query_as!(Task, + "INSERT INTO task (user_id, title, description, remote, location, start_at, end_at) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", + user_id, + title, + description, + remote, + location, + start_at, + end_at, + ) + .fetch_one(&pool) + .await?; + + Ok((StatusCode::CREATED, Json(task))) +} + +pub async fn show( + State(pool): State<PgPool>, + Path(uuid): Path<Uuid>, + _: AccessClaims, +) -> Result<Json<Task>, Error> { + sqlx::query_as!(Task, "SELECT * FROM task WHERE id = $1 LIMIT 1", uuid) + .fetch_optional(&pool) + .await? + .ok_or_else(|| Error::TaskNotFound) + .map(Json) +} + +pub async fn destroy( + State(pool): State<PgPool>, + Path(uuid): Path<Uuid>, + AccessClaims { sub, .. }: AccessClaims, +) -> Result<Json<Task>, Error> { + sqlx::query_as!( + Task, + "SELECT * FROM task WHERE id = $1 AND user_id = $2 LIMIT 1", + uuid, + sub + ) + .fetch_optional(&pool) + .await? + .ok_or_else(|| Error::TaskNotFound) + .map(Json) +} |