summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--migrations/20240417203222_create_task.down.sql1
-rw-r--r--migrations/20240417203222_create_task.up.sql12
-rw-r--r--src/api.rs1
-rw-r--r--src/api/error.rs7
-rw-r--r--src/api/tasks.rs104
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()
+);
diff --git a/src/api.rs b/src/api.rs
index a35fba5..6013a98 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -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)
+}