properly implement trip init & copying trips
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ -n "$1" ]] ; then
|
||||
export DATABASE_URL="sqlite://${1}"
|
||||
else
|
||||
db="$(mktemp)"
|
||||
|
||||
export DATABASE_URL="sqlite://${db}"
|
||||
fi
|
||||
|
||||
cargo sqlx database create
|
||||
cargo sqlx migrate run
|
||||
|
||||
@@ -532,7 +532,7 @@ impl Trip {
|
||||
#[tracing::instrument]
|
||||
pub async fn all(ctx: &Context, pool: &sqlite::Pool) -> Result<Vec<Trip>, Error> {
|
||||
let user_id = ctx.user.id.to_string();
|
||||
crate::query_all!(
|
||||
let mut trips = crate::query_all!(
|
||||
&sqlite::QueryClassification {
|
||||
query_type: sqlite::QueryType::Select,
|
||||
component: sqlite::Component::Trips,
|
||||
@@ -554,7 +554,10 @@ impl Trip {
|
||||
WHERE user_id = ?",
|
||||
user_id
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
|
||||
trips.sort_by_key(|trip| trip.date_start);
|
||||
Ok(trips)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -842,6 +845,7 @@ impl Trip {
|
||||
name: &str,
|
||||
date_start: time::Date,
|
||||
date_end: time::Date,
|
||||
copy_from: Option<Uuid>,
|
||||
) -> Result<Uuid, Error> {
|
||||
let user_id = ctx.user.id.to_string();
|
||||
let id = Uuid::new_v4();
|
||||
@@ -851,12 +855,14 @@ impl Trip {
|
||||
|
||||
let trip_state = TripState::new();
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
crate::execute!(
|
||||
&sqlite::QueryClassification {
|
||||
query_type: sqlite::QueryType::Insert,
|
||||
component: sqlite::Component::Trips,
|
||||
},
|
||||
pool,
|
||||
&mut *transaction,
|
||||
"INSERT INTO trips
|
||||
(id, name, date_start, date_end, state, user_id)
|
||||
VALUES
|
||||
@@ -870,6 +876,70 @@ impl Trip {
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(copy_from_trip_id) = copy_from {
|
||||
let copy_from_trip_id_param = copy_from_trip_id.to_string();
|
||||
crate::execute!(
|
||||
&sqlite::QueryClassification {
|
||||
query_type: sqlite::QueryType::Insert,
|
||||
component: sqlite::Component::Trips,
|
||||
},
|
||||
&mut *transaction,
|
||||
r#"INSERT INTO trips_items (
|
||||
item_id,
|
||||
trip_id,
|
||||
pick,
|
||||
pack,
|
||||
ready,
|
||||
new,
|
||||
user_id
|
||||
) SELECT
|
||||
item_id,
|
||||
$1 as trip_id,
|
||||
pick,
|
||||
false as pack,
|
||||
false as ready,
|
||||
false as new,
|
||||
user_id
|
||||
FROM trips_items
|
||||
WHERE trip_id = $2 AND user_id = $3"#,
|
||||
id_param,
|
||||
copy_from_trip_id_param,
|
||||
user_id,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
crate::execute!(
|
||||
&sqlite::QueryClassification {
|
||||
query_type: sqlite::QueryType::Insert,
|
||||
component: sqlite::Component::Trips,
|
||||
},
|
||||
&mut *transaction,
|
||||
r#"INSERT INTO trips_items (
|
||||
item_id,
|
||||
trip_id,
|
||||
pick,
|
||||
pack,
|
||||
ready,
|
||||
new,
|
||||
user_id
|
||||
) SELECT
|
||||
id as item_id,
|
||||
$1 as trip_id,
|
||||
false as pick,
|
||||
false as pack,
|
||||
false as ready,
|
||||
false as new,
|
||||
user_id
|
||||
FROM inventory_items
|
||||
WHERE user_id = $2"#,
|
||||
id_param,
|
||||
user_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -1019,7 +1089,7 @@ impl Trip {
|
||||
//
|
||||
// * if the trip is new (it's state is INITIAL), we can just forward
|
||||
// as-is
|
||||
// * if the trip is new, we have to make these new items prominently
|
||||
// * if the trip is not new, we have to make these new items prominently
|
||||
// visible so the user knows that there might be new items to
|
||||
// consider
|
||||
let user_id = ctx.user.id.to_string();
|
||||
|
||||
@@ -6,8 +6,10 @@ use axum::{
|
||||
routing::{get, post},
|
||||
BoxError, Router,
|
||||
};
|
||||
use serde::de;
|
||||
use uuid::Uuid;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::{fmt, time::Duration};
|
||||
use tower::{timeout::TimeoutLayer, ServiceBuilder};
|
||||
|
||||
use crate::{AppState, Error, RequestError, TopLevelPage};
|
||||
@@ -31,6 +33,33 @@ fn get_referer(headers: &HeaderMap) -> Result<&str, Error> {
|
||||
})
|
||||
}
|
||||
|
||||
fn uuid_or_empty<'de, D>(input: D) -> Result<Option<Uuid>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct NoneVisitor;
|
||||
|
||||
impl<'vi> de::Visitor<'vi> for NoneVisitor {
|
||||
type Value = Option<Uuid>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "invalid input")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
if value.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Uuid::try_from(value).map_err(|e| {
|
||||
E::custom(format!("UUID parsing failed: {}", e))
|
||||
})?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.deserialize_str(NoneVisitor)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn router(state: AppState) -> Router {
|
||||
Router::new()
|
||||
|
||||
@@ -18,11 +18,13 @@ use crate::{AppState, Context, Error, RequestError, TopLevelPage};
|
||||
use super::{get_referer, html};
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct InventoryQuery {
|
||||
edit_item: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NewItem {
|
||||
#[serde(rename = "new-item-name")]
|
||||
name: String,
|
||||
@@ -35,12 +37,14 @@ pub struct NewItem {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NewItemName {
|
||||
#[serde(rename = "new-item-name")]
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EditItem {
|
||||
#[serde(rename = "edit-item-name")]
|
||||
name: String,
|
||||
@@ -49,6 +53,7 @@ pub struct EditItem {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NewTrip {
|
||||
#[serde(rename = "new-trip-name")]
|
||||
name: String,
|
||||
@@ -56,44 +61,56 @@ pub struct NewTrip {
|
||||
date_start: time::Date,
|
||||
#[serde(rename = "new-trip-end-date")]
|
||||
date_end: time::Date,
|
||||
#[serde(
|
||||
rename = "new-trip-copy-from",
|
||||
deserialize_with = "super::uuid_or_empty"
|
||||
)]
|
||||
copy_from: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TripQuery {
|
||||
edit: Option<models::trips::TripAttribute>,
|
||||
category: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CommentUpdate {
|
||||
#[serde(rename = "new-comment")]
|
||||
new_comment: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TripUpdate {
|
||||
#[serde(rename = "new-value")]
|
||||
new_value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NewCategory {
|
||||
#[serde(rename = "new-category-name")]
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TripTypeQuery {
|
||||
edit: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NewTripType {
|
||||
#[serde(rename = "new-trip-type-name")]
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TripTypeUpdate {
|
||||
#[serde(rename = "new-value")]
|
||||
new_value: String,
|
||||
@@ -367,6 +384,7 @@ pub async fn trip_create(
|
||||
&new_trip.name,
|
||||
new_trip.date_start,
|
||||
new_trip.date_end,
|
||||
new_trip.copy_from,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ impl<'a> Component for HeaderLink<'a> {
|
||||
hx-get=(self.args.item.path())
|
||||
hx-target={ "#" (self.htmx.target().html_id()) }
|
||||
hx-swap="outerHtml"
|
||||
hx-push-url="true"
|
||||
#{"header-link-" (self.args.item.id())}
|
||||
."px-5"
|
||||
."flex"
|
||||
|
||||
@@ -22,8 +22,8 @@ impl TripManager {
|
||||
."gap-8"
|
||||
{
|
||||
h1 ."text-2xl" {"Trips"}
|
||||
(TripTable::build(trips))
|
||||
(NewTrip::build())
|
||||
(TripTable::build(&trips))
|
||||
(NewTrip::build(&trips))
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ pub struct TripTable;
|
||||
|
||||
impl TripTable {
|
||||
#[tracing::instrument]
|
||||
pub fn build(trips: Vec<models::trips::Trip>) -> Markup {
|
||||
pub fn build(trips: &Vec<models::trips::Trip>) -> Markup {
|
||||
html!(
|
||||
table
|
||||
."table"
|
||||
@@ -125,8 +125,8 @@ impl TripTableRow {
|
||||
pub struct NewTrip;
|
||||
|
||||
impl NewTrip {
|
||||
#[tracing::instrument]
|
||||
pub fn build() -> Markup {
|
||||
#[tracing::instrument(skip(trips))]
|
||||
pub fn build(trips: &Vec<models::trips::Trip>) -> Markup {
|
||||
html!(
|
||||
form
|
||||
name="new_trip"
|
||||
@@ -161,7 +161,7 @@ impl NewTrip {
|
||||
}
|
||||
div ."mx-auto" ."pb-8" {
|
||||
div ."flex" ."flex-row" ."justify-center" {
|
||||
label for="trip-name" ."font-bold" ."w-1/2" ."p-2" ."text-center" { "Start date" }
|
||||
label for="start-date" ."font-bold" ."w-1/2" ."p-2" ."text-center" { "Start date" }
|
||||
span ."w-1/2" {
|
||||
input
|
||||
type="date"
|
||||
@@ -183,7 +183,7 @@ impl NewTrip {
|
||||
}
|
||||
div ."mx-auto" ."pb-8" {
|
||||
div ."flex" ."flex-row" ."justify-center" {
|
||||
label for="trip-name" ."font-bold" ."w-1/2" ."p-2" ."text-center" { "Start date" }
|
||||
label for="end-date" ."font-bold" ."w-1/2" ."p-2" ."text-center" { "End date" }
|
||||
span ."w-1/2" {
|
||||
input
|
||||
type="date"
|
||||
@@ -203,6 +203,37 @@ impl NewTrip {
|
||||
}
|
||||
}
|
||||
}
|
||||
div ."mx-auto" ."pb-8" {
|
||||
div ."flex" ."flex-row" ."justify-center" {
|
||||
label for="copy-from" ."font-bold" ."w-1/2" ."p-2" ."text-center" { "Reuse trip" }
|
||||
span ."w-1/2" {
|
||||
select
|
||||
id="copy-from"
|
||||
name="new-trip-copy-from"
|
||||
."block"
|
||||
."w-full"
|
||||
."p-2"
|
||||
."bg-gray-50"
|
||||
."border-2"
|
||||
."border-gray-300"
|
||||
."focus:outline-none"
|
||||
."focus:bg-white"
|
||||
."focus:border-purple-500"
|
||||
{
|
||||
option value="" { "[None]" }
|
||||
@for trip in trips.iter().rev() {
|
||||
option value=(trip.id) {
|
||||
(format!("{year}-{month:02} {name}",
|
||||
year = trip.date_start.year(),
|
||||
month = trip.date_start.month() as u8,
|
||||
name = trip.name
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
input
|
||||
type="submit"
|
||||
value="Add"
|
||||
|
||||
Reference in New Issue
Block a user