start refactor
This commit is contained in:
32
rust/.sqlx/query-3abc853a3a44c7ecb411f4374bcd05c87cd811a0f0d967d7be4bda908d8d45a1.json
generated
Normal file
32
rust/.sqlx/query-3abc853a3a44c7ecb411f4374bcd05c87cd811a0f0d967d7be4bda908d8d45a1.json
generated
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT id,username,fullname FROM users WHERE username = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fullname",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "3abc853a3a44c7ecb411f4374bcd05c87cd811a0f0d967d7be4bda908d8d45a1"
|
||||||
|
}
|
||||||
@@ -3,6 +3,16 @@ name = "packager"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
default-run = "packager"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "packager"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "packager-adm"
|
||||||
|
path = "src/bin/adm.rs"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
lto = "off"
|
lto = "off"
|
||||||
|
|||||||
6
rust/migrations/20230819125455_add_user.sql
Normal file
6
rust/migrations/20230819125455_add_user.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE "users" (
|
||||||
|
id VARCHAR(36) NOT NULL,
|
||||||
|
fullname TEXT NOT NULL,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);
|
||||||
8
rust/src/bin/adm.rs
Normal file
8
rust/src/bin/adm.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(long)]
|
||||||
|
database_url: String,
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ pub enum RequestError {
|
|||||||
RefererNotFound,
|
RefererNotFound,
|
||||||
RefererInvalid { message: String },
|
RefererInvalid { message: String },
|
||||||
NotFound { message: String },
|
NotFound { message: String },
|
||||||
|
AuthenticationUserNotFound { username: String },
|
||||||
|
AuthenticationHeaderMissing,
|
||||||
|
AuthenticationHeaderInvalid { message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for RequestError {
|
impl fmt::Display for RequestError {
|
||||||
@@ -22,6 +25,13 @@ impl fmt::Display for RequestError {
|
|||||||
Self::RefererNotFound => write!(f, "Referer header not found"),
|
Self::RefererNotFound => write!(f, "Referer header not found"),
|
||||||
Self::RefererInvalid { message } => write!(f, "Referer header invalid: {message}"),
|
Self::RefererInvalid { message } => write!(f, "Referer header invalid: {message}"),
|
||||||
Self::NotFound { message } => write!(f, "Not found: {message}"),
|
Self::NotFound { message } => write!(f, "Not found: {message}"),
|
||||||
|
Self::AuthenticationUserNotFound { username } => {
|
||||||
|
write!(f, "User \"{username}\" not found")
|
||||||
|
}
|
||||||
|
Self::AuthenticationHeaderMissing => write!(f, "Authentication header not found"),
|
||||||
|
Self::AuthenticationHeaderInvalid { message } => {
|
||||||
|
write!(f, "Authentication header invalid: {message}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,6 +113,18 @@ impl IntoResponse for Error {
|
|||||||
StatusCode::NOT_FOUND,
|
StatusCode::NOT_FOUND,
|
||||||
view::ErrorPage::build(&format!("not found: {}", message)),
|
view::ErrorPage::build(&format!("not found: {}", message)),
|
||||||
),
|
),
|
||||||
|
RequestError::AuthenticationUserNotFound { username: _ } => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
view::ErrorPage::build(&request_error.to_string()),
|
||||||
|
),
|
||||||
|
RequestError::AuthenticationHeaderMissing => (
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
view::ErrorPage::build(&request_error.to_string()),
|
||||||
|
),
|
||||||
|
RequestError::AuthenticationHeaderInvalid { message: _ } => (
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
view::ErrorPage::build(&request_error.to_string()),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
.into_response()
|
.into_response()
|
||||||
|
|||||||
5
rust/src/lib.rs
Normal file
5
rust/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod error;
|
||||||
|
mod models;
|
||||||
|
mod sqlite;
|
||||||
|
|
||||||
|
use error::{Error, RequestError, StartError};
|
||||||
247
rust/src/main.rs
247
rust/src/main.rs
@@ -1,11 +1,14 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, Query, State},
|
extract::{Extension, Path, Query, State},
|
||||||
http::header::{self, HeaderMap, HeaderName, HeaderValue},
|
http::header::{self, HeaderMap, HeaderName, HeaderValue},
|
||||||
|
middleware::{self, Next},
|
||||||
response::{IntoResponse, Redirect},
|
response::{IntoResponse, Redirect},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Form, Router,
|
Form, Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use hyper::Request;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@@ -14,18 +17,35 @@ use std::fmt;
|
|||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
mod error;
|
|
||||||
mod html;
|
mod html;
|
||||||
mod models;
|
|
||||||
mod sqlite;
|
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
type User = models::user::User;
|
||||||
|
|
||||||
use error::{Error, RequestError, StartError};
|
use error::{Error, RequestError, StartError};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum AuthConfig {
|
||||||
|
Enabled,
|
||||||
|
Disabled { assume_user: String },
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
database_pool: sqlite::Pool<sqlite::Sqlite>,
|
database_pool: sqlite::Pool<sqlite::Sqlite>,
|
||||||
client_state: ClientState,
|
client_state: ClientState,
|
||||||
|
auth_config: AuthConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Context {
|
||||||
|
user: User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
fn build(user: User) -> Self {
|
||||||
|
Self { user }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@@ -39,6 +59,8 @@ struct Args {
|
|||||||
port: u16,
|
port: u16,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
bind: String,
|
bind: String,
|
||||||
|
#[arg(long, name = "USERNAME")]
|
||||||
|
disable_auth_and_assume_user: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -156,6 +178,51 @@ impl From<HtmxRequestHeaders> for HeaderName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn authorize<B>(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
mut request: Request<B>,
|
||||||
|
next: Next<B>,
|
||||||
|
) -> Result<impl IntoResponse, Error> {
|
||||||
|
let current_user = match state.auth_config {
|
||||||
|
AuthConfig::Disabled { assume_user } => {
|
||||||
|
match models::user::User::find_by_name(&state.database_pool, &assume_user).await? {
|
||||||
|
Some(user) => user,
|
||||||
|
None => {
|
||||||
|
return Err(Error::Request(RequestError::AuthenticationUserNotFound {
|
||||||
|
username: assume_user,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AuthConfig::Enabled => {
|
||||||
|
let Some(username) = request.headers().get("x-auth-username") else {
|
||||||
|
return Err(Error::Request(RequestError::AuthenticationHeaderMissing));
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = username
|
||||||
|
.to_str()
|
||||||
|
.map_err(|error| {
|
||||||
|
Error::Request(RequestError::AuthenticationHeaderInvalid {
|
||||||
|
message: error.to_string(),
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
match models::user::User::find_by_name(&state.database_pool, &username).await? {
|
||||||
|
Some(user) => user,
|
||||||
|
None => {
|
||||||
|
return Err(Error::Request(RequestError::AuthenticationUserNotFound {
|
||||||
|
username,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.extensions_mut().insert(current_user);
|
||||||
|
Ok(next.run(request).await)
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), StartError> {
|
async fn main() -> Result<(), StartError> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
@@ -164,12 +231,17 @@ async fn main() -> Result<(), StartError> {
|
|||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let database_pool = sqlite::init_database_pool(&args.database_url).await?;
|
let database_pool = sqlie::init_database_pool(&args.database_url).await?;
|
||||||
sqlite::migrate(&database_pool).await?;
|
sqlite::migrate(&database_pool).await?;
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
database_pool,
|
database_pool,
|
||||||
client_state: ClientState::new(),
|
client_state: ClientState::new(),
|
||||||
|
auth_config: if let Some(assume_user) = args.disable_auth_and_assume_user {
|
||||||
|
AuthConfig::Disabled { assume_user }
|
||||||
|
} else {
|
||||||
|
AuthConfig::Enabled
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon_handler = || async {
|
let icon_handler = || async {
|
||||||
@@ -183,7 +255,6 @@ async fn main() -> Result<(), StartError> {
|
|||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/favicon.svg", get(icon_handler))
|
.route("/favicon.svg", get(icon_handler))
|
||||||
.route("/assets/luggage.svg", get(icon_handler))
|
.route("/assets/luggage.svg", get(icon_handler))
|
||||||
.route("/", get(root))
|
|
||||||
.route(
|
.route(
|
||||||
"/notfound",
|
"/notfound",
|
||||||
get(|| async {
|
get(|| async {
|
||||||
@@ -193,75 +264,81 @@ async fn main() -> Result<(), StartError> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.route("/debug", get(debug))
|
.route("/debug", get(debug))
|
||||||
.nest(
|
.merge(
|
||||||
(&TopLevelPage::Trips.path()).into(),
|
// thse are routes that require authentication
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(trips).post(trip_create))
|
.route("/", get(root))
|
||||||
.route("/types/", get(trips_types).post(trip_type_create))
|
.nest(
|
||||||
.route("/types/:id/edit/name/submit", post(trips_types_edit_name))
|
(&TopLevelPage::Trips.path()).into(),
|
||||||
.route("/:id/", get(trip))
|
Router::new()
|
||||||
.route("/:id/comment/submit", post(trip_comment_set))
|
.route("/", get(trips).post(trip_create))
|
||||||
.route("/:id/categories/:id/select", post(trip_category_select))
|
.route("/types/", get(trips_types).post(trip_type_create))
|
||||||
.route("/:id/packagelist/", get(trip_packagelist))
|
.route("/types/:id/edit/name/submit", post(trips_types_edit_name))
|
||||||
.route(
|
.route("/:id/", get(trip))
|
||||||
"/:id/packagelist/item/:id/pack",
|
.route("/:id/comment/submit", post(trip_comment_set))
|
||||||
post(trip_item_packagelist_set_pack_htmx),
|
.route("/:id/categories/:id/select", post(trip_category_select))
|
||||||
|
.route("/:id/packagelist/", get(trip_packagelist))
|
||||||
|
.route(
|
||||||
|
"/:id/packagelist/item/:id/pack",
|
||||||
|
post(trip_item_packagelist_set_pack_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/packagelist/item/:id/unpack",
|
||||||
|
post(trip_item_packagelist_set_unpack_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/packagelist/item/:id/ready",
|
||||||
|
post(trip_item_packagelist_set_ready_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/packagelist/item/:id/unready",
|
||||||
|
post(trip_item_packagelist_set_unready_htmx),
|
||||||
|
)
|
||||||
|
.route("/:id/state/:id", post(trip_state_set))
|
||||||
|
.route("/:id/total_weight", get(trip_total_weight_htmx))
|
||||||
|
.route("/:id/type/:id/add", get(trip_type_add))
|
||||||
|
.route("/:id/type/:id/remove", get(trip_type_remove))
|
||||||
|
.route("/:id/edit/:attribute/submit", post(trip_edit_attribute))
|
||||||
|
.route(
|
||||||
|
"/:id/items/:id/pick",
|
||||||
|
get(trip_item_set_pick).post(trip_item_set_pick_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/items/:id/unpick",
|
||||||
|
get(trip_item_set_unpick).post(trip_item_set_unpick_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/items/:id/pack",
|
||||||
|
get(trip_item_set_pack).post(trip_item_set_pack_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/items/:id/unpack",
|
||||||
|
get(trip_item_set_unpack).post(trip_item_set_unpack_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/items/:id/ready",
|
||||||
|
get(trip_item_set_ready).post(trip_item_set_ready_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:id/items/:id/unready",
|
||||||
|
get(trip_item_set_unready).post(trip_item_set_unready_htmx),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.route(
|
.nest(
|
||||||
"/:id/packagelist/item/:id/unpack",
|
(&TopLevelPage::Inventory.path()).into(),
|
||||||
post(trip_item_packagelist_set_unpack_htmx),
|
Router::new()
|
||||||
|
.route("/", get(inventory_inactive))
|
||||||
|
.route("/categories/:id/select", post(inventory_category_select))
|
||||||
|
.route("/category/", post(inventory_category_create))
|
||||||
|
.route("/category/:id/", get(inventory_active))
|
||||||
|
.route("/item/", post(inventory_item_create))
|
||||||
|
.route("/item/:id/", get(inventory_item))
|
||||||
|
.route("/item/:id/cancel", get(inventory_item_cancel))
|
||||||
|
.route("/item/:id/delete", get(inventory_item_delete))
|
||||||
|
.route("/item/:id/edit", post(inventory_item_edit))
|
||||||
|
.route("/item/name/validate", post(inventory_item_validate_name)),
|
||||||
)
|
)
|
||||||
.route(
|
.layer(middleware::from_fn_with_state(state.clone(), authorize)),
|
||||||
"/:id/packagelist/item/:id/ready",
|
|
||||||
post(trip_item_packagelist_set_ready_htmx),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/:id/packagelist/item/:id/unready",
|
|
||||||
post(trip_item_packagelist_set_unready_htmx),
|
|
||||||
)
|
|
||||||
.route("/:id/state/:id", post(trip_state_set))
|
|
||||||
.route("/:id/total_weight", get(trip_total_weight_htmx))
|
|
||||||
.route("/:id/type/:id/add", get(trip_type_add))
|
|
||||||
.route("/:id/type/:id/remove", get(trip_type_remove))
|
|
||||||
.route("/:id/edit/:attribute/submit", post(trip_edit_attribute))
|
|
||||||
.route(
|
|
||||||
"/:id/items/:id/pick",
|
|
||||||
get(trip_item_set_pick).post(trip_item_set_pick_htmx),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/:id/items/:id/unpick",
|
|
||||||
get(trip_item_set_unpick).post(trip_item_set_unpick_htmx),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/:id/items/:id/pack",
|
|
||||||
get(trip_item_set_pack).post(trip_item_set_pack_htmx),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/:id/items/:id/unpack",
|
|
||||||
get(trip_item_set_unpack).post(trip_item_set_unpack_htmx),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/:id/items/:id/ready",
|
|
||||||
get(trip_item_set_ready).post(trip_item_set_ready_htmx),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/:id/items/:id/unready",
|
|
||||||
get(trip_item_set_unready).post(trip_item_set_unready_htmx),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.nest(
|
|
||||||
(&TopLevelPage::Inventory.path()).into(),
|
|
||||||
Router::new()
|
|
||||||
.route("/", get(inventory_inactive))
|
|
||||||
.route("/categories/:id/select", post(inventory_category_select))
|
|
||||||
.route("/category/", post(inventory_category_create))
|
|
||||||
.route("/category/:id/", get(inventory_active))
|
|
||||||
.route("/item/", post(inventory_item_create))
|
|
||||||
.route("/item/:id/", get(inventory_item))
|
|
||||||
.route("/item/:id/cancel", get(inventory_item_cancel))
|
|
||||||
.route("/item/:id/delete", get(inventory_item_delete))
|
|
||||||
.route("/item/:id/edit", post(inventory_item_edit))
|
|
||||||
.route("/item/name/validate", post(inventory_item_validate_name)),
|
|
||||||
)
|
)
|
||||||
.fallback(|| async {
|
.fallback(|| async {
|
||||||
Error::Request(RequestError::NotFound {
|
Error::Request(RequestError::NotFound {
|
||||||
@@ -287,8 +364,12 @@ async fn main() -> Result<(), StartError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn root() -> impl IntoResponse {
|
async fn root(Extension(current_user): Extension<User>) -> impl IntoResponse {
|
||||||
view::Root::build(&view::home::Home::build(), None)
|
view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
|
&view::home::Home::build(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn debug(headers: HeaderMap) -> impl IntoResponse {
|
async fn debug(headers: HeaderMap) -> impl IntoResponse {
|
||||||
@@ -308,6 +389,7 @@ struct InventoryQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn inventory_active(
|
async fn inventory_active(
|
||||||
|
Extension(current_user): Extension<User>,
|
||||||
State(mut state): State<AppState>,
|
State(mut state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Query(inventory_query): Query<InventoryQuery>,
|
Query(inventory_query): Query<InventoryQuery>,
|
||||||
@@ -332,6 +414,7 @@ async fn inventory_active(
|
|||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
Ok(view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
&view::inventory::Inventory::build(
|
&view::inventory::Inventory::build(
|
||||||
active_category,
|
active_category,
|
||||||
&inventory.categories,
|
&inventory.categories,
|
||||||
@@ -342,6 +425,7 @@ async fn inventory_active(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn inventory_inactive(
|
async fn inventory_inactive(
|
||||||
|
Extension(current_user): Extension<User>,
|
||||||
State(mut state): State<AppState>,
|
State(mut state): State<AppState>,
|
||||||
Query(inventory_query): Query<InventoryQuery>,
|
Query(inventory_query): Query<InventoryQuery>,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
@@ -351,6 +435,7 @@ async fn inventory_inactive(
|
|||||||
let inventory = models::inventory::Inventory::load(&state.database_pool).await?;
|
let inventory = models::inventory::Inventory::load(&state.database_pool).await?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
Ok(view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
&view::inventory::Inventory::build(
|
&view::inventory::Inventory::build(
|
||||||
None,
|
None,
|
||||||
&inventory.categories,
|
&inventory.categories,
|
||||||
@@ -543,10 +628,14 @@ async fn trip_create(
|
|||||||
Ok(Redirect::to(&format!("/trips/{new_id}/")))
|
Ok(Redirect::to(&format!("/trips/{new_id}/")))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn trips(State(state): State<AppState>) -> Result<impl IntoResponse, Error> {
|
async fn trips(
|
||||||
|
Extension(current_user): Extension<User>,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
) -> Result<impl IntoResponse, Error> {
|
||||||
let trips = models::trips::Trip::all(&state.database_pool).await?;
|
let trips = models::trips::Trip::all(&state.database_pool).await?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
Ok(view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
&view::trip::TripManager::build(trips),
|
&view::trip::TripManager::build(trips),
|
||||||
Some(&TopLevelPage::Trips),
|
Some(&TopLevelPage::Trips),
|
||||||
))
|
))
|
||||||
@@ -559,6 +648,7 @@ struct TripQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn trip(
|
async fn trip(
|
||||||
|
Extension(current_user): Extension<User>,
|
||||||
State(mut state): State<AppState>,
|
State(mut state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Query(trip_query): Query<TripQuery>,
|
Query(trip_query): Query<TripQuery>,
|
||||||
@@ -593,6 +683,7 @@ async fn trip(
|
|||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
Ok(view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
&view::trip::Trip::build(
|
&view::trip::Trip::build(
|
||||||
&trip,
|
&trip,
|
||||||
state.client_state.trip_edit_attribute,
|
state.client_state.trip_edit_attribute,
|
||||||
@@ -1027,6 +1118,7 @@ struct TripTypeQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn trips_types(
|
async fn trips_types(
|
||||||
|
Extension(current_user): Extension<User>,
|
||||||
State(mut state): State<AppState>,
|
State(mut state): State<AppState>,
|
||||||
Query(trip_type_query): Query<TripTypeQuery>,
|
Query(trip_type_query): Query<TripTypeQuery>,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
@@ -1036,6 +1128,7 @@ async fn trips_types(
|
|||||||
models::trips::TripsType::all(&state.database_pool).await?;
|
models::trips::TripsType::all(&state.database_pool).await?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
Ok(view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
&view::trip::types::TypeList::build(&state.client_state, trip_types),
|
&view::trip::types::TypeList::build(&state.client_state, trip_types),
|
||||||
Some(&TopLevelPage::Trips),
|
Some(&TopLevelPage::Trips),
|
||||||
))
|
))
|
||||||
@@ -1096,6 +1189,7 @@ async fn trips_types_edit_name(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn inventory_item(
|
async fn inventory_item(
|
||||||
|
Extension(current_user): Extension<User>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
@@ -1106,6 +1200,7 @@ async fn inventory_item(
|
|||||||
}))?;
|
}))?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
Ok(view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
&view::inventory::InventoryItem::build(&state.client_state, &item),
|
&view::inventory::InventoryItem::build(&state.client_state, &item),
|
||||||
Some(&TopLevelPage::Inventory),
|
Some(&TopLevelPage::Inventory),
|
||||||
))
|
))
|
||||||
@@ -1178,6 +1273,7 @@ async fn inventory_category_select(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn trip_packagelist(
|
async fn trip_packagelist(
|
||||||
|
Extension(current_user): Extension<User>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(trip_id): Path<Uuid>,
|
Path(trip_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
@@ -1190,6 +1286,7 @@ async fn trip_packagelist(
|
|||||||
trip.load_categories(&state.database_pool).await?;
|
trip.load_categories(&state.database_pool).await?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
Ok(view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
&view::trip::packagelist::TripPackageList::build(&trip),
|
&view::trip::packagelist::TripPackageList::build(&trip),
|
||||||
Some(&TopLevelPage::Trips),
|
Some(&TopLevelPage::Trips),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
pub mod trips;
|
pub mod trips;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
pub use error::{DatabaseError, Error, QueryError};
|
pub use error::{DatabaseError, Error, QueryError};
|
||||||
|
|||||||
45
rust/src/models/user.rs
Normal file
45
rust/src/models/user.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use super::Error;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub username: String,
|
||||||
|
pub fullname: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DbUserRow {
|
||||||
|
id: String,
|
||||||
|
username: String,
|
||||||
|
fullname: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DbUserRow> for User {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(row: DbUserRow) -> Result<Self, Self::Error> {
|
||||||
|
Ok(User {
|
||||||
|
id: Uuid::try_parse(&row.id)?,
|
||||||
|
username: row.username,
|
||||||
|
fullname: row.fullname,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub async fn find_by_name(
|
||||||
|
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Option<Self>, Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
DbUserRow,
|
||||||
|
"SELECT id,username,fullname FROM users WHERE username = ?",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?
|
||||||
|
.map(|row: DbUserRow| row.try_into())
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use super::Context;
|
||||||
|
|
||||||
use maud::{html, Markup, PreEscaped, DOCTYPE};
|
use maud::{html, Markup, PreEscaped, DOCTYPE};
|
||||||
|
|
||||||
pub mod home;
|
pub mod home;
|
||||||
@@ -9,7 +11,7 @@ pub struct Root;
|
|||||||
use crate::TopLevelPage;
|
use crate::TopLevelPage;
|
||||||
|
|
||||||
impl Root {
|
impl Root {
|
||||||
pub fn build(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(|page| *page == item).unwrap_or(false);
|
||||||
html!(
|
html!(
|
||||||
@@ -88,6 +90,24 @@ impl Root {
|
|||||||
(menu_item(TopLevelPage::Inventory, active_page))
|
(menu_item(TopLevelPage::Inventory, active_page))
|
||||||
(menu_item(TopLevelPage::Trips, active_page))
|
(menu_item(TopLevelPage::Trips, active_page))
|
||||||
}
|
}
|
||||||
|
a
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."items-center"
|
||||||
|
."gap-3"
|
||||||
|
."px-5"
|
||||||
|
."bg-gray-200"
|
||||||
|
."hover:bg-gray-300"
|
||||||
|
href=(format!("/user/{}", context.user.username))
|
||||||
|
{
|
||||||
|
span
|
||||||
|
."m-auto"
|
||||||
|
."mdi"
|
||||||
|
."mdi-account"
|
||||||
|
."text-3xl"
|
||||||
|
{}
|
||||||
|
p { (context.user.username)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(body)
|
(body)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user