This commit is contained in:
2023-08-29 21:34:01 +02:00
parent ec6b6090fc
commit 5eee5309ad
13 changed files with 66 additions and 75 deletions

View File

@@ -6,7 +6,7 @@ use super::models;
use super::{AppState, Error, RequestError}; use super::{AppState, Error, RequestError};
#[derive(Clone)] #[derive(Clone)]
pub enum AuthConfig { pub enum Config {
Enabled, Enabled,
Disabled { assume_user: String }, Disabled { assume_user: String },
} }
@@ -17,7 +17,7 @@ pub async fn authorize<B>(
next: Next<B>, next: Next<B>,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let current_user = match state.auth_config { let current_user = match state.auth_config {
AuthConfig::Disabled { assume_user } => { Config::Disabled { assume_user } => {
match models::user::User::find_by_name(&state.database_pool, &assume_user).await? { match models::user::User::find_by_name(&state.database_pool, &assume_user).await? {
Some(user) => user, Some(user) => user,
None => { None => {
@@ -27,7 +27,7 @@ pub async fn authorize<B>(
} }
} }
} }
AuthConfig::Enabled => { Config::Enabled => {
let Some(username) = request.headers().get("x-auth-username") else { let Some(username) = request.headers().get("x-auth-username") else {
return Err(Error::Request(RequestError::AuthenticationHeaderMissing)); return Err(Error::Request(RequestError::AuthenticationHeaderMissing));
}; };

View File

@@ -88,7 +88,7 @@ impl IntoResponse for Error {
Self::Model(ref model_error) => match model_error { Self::Model(ref model_error) => match model_error {
models::Error::Database(_) => ( models::Error::Database(_) => (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
view::ErrorPage::build(&format!("{}", self)), view::ErrorPage::build(&self.to_string()),
), ),
models::Error::Query(error) => match error { models::Error::Query(error) => match error {
models::QueryError::NotFound { description } => { models::QueryError::NotFound { description } => {
@@ -96,7 +96,7 @@ impl IntoResponse for Error {
} }
_ => ( _ => (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
view::ErrorPage::build(&format!("{}", error)), view::ErrorPage::build(&error.to_string()),
), ),
}, },
}, },
@@ -107,25 +107,22 @@ impl IntoResponse for Error {
), ),
RequestError::RefererInvalid { message } => ( RequestError::RefererInvalid { message } => (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
view::ErrorPage::build(&format!("referer could not be converted: {}", message)), view::ErrorPage::build(&format!("referer could not be converted: {message}")),
), ),
RequestError::EmptyFormElement { name } => ( RequestError::EmptyFormElement { name } => (
StatusCode::UNPROCESSABLE_ENTITY, StatusCode::UNPROCESSABLE_ENTITY,
view::ErrorPage::build(&format!("empty form element: {}", name)), view::ErrorPage::build(&format!("empty form element: {name}")),
), ),
RequestError::NotFound { message } => ( RequestError::NotFound { message } => (
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
view::ErrorPage::build(&format!("not found: {}", message)), view::ErrorPage::build(&format!("not found: {message}")),
), ),
RequestError::AuthenticationUserNotFound { username: _ } => ( RequestError::AuthenticationUserNotFound { username: _ } => (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
view::ErrorPage::build(&request_error.to_string()), view::ErrorPage::build(&request_error.to_string()),
), ),
RequestError::AuthenticationHeaderMissing => ( RequestError::AuthenticationHeaderMissing
StatusCode::UNAUTHORIZED, | RequestError::AuthenticationHeaderInvalid { message: _ } => (
view::ErrorPage::build(&request_error.to_string()),
),
RequestError::AuthenticationHeaderInvalid { message: _ } => (
StatusCode::UNAUTHORIZED, StatusCode::UNAUTHORIZED,
view::ErrorPage::build(&request_error.to_string()), view::ErrorPage::build(&request_error.to_string()),
), ),

View File

@@ -47,6 +47,5 @@ impl From<RequestHeaders> for HeaderName {
pub fn is_htmx(headers: &HeaderMap) -> bool { pub fn is_htmx(headers: &HeaderMap) -> bool {
headers headers
.get::<HeaderName>(RequestHeaders::HtmxRequest.into()) .get::<HeaderName>(RequestHeaders::HtmxRequest.into())
.map(|value| value == "true") .map_or(false, |value| value == "true")
.unwrap_or(false)
} }

View File

@@ -17,7 +17,7 @@ pub use error::{CommandError, Error, RequestError, StartError};
pub struct AppState { pub struct AppState {
pub database_pool: sqlite::Pool<sqlite::Sqlite>, pub database_pool: sqlite::Pool<sqlite::Sqlite>,
pub client_state: ClientState, pub client_state: ClientState,
pub auth_config: auth::AuthConfig, pub auth_config: auth::Config,
} }
#[derive(Clone)] #[derive(Clone)]
@@ -64,9 +64,9 @@ impl fmt::Display for UriPath {
} }
} }
impl<'a> From<&'a UriPath> for &'a str { impl<'a> Into<&'a str> for &'a UriPath {
fn from(val: &'a UriPath) -> Self { fn into(self) -> &'a str {
val.0.as_str() self.0.as_str()
} }
} }

View File

@@ -95,9 +95,9 @@ async fn main() -> MainResult {
database_pool, database_pool,
client_state: ClientState::new(), client_state: ClientState::new(),
auth_config: if let Some(assume_user) = serve_args.disable_auth_and_assume_user { auth_config: if let Some(assume_user) = serve_args.disable_auth_and_assume_user {
auth::AuthConfig::Disabled { assume_user } auth::Config::Disabled { assume_user }
} else { } else {
auth::AuthConfig::Enabled auth::Config::Enabled
}, },
}; };

View File

@@ -77,8 +77,8 @@ pub enum Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::Database(error) => write!(f, "{}", error), Self::Database(error) => write!(f, "{error}"),
Self::Query(error) => write!(f, "{}", error), Self::Query(error) => write!(f, "{error}"),
} }
} }
} }
@@ -125,23 +125,17 @@ impl From<sqlx::Error> for Error {
description: "item with unique constraint already exists".to_string(), description: "item with unique constraint already exists".to_string(),
}), }),
_ => Error::Database(DatabaseError::Sql { _ => Error::Database(DatabaseError::Sql {
description: format!( description: format!("got error with unknown code: {sqlite_error}"),
"got error with unknown code: {}",
sqlite_error
),
}), }),
} }
} else { } else {
Error::Database(DatabaseError::Sql { Error::Database(DatabaseError::Sql {
description: format!( description: format!("got error without code: {sqlite_error}"),
"got error without code: {}",
sqlite_error
),
}) })
} }
} }
_ => Error::Database(DatabaseError::Sql { _ => Error::Database(DatabaseError::Sql {
description: format!("got unknown error: {}", value), description: format!("got unknown error: {value}"),
}), }),
} }
} }

