diff --git a/Cargo.lock b/Cargo.lock index eaefede..98ebc5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,7 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", + "axum-macros", "bytes", "futures-util", "http", @@ -172,6 +173,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "backtrace" version = "0.3.74" diff --git a/Cargo.toml b/Cargo.toml index c56c6b7..30a8bda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,14 +4,14 @@ version = "0.1.0" edition = "2021" [dependencies] -askama = { version = "0.12.1", features=[ "with-axum" ] } +askama = { version = "0.12.1", features = ["with-axum"] } askama_axum = "0.4.0" -axum = "0.7" +axum = { version = "0.7.2", features = ["macros"] } dotenvy = "0.15" http = "1.1.0" serde = "1.0" serde_json = "1.0" -sqlx = { version = "0.7", features=[ "runtime-tokio", "sqlite" ] } -tokio = { version = "1.38", features=[ "full" ] } +sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] } +tokio = { version = "1.38", features = ["full"] } tower = "0.4.13" -tower-http = { version = "0.5.2", features=[ "cors", "fs" ] } +tower-http = { version = "0.5.2", features = ["cors", "fs"] } diff --git a/README.md b/README.md index 17ff54f..6e38916 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,9 @@ A WIP bespoke task tracking app with a userbase of 1. ## Running the server There are 3 servers to run to enable hot reloading of the frontend -and backend. The server can then be accessed at `localhost:8000` in +and backend. The server can then be accessed at `localhost:8000` in the browser. - ```bash $ cargo watch -x run $ cd frontend/ diff --git a/src/main.rs b/src/main.rs index 4ccbae6..7b5a315 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ mod routes; use axum::http::{StatusCode, Uri}; use axum::response::IntoResponse; -use axum::routing::{delete, get}; +use axum::routing::{get, put}; use dotenvy::dotenv; use http::{HeaderName, Method}; use sqlx::SqlitePool; @@ -35,7 +35,11 @@ async fn main() { "/tasks", get(routes::tasks::list).post(routes::tasks::create), ) - .route("/tasks/:task_id", delete(routes::tasks::delete)) + .route( + "/tasks/:task_id", + put(routes::tasks::put).delete(routes::tasks::delete), + ) + .route("/tasks/:task_id/edit", get(routes::tasks::edit)) .layer( ServiceBuilder::new().layer( CorsLayer::new() diff --git a/src/models/task.rs b/src/models/task.rs index e751d7b..80660bc 100644 --- a/src/models/task.rs +++ b/src/models/task.rs @@ -22,6 +22,18 @@ impl Task { .await } + pub async fn delete(pool: &SqlitePool, id: i64) -> Result { + sqlx::query!("DELETE FROM task WHERE id = ?1", id) + .execute(pool) + .await + } + + pub async fn by_id(pool: &SqlitePool, id: i64) -> Result { + sqlx::query_as!(Task, "SELECT * FROM task WHERE id = ?1", id,) + .fetch_one(pool) + .await + } + pub async fn insert(&self, pool: &SqlitePool) -> Result { sqlx::query_as!( Task, @@ -33,9 +45,14 @@ impl Task { .await } - pub async fn delete(pool: &SqlitePool, id: i64) -> Result { - sqlx::query!("DELETE FROM task WHERE id = ?1", id) - .execute(pool) - .await + pub async fn update(&self, pool: &SqlitePool) -> Result { + sqlx::query!( + "UPDATE task SET title=?2, description=?3 WHERE id = ?1", + self.id, + self.title, + self.description, + ) + .execute(pool) + .await } } diff --git a/src/routes/tasks.rs b/src/routes/tasks.rs index 3be51a6..5b0c086 100644 --- a/src/routes/tasks.rs +++ b/src/routes/tasks.rs @@ -2,6 +2,7 @@ use askama_axum::Template; use axum::extract::{Json, Path, State}; use axum::http::StatusCode; use axum::response::IntoResponse; +use axum::Form; use serde::Deserialize; use crate::global::FormErrorResponse; @@ -60,3 +61,33 @@ pub async fn delete(state: State, Path(id): Path) -> impl IntoRes (StatusCode::OK, "") } + +#[derive(Template)] +#[template(path = "partials/tasks/edit.html")] +struct TaskEditTemplate { + pub task: Task, +} + +pub async fn edit(state: State, Path(id): Path) -> impl IntoResponse { + TaskEditTemplate { + task: Task::by_id(&state.db_pool, id).await.unwrap(), + } +} + +#[derive(Template)] +#[template(path = "partials/tasks/item.html")] +struct TaskItemTemplate { + pub task: Task, +} + +pub async fn put( + state: State, + Path(id): Path, + Form(req): Form, +) -> impl IntoResponse { + let mut task = Task::by_id(&state.db_pool, id).await.unwrap(); + task.title = req.title; + task.description = req.description; + task.update(&state.db_pool).await.unwrap(); + TaskItemTemplate { task } +} diff --git a/templates/index.html b/templates/index.html index 99f53cb..ee687ee 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,7 +2,7 @@ Captains Log - + @@ -22,8 +22,8 @@
    {% for task in tasks %} -
  • {{ task.title }}
  • + {% include "partials/tasks/item.html" %} {% endfor %}
- \ No newline at end of file + diff --git a/templates/partials/tasks/edit.html b/templates/partials/tasks/edit.html new file mode 100644 index 0000000..c9226b7 --- /dev/null +++ b/templates/partials/tasks/edit.html @@ -0,0 +1,21 @@ +
+
+ + +
+
+ + {% if let Some(desc) = task.description %} + + {% else %} + + {% endif %} +
+ + + +
diff --git a/templates/partials/tasks/item.html b/templates/partials/tasks/item.html new file mode 100644 index 0000000..10a9811 --- /dev/null +++ b/templates/partials/tasks/item.html @@ -0,0 +1,9 @@ +
  • + {{ task.title }} + {% match task.description %} + {% when Some with (val) %} + -- {{ val }} + {% when None %} + {% endmatch %} + +