Files
packager/rust/src/main.rs

709 lines
21 KiB
Rust
Raw Normal View History

2023-05-08 22:31:01 +02:00
#![allow(unused_imports)]
use axum::{
2023-05-11 16:51:57 +02:00
extract::{Path, Query, State},
2023-05-10 00:42:42 +02:00
headers,
headers::Header,
http::{header::HeaderMap, StatusCode},
response::{Html, Redirect},
2023-05-08 22:31:01 +02:00
routing::{get, post},
2023-05-10 00:42:42 +02:00
Form, Router,
2023-05-08 22:31:01 +02:00
};
2023-05-10 00:42:42 +02:00
2023-08-29 21:33:59 +02:00
use serde_variant::to_variant_name;
2023-05-10 00:42:42 +02:00
use sqlx::{
error::DatabaseError,
query,
sqlite::{SqliteConnectOptions, SqliteError, SqlitePoolOptions},
2023-05-12 00:31:08 +02:00
Pool, Row, Sqlite,
2023-05-10 00:42:42 +02:00
};
2023-08-29 21:33:59 +02:00
use maud::Markup;
2023-05-10 00:42:42 +02:00
use serde::Deserialize;
2023-05-08 00:05:45 +02:00
2023-05-12 00:31:08 +02:00
use futures::TryFutureExt;
2023-05-08 00:05:45 +02:00
use futures::TryStreamExt;
2023-05-10 00:42:42 +02:00
use uuid::{uuid, Uuid};
2023-05-08 00:05:45 +02:00
use std::net::SocketAddr;
mod components;
mod models;
use crate::components::*;
use crate::models::*;
2023-05-10 00:42:42 +02:00
#[derive(Clone)]
pub struct AppState {
database_pool: Pool<Sqlite>,
client_state: ClientState,
}
#[derive(Clone)]
pub struct ClientState {
2023-05-08 22:31:01 +02:00
pub active_category_id: Option<Uuid>,
2023-05-11 16:51:57 +02:00
pub edit_item: Option<Uuid>,
2023-08-29 21:33:59 +02:00
pub trip_edit_attribute: Option<TripAttribute>,
2023-05-08 00:05:45 +02:00
}
2023-05-10 00:42:42 +02:00
impl ClientState {
2023-05-08 00:05:45 +02:00
pub fn new() -> Self {
2023-05-10 00:42:42 +02:00
ClientState {
2023-05-08 22:31:01 +02:00
active_category_id: None,
2023-05-11 16:51:57 +02:00
edit_item: None,
2023-08-29 21:33:59 +02:00
trip_edit_attribute: None,
2023-05-08 00:05:45 +02:00
}
}
}
2023-05-10 00:48:25 +02:00
impl Default for ClientState {
fn default() -> Self {
Self::new()
}
}
2023-05-08 00:05:45 +02:00
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
2023-05-08 22:31:01 +02:00
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
2023-05-10 00:42:42 +02:00
let database_pool = SqlitePoolOptions::new()
.max_connections(5)
.connect_with(
SqliteConnectOptions::new()
2023-05-11 16:51:57 +02:00
.filename(std::env::var("SQLITE_DATABASE").expect("env SQLITE_DATABASE not found"))
2023-05-10 00:42:42 +02:00
.pragma("foreign_keys", "1"),
)
.await
.unwrap();
let state = AppState {
database_pool,
client_state: ClientState::new(),
};
2023-05-08 00:05:45 +02:00
// build our application with a route
let app = Router::new()
.route("/", get(root))
.route("/trips/", get(trips))
2023-05-18 00:11:52 +02:00
.route("/trip/", post(trip_create))
.route("/trip/:id/", get(trip))
2023-08-29 21:33:59 +02:00
.route("/trip/:id/comment/submit", post(trip_comment_set))
2023-05-18 00:11:52 +02:00
.route("/trip/:id/type/:id/add", get(trip_type_add))
.route("/trip/:id/type/:id/remove", get(trip_type_remove))
2023-08-29 21:33:59 +02:00
.route(
"/trip/:id/edit/:attribute/submit",
post(trip_edit_attribute),
)
2023-05-08 00:05:45 +02:00
.route("/inventory/", get(inventory_inactive))
2023-05-10 00:42:42 +02:00
.route("/inventory/item/", post(inventory_item_create))
2023-05-18 00:11:52 +02:00
.route("/inventory/category/:id/", get(inventory_active))
2023-05-10 00:42:42 +02:00
.route("/inventory/item/:id/delete", get(inventory_item_delete))
2023-05-12 00:31:08 +02:00
.route("/inventory/item/:id/edit", post(inventory_item_edit))
.route("/inventory/item/:id/cancel", get(inventory_item_cancel))
2023-05-08 22:31:01 +02:00
// .route(
// "/inventory/category/:id/items",
// post(htmx_inventory_category_items),
// );
2023-05-10 00:42:42 +02:00
.with_state(state);
2023-05-08 00:05:45 +02:00
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
Ok(())
}
2023-08-29 21:33:59 +02:00
async fn root() -> (StatusCode, Markup) {
2023-05-08 00:05:45 +02:00
(
StatusCode::OK,
2023-08-29 21:33:59 +02:00
Root::build(Home::build(), &TopLevelPage::None),
2023-05-08 00:05:45 +02:00
)
}
2023-05-11 16:51:57 +02:00
#[derive(Deserialize)]
struct InventoryQuery {
edit_item: Option<Uuid>,
}
impl Default for InventoryQuery {
fn default() -> Self {
Self { edit_item: None }
}
}
2023-05-08 00:05:45 +02:00
async fn inventory_active(
2023-05-18 00:11:52 +02:00
Path(id): Path<Uuid>,
2023-05-11 16:51:57 +02:00
State(mut state): State<AppState>,
Query(inventory_query): Query<InventoryQuery>,
2023-08-29 21:33:59 +02:00
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
2023-05-11 16:51:57 +02:00
state.client_state.edit_item = inventory_query.edit_item;
2023-05-10 00:42:42 +02:00
inventory(state, Some(id)).await
2023-05-08 00:05:45 +02:00
}
2023-05-10 00:42:42 +02:00
async fn inventory_inactive(
2023-05-11 16:51:57 +02:00
State(mut state): State<AppState>,
Query(inventory_query): Query<InventoryQuery>,
2023-08-29 21:33:59 +02:00
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
2023-05-11 16:51:57 +02:00
state.client_state.edit_item = inventory_query.edit_item;
2023-05-10 00:42:42 +02:00
inventory(state, None).await
2023-05-08 00:05:45 +02:00
}
async fn inventory(
2023-05-10 00:42:42 +02:00
mut state: AppState,
2023-05-18 00:11:52 +02:00
active_id: Option<Uuid>,
2023-08-29 21:33:59 +02:00
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
2023-05-10 00:42:42 +02:00
state.client_state.active_category_id = active_id;
2023-05-08 00:05:45 +02:00
2023-05-10 00:42:42 +02:00
let mut categories = query("SELECT id,name,description FROM inventoryitemcategories")
.fetch(&state.database_pool)
2023-05-10 01:02:37 +02:00
.map_ok(std::convert::TryInto::try_into)
2023-05-08 00:05:45 +02:00
.try_collect::<Vec<Result<Category, models::Error>>>()
.await
// we have two error handling lines here. these are distinct errors
// this one is the SQL error that may arise during the query
2023-08-29 21:33:59 +02:00
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?
2023-05-08 00:05:45 +02:00
.into_iter()
.collect::<Result<Vec<Category>, models::Error>>()
// and this one is the model mapping error that may arise e.g. during
// reading of the rows
2023-08-29 21:33:59 +02:00
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?;
2023-05-08 00:05:45 +02:00
for category in &mut categories {
category
2023-05-11 16:51:57 +02:00
.populate_items(&state.database_pool)
2023-05-08 00:05:45 +02:00
.await
2023-08-29 21:33:59 +02:00
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?;
2023-05-08 00:05:45 +02:00
}
Ok((
StatusCode::OK,
2023-08-29 21:33:59 +02:00
Root::build(
Inventory::build(state.client_state, categories)
.await
.map_err(|e| match e {
Error::NotFoundError { description } => {
(StatusCode::NOT_FOUND, ErrorPage::build(&description))
}
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
),
})?,
&TopLevelPage::Inventory,
2023-05-08 00:05:45 +02:00
),
))
}
2023-05-10 00:42:42 +02:00
#[derive(Deserialize)]
struct NewItem {
#[serde(rename = "new-item-name")]
name: String,
#[serde(rename = "new-item-weight")]
weight: u32,
// damn i just love how serde is integrated everywhere, just add a feature to the uuid in
// cargo.toml and go
#[serde(rename = "new-item-category-id")]
category_id: Uuid,
}
async fn inventory_item_create(
State(state): State<AppState>,
Form(new_item): Form<NewItem>,
) -> Result<Redirect, (StatusCode, String)> {
query(
"INSERT INTO inventoryitems
(id, name, description, weight, category_id)
VALUES
(?, ?, ?, ?, ?)",
)
.bind(Uuid::new_v4().to_string())
.bind(&new_item.name)
.bind("")
.bind(new_item.weight)
.bind(new_item.category_id.to_string())
.execute(&state.database_pool)
.await
.map_err(|e| match e {
sqlx::Error::Database(ref error) => {
let sqlite_error = error.downcast_ref::<SqliteError>();
if let Some(code) = sqlite_error.code() {
match &*code {
"787" => {
// SQLITE_CONSTRAINT_FOREIGNKEY
(
StatusCode::BAD_REQUEST,
format!("category {id} not found", id = new_item.category_id),
)
}
"2067" => {
// SQLITE_CONSTRAINT_UNIQUE
(
StatusCode::BAD_REQUEST,
format!(
"item with name \"{name}\" already exists in category {id}",
name = new_item.name,
id = new_item.category_id
),
)
}
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("got error with unknown code: {}", sqlite_error.to_string()),
),
}
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("got error without code: {}", sqlite_error.to_string()),
)
}
}
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("got unknown error: {}", e.to_string()),
),
})?;
Ok(Redirect::to(&format!(
2023-05-18 00:11:52 +02:00
"/inventory/category/{id}/",
2023-05-10 00:42:42 +02:00
id = new_item.category_id
)))
}
async fn inventory_item_delete(
State(state): State<AppState>,
headers: HeaderMap,
Path(id): Path<Uuid>,
) -> Result<Redirect, (StatusCode, String)> {
2023-05-12 00:31:08 +02:00
let results = query(
2023-05-10 00:42:42 +02:00
"DELETE FROM inventoryitems
WHERE id = ?",
)
.bind(id.to_string())
.execute(&state.database_pool)
.await
2023-05-10 00:48:25 +02:00
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
2023-05-10 00:42:42 +02:00
2023-05-12 00:31:08 +02:00
if results.rows_affected() == 0 {
Err((
StatusCode::NOT_FOUND,
format!("item with id {id} not found", id = id),
))
} else {
Ok(Redirect::to(
headers
.get("referer")
.ok_or((
2023-05-10 00:42:42 +02:00
StatusCode::BAD_REQUEST,
2023-05-12 00:31:08 +02:00
"no referer header found".to_string(),
))?
.to_str()
.map_err(|e| {
(
StatusCode::BAD_REQUEST,
format!("referer could not be converted: {}", e),
)
})?,
))
}
2023-05-10 00:42:42 +02:00
}
2023-05-08 22:31:01 +02:00
// async fn htmx_inventory_category_items(
// Path(id): Path<String>,
2023-08-29 21:33:59 +02:00
// ) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
2023-05-08 22:31:01 +02:00
// let pool = SqlitePoolOptions::new()
// .max_connections(5)
// .connect("sqlite:///home/hannes-private/sync/items/items.sqlite")
// .await
// .unwrap();
2023-05-10 00:42:42 +02:00
// let items = query(&format!(
// //TODO bind this stuff!!!!!!! no sql injection pls
2023-05-08 22:31:01 +02:00
// "SELECT
// i.id, i.name, i.description, i.weight, i.category_id
// FROM inventoryitemcategories AS c
2023-05-18 00:11:52 +02:00
// INNER JOIN inventoryitems AS i
2023-05-08 22:31:01 +02:00
// ON i.category_id = c.id WHERE c.id = '{id}';",
// id = id,
// ))
// .fetch(&pool)
// .map_ok(|row| row.try_into())
// .try_collect::<Vec<Result<Item, models::Error>>>()
// .await
// // we have two error handling lines here. these are distinct errors
// // this one is the SQL error that may arise during the query
// .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Html::from(e.to_string())))?
// .into_iter()
// .collect::<Result<Vec<Item>, models::Error>>()
// // and this one is the model mapping error that may arise e.g. during
// // reading of the rows
// .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Html::from(e.to_string())))?;
// Ok((
// StatusCode::OK,
// Html::from(
// InventoryItemList::build(&items)
// .await
// .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Html::from(e.to_string())))?
// .to_string(),
// ),
// ))
// }
2023-05-12 00:31:08 +02:00
#[derive(Deserialize)]
struct EditItem {
#[serde(rename = "edit-item-name")]
name: String,
#[serde(rename = "edit-item-weight")]
weight: u32,
}
async fn inventory_item_edit(
State(state): State<AppState>,
Path(id): Path<Uuid>,
Form(edit_item): Form<EditItem>,
) -> Result<Redirect, (StatusCode, String)> {
let id = Item::update(&state.database_pool, id, &edit_item.name, edit_item.weight)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((
StatusCode::NOT_FOUND,
format!("item with id {id} not found", id = id),
))?;
2023-05-18 00:11:52 +02:00
Ok(Redirect::to(&format!("/inventory/category/{id}/", id = id)))
2023-05-12 00:31:08 +02:00
}
async fn inventory_item_cancel(
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<Redirect, (StatusCode, String)> {
let id = Item::find(&state.database_pool, id)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((
StatusCode::NOT_FOUND,
format!("item with id {id} not found", id = id),
))?;
Ok(Redirect::to(&format!(
2023-05-18 00:11:52 +02:00
"/inventory/category/{id}/",
2023-05-12 00:31:08 +02:00
id = id.category_id
)))
}
2023-05-18 00:11:52 +02:00
#[derive(Deserialize)]
struct NewTrip {
#[serde(rename = "new-trip-name")]
name: String,
#[serde(rename = "new-trip-start-date")]
2023-08-29 21:33:59 +02:00
date_start: time::Date,
2023-05-18 00:11:52 +02:00
#[serde(rename = "new-trip-end-date")]
2023-08-29 21:33:59 +02:00
date_end: time::Date,
2023-05-18 00:11:52 +02:00
}
async fn trip_create(
State(state): State<AppState>,
Form(new_trip): Form<NewTrip>,
) -> Result<Redirect, (StatusCode, String)> {
let id = Uuid::new_v4();
query(
"INSERT INTO trips
2023-08-29 21:33:59 +02:00
(id, name, date_start, date_end)
2023-05-18 00:11:52 +02:00
VALUES
(?, ?, ?, ?)",
)
.bind(id.to_string())
.bind(&new_trip.name)
2023-08-29 21:33:59 +02:00
.bind(new_trip.date_start)
.bind(new_trip.date_end)
2023-05-18 00:11:52 +02:00
.execute(&state.database_pool)
.await
.map_err(|e| match e {
sqlx::Error::Database(ref error) => {
let sqlite_error = error.downcast_ref::<SqliteError>();
if let Some(code) = sqlite_error.code() {
match &*code {
"2067" => {
// SQLITE_CONSTRAINT_UNIQUE
(
StatusCode::BAD_REQUEST,
format!(
"trip with name \"{name}\" already exists",
name = new_trip.name,
),
)
}
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("got error with unknown code: {}", sqlite_error.to_string()),
),
}
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("got error without code: {}", sqlite_error.to_string()),
)
}
}
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("got unknown error: {}", e.to_string()),
),
})?;
Ok(Redirect::to(&format!("/trips/{id}/", id = id.to_string())))
}
async fn trips(
State(state): State<AppState>,
2023-08-29 21:33:59 +02:00
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
2023-05-18 00:11:52 +02:00
let trips: Vec<models::Trip> = query("SELECT * FROM trips")
.fetch(&state.database_pool)
.map_ok(std::convert::TryInto::try_into)
.try_collect::<Vec<Result<models::Trip, models::Error>>>()
.await
// we have two error handling lines here. these are distinct errors
// this one is the SQL error that may arise during the query
2023-08-29 21:33:59 +02:00
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?
2023-05-18 00:11:52 +02:00
.into_iter()
.collect::<Result<Vec<models::Trip>, models::Error>>()
// and this one is the model mapping error that may arise e.g. during
// reading of the rows
2023-08-29 21:33:59 +02:00
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?;
2023-05-18 00:11:52 +02:00
Ok((
StatusCode::OK,
2023-08-29 21:33:59 +02:00
Root::build(TripManager::build(trips), &TopLevelPage::Trips),
2023-05-18 00:11:52 +02:00
))
}
2023-08-29 21:33:59 +02:00
#[derive(Debug, Deserialize)]
struct TripQuery {
edit: Option<TripAttribute>,
}
2023-05-18 00:11:52 +02:00
async fn trip(
Path(id): Path<Uuid>,
2023-08-29 21:33:59 +02:00
State(mut state): State<AppState>,
Query(trip_query): Query<TripQuery>,
2023-08-29 21:33:59 +02:00
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
2023-08-29 21:33:59 +02:00
state.client_state.trip_edit_attribute = trip_query.edit;
2023-05-18 00:11:52 +02:00
let mut trip: models::Trip =
2023-08-29 21:33:59 +02:00
query("SELECT id,name,date_start,date_end,state,location,temp_min,temp_max,comment FROM trips WHERE id = ?")
2023-05-18 00:11:52 +02:00
.bind(id.to_string())
.fetch_one(&state.database_pool)
.map_ok(std::convert::TryInto::try_into)
.await
.map_err(|e: sqlx::Error| match e {
sqlx::Error::RowNotFound => (
StatusCode::NOT_FOUND,
2023-08-29 21:33:59 +02:00
ErrorPage::build(&format!("trip with id {} not found", id)),
2023-05-18 00:11:52 +02:00
),
2023-08-29 21:33:59 +02:00
_ => (StatusCode::INTERNAL_SERVER_ERROR, ErrorPage::build(&e.to_string())),
2023-05-18 00:11:52 +02:00
})?
2023-08-29 21:33:59 +02:00
.map_err(|e: Error| (StatusCode::INTERNAL_SERVER_ERROR, ErrorPage::build(&e.to_string())))?;
2023-05-18 00:11:52 +02:00
trip.load_triptypes(&state.database_pool)
.await
2023-08-29 21:33:59 +02:00
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?;
2023-05-18 00:11:52 +02:00
Ok((
StatusCode::OK,
2023-08-29 21:33:59 +02:00
Root::build(
components::Trip::build(&state.client_state, &trip),
&TopLevelPage::Trips,
),
2023-05-18 00:11:52 +02:00
))
}
async fn trip_type_remove(
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
State(state): State<AppState>,
2023-08-29 21:33:59 +02:00
) -> Result<Redirect, (StatusCode, Markup)> {
2023-05-18 00:11:52 +02:00
let results = query(
"DELETE FROM trips_to_triptypes AS ttt
WHERE ttt.trip_id = ?
AND ttt.trip_type_id = ?
",
)
.bind(trip_id.to_string())
.bind(type_id.to_string())
.execute(&state.database_pool)
.await
2023-08-29 21:33:59 +02:00
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
2023-05-18 00:11:52 +02:00
if results.rows_affected() == 0 {
Err((
StatusCode::NOT_FOUND,
2023-08-29 21:33:59 +02:00
ErrorPage::build(&format!("type {type_id} is not active for trip {trip_id}")),
2023-05-18 00:11:52 +02:00
))
} else {
Ok(Redirect::to(&format!("/trip/{trip_id}/")))
}
}
async fn trip_type_add(
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
State(state): State<AppState>,
2023-08-29 21:33:59 +02:00
) -> Result<Redirect, (StatusCode, Markup)> {
query(
2023-05-18 00:11:52 +02:00
"INSERT INTO trips_to_triptypes
(trip_id, trip_type_id) VALUES (?, ?)",
)
.bind(trip_id.to_string())
.bind(type_id.to_string())
.execute(&state.database_pool)
.await
.map_err(|e| match e {
sqlx::Error::Database(ref error) => {
let sqlite_error = error.downcast_ref::<SqliteError>();
if let Some(code) = sqlite_error.code() {
match &*code {
"787" => {
// SQLITE_CONSTRAINT_FOREIGNKEY
(
StatusCode::BAD_REQUEST,
// TODO: this is not perfect, as both foreign keys
// may be responsible for the error. how can we tell
// which one?
2023-08-29 21:33:59 +02:00
ErrorPage::build(&format!("invalid id: {}", code.to_string())),
2023-05-18 00:11:52 +02:00
)
}
"2067" => {
// SQLITE_CONSTRAINT_UNIQUE
(
StatusCode::BAD_REQUEST,
2023-08-29 21:33:59 +02:00
ErrorPage::build(&format!(
2023-05-18 00:11:52 +02:00
"type {type_id} is already active for trip {trip_id}"
)),
)
}
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
2023-08-29 21:33:59 +02:00
ErrorPage::build(&format!(
2023-05-18 00:11:52 +02:00
"got error with unknown code: {}",
sqlite_error.to_string()
)),
),
}
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
2023-08-29 21:33:59 +02:00
ErrorPage::build(&format!(
2023-05-18 00:11:52 +02:00
"got error without code: {}",
sqlite_error.to_string()
)),
)
}
}
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
2023-08-29 21:33:59 +02:00
ErrorPage::build(&format!("got unknown error: {}", e.to_string())),
2023-05-18 00:11:52 +02:00
),
})?;
Ok(Redirect::to(&format!("/trip/{trip_id}/")))
}
2023-08-29 21:33:59 +02:00
#[derive(Deserialize)]
struct CommentUpdate {
#[serde(rename = "new-comment")]
new_comment: String,
}
async fn trip_comment_set(
Path(trip_id): Path<Uuid>,
State(state): State<AppState>,
Form(comment_update): Form<CommentUpdate>,
) -> Result<Redirect, (StatusCode, Markup)> {
let result = query(
"UPDATE trips
SET comment = ?
WHERE id = ?",
)
.bind(comment_update.new_comment)
.bind(trip_id.to_string())
.execute(&state.database_pool)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
if result.rows_affected() == 0 {
Err((
StatusCode::NOT_FOUND,
ErrorPage::build(&format!("trip with id {id} not found", id = trip_id)),
))
} else {
Ok(Redirect::to(&format!("/trip/{id}/", id = trip_id)))
}
}
#[derive(Deserialize)]
struct TripUpdate {
#[serde(rename = "new-value")]
new_value: String,
}
async fn trip_edit_attribute(
Path((trip_id, attribute)): Path<(Uuid, TripAttribute)>,
State(state): State<AppState>,
Form(trip_update): Form<TripUpdate>,
) -> Result<Redirect, (StatusCode, Markup)> {
let result = query(&format!(
"UPDATE trips
SET {attribute} = ?
WHERE id = ?",
attribute = to_variant_name(&attribute).unwrap()
))
.bind(trip_update.new_value)
.bind(trip_id.to_string())
.execute(&state.database_pool)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
if result.rows_affected() == 0 {
Err((
StatusCode::NOT_FOUND,
ErrorPage::build(&format!("trip with id {id} not found", id = trip_id)),
))
} else {
Ok(Redirect::to(&format!("/trips/")))
}
}