View File

@@ -29,7 +29,7 @@ impl Inventory {
.collect::<Result<Vec<Category>, Error>>()?; .collect::<Result<Vec<Category>, Error>>()?;
for category in &mut categories { for category in &mut categories {
category.populate_items(ctx, pool).await?; category.populate_items(&ctx, &pool).await?;
} }
Ok(Self { categories }) Ok(Self { categories })
@@ -384,7 +384,7 @@ impl InventoryItem {
.map_ok(|row| { .map_ok(|row| {
// convert to i64 because that the default integer type, but looks // convert to i64 because that the default integer type, but looks
// like COALESCE return i32? // like COALESCE return i32?
row.weight as i64 i64::from(row.weight)
}) })
.await?; .await?;
@@ -451,7 +451,7 @@ impl Item {
.map_ok(|row| { .map_ok(|row| {
// convert to i64 because that the default integer type, but looks // convert to i64 because that the default integer type, but looks
// like COALESCE return i32? // like COALESCE return i32?
row.weight as i64 i64::from(row.weight)
}) })
.await?) .await?)
} }

View File

@@ -199,7 +199,7 @@ impl TripCategory {
items: None, items: None,
}, },
items: None, items: None,
}) });
} }
}; };
@@ -245,6 +245,7 @@ impl TripCategory {
} }
} }
// TODO refactor the bools into an enum
#[derive(Debug)] #[derive(Debug)]
pub struct TripItem { pub struct TripItem {
pub item: inventory::Item, pub item: inventory::Item,
@@ -673,7 +674,7 @@ impl Trip {
user_id, user_id,
) )
.fetch_one(pool) .fetch_one(pool)
.map_ok(|row| row.total_weight.unwrap() as i64) .map_ok(|row| i64::from(row.total_weight.unwrap()))
.await?; .await?;
Ok(weight) Ok(weight)

View File

@@ -1,5 +1,5 @@
use maud::{html, Markup}; use maud::{html, Markup};
pub fn concat(a: Markup, b: Markup) -> Markup { pub fn concat(a: &Markup, b: &Markup) -> Markup {
html!((a)(b)) html!((a)(b))
} }

View File

@@ -113,15 +113,13 @@ pub async fn icon() -> impl IntoResponse {
} }
pub async fn debug(headers: HeaderMap) -> impl IntoResponse { pub async fn debug(headers: HeaderMap) -> impl IntoResponse {
{
let mut out = String::new(); let mut out = String::new();
for (key, value) in headers.iter() { for (key, value) in headers.iter() {
out.push_str(&format!("{}: {}\n", key, value.to_str().unwrap())); out.push_str(&format!("{}: {}\n", key, value.to_str().unwrap()));
} }
out out
}
} }
pub async fn inventory_active( pub async fn inventory_active(
Extension(current_user): Extension<models::user::User>, Extension(current_user): Extension<models::user::User>,
State(mut state): State<AppState>, State(mut state): State<AppState>,
@@ -255,12 +253,12 @@ pub async fn inventory_item_delete(
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
let deleted = models::inventory::InventoryItem::delete(&ctx, &state.database_pool, id).await?; let deleted = models::inventory::InventoryItem::delete(&ctx, &state.database_pool, id).await?;
if !deleted { if deleted {
Ok(Redirect::to(get_referer(&headers)?))
} else {
Err(Error::Request(RequestError::NotFound { Err(Error::Request(RequestError::NotFound {
message: format!("item with id {id} not found"), message: format!("item with id {id} not found"),
})) }))
} else {
Ok(Redirect::to(get_referer(&headers)?))
} }
} }
@@ -286,7 +284,7 @@ pub async fn inventory_item_edit(
) )
.await?; .await?;
Ok(Redirect::to(&format!("/inventory/category/{id}/", id = id))) Ok(Redirect::to(&format!("/inventory/category/{id}/")))
} }
pub async fn inventory_item_cancel( pub async fn inventory_item_cancel(
@@ -385,7 +383,7 @@ pub async fn trip(
&ctx, &ctx,
&view::trip::Trip::build( &view::trip::Trip::build(
&trip, &trip,
state.client_state.trip_edit_attribute, state.client_state.trip_edit_attribute.as_ref(),
active_category, active_category,
), ),
Some(&TopLevelPage::Trips), Some(&TopLevelPage::Trips),
@@ -401,12 +399,12 @@ pub async fn trip_type_remove(
let found = let found =
models::trips::Trip::trip_type_remove(&ctx, &state.database_pool, trip_id, type_id).await?; models::trips::Trip::trip_type_remove(&ctx, &state.database_pool, trip_id, type_id).await?;
if !found { if found {
Ok(Redirect::to(&format!("/trips/{trip_id}/")))
} else {
Err(Error::Request(RequestError::NotFound { Err(Error::Request(RequestError::NotFound {
message: format!("type {type_id} is not active for trip {trip_id}"), message: format!("type {type_id} is not active for trip {trip_id}"),
})) }))
} else {
Ok(Redirect::to(&format!("/trips/{trip_id}/")))
} }
} }
@@ -441,7 +439,7 @@ pub async fn trip_comment_set(
message: format!("trip with id {trip_id} not found"), message: format!("trip with id {trip_id} not found"),
})) }))
} else { } else {
Ok(Redirect::to(&format!("/trips/{id}/", id = trip_id))) Ok(Redirect::to(&format!("/trips/{trip_id}/")))
} }
} }
@@ -452,11 +450,13 @@ pub async fn trip_edit_attribute(
Form(trip_update): Form<TripUpdate>, Form(trip_update): Form<TripUpdate>,
) -> Result<Redirect, Error> { ) -> Result<Redirect, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
if attribute == models::trips::TripAttribute::Name && trip_update.new_value.is_empty() { if attribute == models::trips::TripAttribute::Name {
if trip_update.new_value.is_empty() {
return Err(Error::Request(RequestError::EmptyFormElement { return Err(Error::Request(RequestError::EmptyFormElement {
name: "name".to_string(), name: "name".to_string(),
})); }));
} }
}
models::trips::Trip::set_attribute( models::trips::Trip::set_attribute(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
@@ -477,7 +477,7 @@ pub async fn trip_item_set_state(
key: models::trips::TripItemStateKey, key: models::trips::TripItemStateKey,
value: bool, value: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
models::trips::TripItem::set_state(ctx, &state.database_pool, trip_id, item_id, key, value) models::trips::TripItem::set_state(&ctx, &state.database_pool, trip_id, item_id, key, value)
.await?; .await?;
Ok(()) Ok(())
} }
@@ -500,7 +500,7 @@ pub async fn trip_row(
trip_id, trip_id,
&item, &item,
models::inventory::InventoryItem::get_category_max_weight( models::inventory::InventoryItem::get_category_max_weight(
ctx, &ctx,
&state.database_pool, &state.database_pool,
item.item.category_id, item.item.category_id,
) )
@@ -523,7 +523,7 @@ pub async fn trip_row(
// TODO biggest_category_weight? // TODO biggest_category_weight?
let category_row = view::trip::TripCategoryListRow::build(trip_id, &category, true, 0, true); let category_row = view::trip::TripCategoryListRow::build(trip_id, &category, true, 0, true);
Ok(html::concat(item_row, category_row)) Ok(html::concat(&item_row, &category_row))
} }
pub async fn trip_item_set_pick( pub async fn trip_item_set_pick(
@@ -841,7 +841,7 @@ pub async fn trip_state_set(
if htmx::is_htmx(&headers) { if htmx::is_htmx(&headers) {
Ok(view::trip::TripInfoStateRow::build(&new_state).into_response()) Ok(view::trip::TripInfoStateRow::build(&new_state).into_response())
} else { } else {
Ok(Redirect::to(&format!("/trips/{id}/", id = trip_id)).into_response()) Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response())
} }
} }
pub async fn trips_types( pub async fn trips_types(
@@ -899,12 +899,12 @@ pub async fn trips_types_edit_name(
) )
.await?; .await?;
if !exists { if exists {
Ok(Redirect::to("/trips/types/"))
} else {
Err(Error::Request(RequestError::NotFound { Err(Error::Request(RequestError::NotFound {
message: format!("trip type with id {trip_type_id} not found"), message: format!("trip type with id {trip_type_id} not found"),
})) }))
} else {
Ok(Redirect::to("/trips/types/"))
} }
} }

View File

@@ -570,7 +570,7 @@ impl InventoryItem {
} }
tr ."h-10" ."even:bg-gray-100" ."hover:bg-gray-100" ."h-full" { tr ."h-10" ."even:bg-gray-100" ."hover:bg-gray-100" ."h-full" {
td ."border" ."p-2" { "Description" } td ."border" ."p-2" { "Description" }
td ."border" ."p-2" { (item.description.clone().unwrap_or("".to_string())) } td ."border" ."p-2" { (item.description.clone().unwrap_or(String::new())) }
} }
tr ."h-10" ."even:bg-gray-100" ."hover:bg-gray-100" ."h-full" { tr ."h-10" ."even:bg-gray-100" ."hover:bg-gray-100" ."h-full" {
td ."border" ."p-2" { "Weight" } td ."border" ."p-2" { "Weight" }

View File

@@ -13,7 +13,7 @@ use crate::TopLevelPage;
impl Root { impl Root {
pub fn build(context: &Context, body: &Markup, active_page: Option<&TopLevelPage>) -> Markup { pub fn build(context: &Context, body: &Markup, active_page: Option<&TopLevelPage>) -> Markup {
let menu_item = |item: TopLevelPage, active_page: Option<&TopLevelPage>| { let menu_item = |item: TopLevelPage, active_page: Option<&TopLevelPage>| {
let active = active_page.map(|page| *page == item).unwrap_or(false); let active = active_page.map_or(false, |page| *page == item);
html!( html!(
a a
href=(item.path()) href=(item.path())

View File

@@ -219,7 +219,7 @@ pub struct Trip;
impl Trip { impl Trip {
pub fn build( pub fn build(
trip: &models::trips::Trip, trip: &models::trips::Trip,
trip_edit_attribute: Option<models::trips::TripAttribute>, trip_edit_attribute: Option<&models::trips::TripAttribute>,
active_category: Option<&models::trips::TripCategory>, active_category: Option<&models::trips::TripCategory>,
) -> Markup { ) -> Markup {
html!( html!(
@@ -253,7 +253,7 @@ impl Trip {
} }
} }
div ."flex" ."flex-row" ."items-center" ."gap-x-3" { div ."flex" ."flex-row" ."items-center" ."gap-x-3" {
@if trip_edit_attribute.as_ref().map_or(false, |a| *a == models::trips::TripAttribute::Name) { @if trip_edit_attribute.map_or(false, |a| *a == models::trips::TripAttribute::Name) {
form form
id="edit-trip" id="edit-trip"
action=(format!("edit/{}/submit", to_variant_name(&models::trips::TripAttribute::Name).unwrap())) action=(format!("edit/{}/submit", to_variant_name(&models::trips::TripAttribute::Name).unwrap()))
@@ -597,7 +597,7 @@ pub struct TripInfo;
impl TripInfo { impl TripInfo {
pub fn build( pub fn build(
trip_edit_attribute: Option<models::trips::TripAttribute>, trip_edit_attribute: Option<&models::trips::TripAttribute>,
trip: &models::trips::Trip, trip: &models::trips::Trip,
) -> Markup { ) -> Markup {
html!( html!(
@@ -613,31 +613,31 @@ impl TripInfo {
(TripInfoRow::build("Location", (TripInfoRow::build("Location",
trip.location.as_ref(), trip.location.as_ref(),
&models::trips::TripAttribute::Location, &models::trips::TripAttribute::Location,
trip_edit_attribute.as_ref(), trip_edit_attribute,
InputType::Text, InputType::Text,
)) ))
(TripInfoRow::build("Start date", (TripInfoRow::build("Start date",
Some(trip.date_start), Some(trip.date_start),
&models::trips::TripAttribute::DateStart, &models::trips::TripAttribute::DateStart,
trip_edit_attribute.as_ref(), trip_edit_attribute,
InputType::Date, InputType::Date,
)) ))
(TripInfoRow::build("End date", (TripInfoRow::build("End date",
Some(trip.date_end), Some(trip.date_end),
&models::trips::TripAttribute::DateEnd, &models::trips::TripAttribute::DateEnd,
trip_edit_attribute.as_ref(), trip_edit_attribute,
InputType::Date, InputType::Date,
)) ))
(TripInfoRow::build("Temp (min)", (TripInfoRow::build("Temp (min)",
trip.temp_min, trip.temp_min,
&models::trips::TripAttribute::TempMin, &models::trips::TripAttribute::TempMin,
trip_edit_attribute.as_ref(), trip_edit_attribute,
InputType::Number, InputType::Number,
)) ))
(TripInfoRow::build("Temp (max)", (TripInfoRow::build("Temp (max)",
trip.temp_max, trip.temp_max,
&models::trips::TripAttribute::TempMax, &models::trips::TripAttribute::TempMax,
trip_edit_attribute.as_ref(), trip_edit_attribute,
InputType::Number, InputType::Number,
)) ))
(TripInfoStateRow::build(&trip.state)) (TripInfoStateRow::build(&trip.state))