demo-mode
Wes Holland 1 year ago
parent cf65324827
commit 20309b8d9b

@ -0,0 +1,54 @@
use askama::Template;
use serde::Deserialize;
use axum_htmx::HxRequest;
use axum::extract::State;
use sqlx::SqlitePool;
use askama_axum::{IntoResponse, Response};
use crate::db::inventory_item::{inventory_item_get_all, inventory_item_get_search, DbInventoryItem};
use crate::error::{AppError, QueryExtractor};
#[derive(Template)]
#[template(path = "catalog.html")]
struct CatalogTemplate {
items: Vec<DbInventoryItem>,
query: CatalogQueryArgs,
}
#[derive(Template)]
#[template(path = "catalog_item_fragment.html")]
struct CatalogItemFragmentTemplate {
items: Vec<DbInventoryItem>
}
/// Query string response for "authorized" endpoint
#[derive(Debug, Deserialize)]
pub struct CatalogQueryArgs {
#[serde(rename = "q")]
pub search: Option<String>,
#[serde(alias = "p")]
pub page: Option<i64>,
#[serde(rename = "size")]
pub page_size: Option<i64>,
}
#[axum::debug_handler]
pub async fn catalog(
QueryExtractor(query): QueryExtractor<CatalogQueryArgs>,
HxRequest(hx_request): HxRequest,
State(db): State<SqlitePool>
) -> Result<Response, AppError> {
let page = query.page.unwrap_or(0);
let page_size = query.page_size.unwrap_or(100);
let items = match query.search.as_ref() {
Some(s) => inventory_item_get_search(&db, &s, page_size, page).await?,
None => inventory_item_get_all(&db, page_size, page).await?,
};
if hx_request {
Ok(CatalogItemFragmentTemplate { items }.into_response())
}
else {
Ok(CatalogTemplate { items, query }.into_response())
}
}

@ -0,0 +1,26 @@
use askama::Template;
use askama_axum::IntoResponse;
use axum::extract::{Path, State};
use sqlx::SqlitePool;
use axum::response::Response;
use crate::db::inventory_item::{inventory_item_get_by_id, sum_all_adjustments_for_item, DbInventoryItem};
use crate::error::AppError;
#[derive(Template)]
#[template(path = "item.html")]
struct ItemTemplate {
item: DbInventoryItem
}
pub async fn item(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> {
let item = inventory_item_get_by_id(&db, id).await?;
Ok(ItemTemplate { item }.into_response())
}
pub async fn item_count(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> {
let count = sum_all_adjustments_for_item(&db, id).await?;
Ok(count.to_string().into_response())
}

@ -1,74 +0,0 @@
use askama::Template;
use askama_axum::IntoResponse;
use axum::extract::{Path, State};
use sqlx::SqlitePool;
use axum::response::Response;
use axum_htmx::HxRequest;
use serde::Deserialize;
use crate::db::inventory_item::{inventory_item_get_all, inventory_item_get_by_id, inventory_item_get_search, sum_all_adjustments_for_item, DbInventoryItem};
use crate::error::{AppError, QueryExtractor};
#[derive(Template)]
#[template(path = "item_list.html")]
struct ItemListTemplate {
items: Vec<DbInventoryItem>,
query: ItemsQueryArgs,
}
#[derive(Template)]
#[template(path = "item_list_fragment.html")]
struct ItemListFragmentTemplate {
items: Vec<DbInventoryItem>
}
/// Query string response for "authorized" endpoint
#[derive(Debug, Deserialize)]
pub struct ItemsQueryArgs {
#[serde(rename = "q")]
pub search: Option<String>,
#[serde(alias = "p")]
pub page: Option<i64>,
#[serde(rename = "size")]
pub page_size: Option<i64>,
}
#[axum::debug_handler]
pub async fn item_list(
QueryExtractor(query): QueryExtractor<ItemsQueryArgs>,
HxRequest(hx_request): HxRequest,
State(db): State<SqlitePool>
) -> Result<Response, AppError> {
let page = query.page.unwrap_or(0);
let page_size = query.page_size.unwrap_or(100);
let items = match query.search.as_ref() {
Some(s) => inventory_item_get_search(&db, &s, page_size, page).await?,
None => inventory_item_get_all(&db, page_size, page).await?,
};
if hx_request {
Ok(ItemListFragmentTemplate { items }.into_response())
}
else {
Ok(ItemListTemplate { items, query }.into_response())
}
}
#[derive(Template)]
#[template(path = "item.html")]
struct ItemTemplate {
item: DbInventoryItem
}
pub async fn item(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> {
let item = inventory_item_get_by_id(&db, id).await?;
Ok(ItemTemplate { item }.into_response())
}
pub async fn item_count(State(db): State<SqlitePool>, Path(id): Path<i64>) -> Result<Response, AppError> {
let count = sum_all_adjustments_for_item(&db, id).await?;
Ok(count.to_string().into_response())
}

@ -1,23 +1,25 @@
use axum::middleware::from_extractor;
use axum::Router;
use axum::routing::{get, post};
use axum_htmx::{AutoVaryLayer};
use crate::app::state::AppState;
use axum_htmx::AutoVaryLayer;
use sqlx::SqlitePool;
use oauth2::basic::BasicClient;
use axum::extract::FromRef;
use crate::session::SessionUser;
pub mod state;
pub mod items;
pub mod item;
mod upload;
pub mod catalog;
pub fn routes() -> Router<AppState> {
Router::new()
.route("/", get(items::item_list))
.route("/index.html", get(items::item_list))
.route("/items", get(items::item_list))
.route("/items/", get(items::item_list))
.route("/item/:item_id", get(items::item))
.route("/item/:item_id/", get(items::item))
.route("/item/:item_id/count", get(items::item_count))
.route("/", get(catalog::catalog))
.route("/index.html", get(catalog::catalog))
.route("/items", get(catalog::catalog))
.route("/items/", get(catalog::catalog))
.route("/item/:item_id", get(item::item))
.route("/item/:item_id/", get(item::item))
.route("/item/:item_id/count", get(item::item_count))
.route("/catalog", post(upload::catalog))
// Ensure that all routes here require an authenticated user
// whether explicitly asked or not
@ -25,3 +27,26 @@ pub fn routes() -> Router<AppState> {
.layer(AutoVaryLayer)
}
// App state. Pretty basic stuff. Gets passed around by the server to the handlers and whatnot
// Use in a handler with the state enum:
// async fn handler(State(my_app_state): State<AppState>)
#[derive(Clone)]
pub struct AppState {
pub db: SqlitePool,
pub oauth_client: BasicClient,
}
// Axum extractors for app state. These allow the handler to just use
// pieces of the App state
// async fn handler(State(my_db): State<SqlitePool>)
impl FromRef<AppState> for SqlitePool {
fn from_ref(input: &AppState) -> Self {
input.db.clone()
}
}
impl FromRef<AppState> for BasicClient {
fn from_ref(input: &AppState) -> Self {
input.oauth_client.clone()
}
}

@ -1,27 +0,0 @@
use sqlx::SqlitePool;
use oauth2::basic::BasicClient;
use axum::extract::FromRef;
// App state. Pretty basic stuff. Gets passed around by the server to the handlers and whatnot
// Use in a handler with the state enum:
// async fn handler(State(my_app_state): State<AppState>)
#[derive(Clone)]
pub struct AppState {
pub db: SqlitePool,
pub oauth_client: BasicClient,
}
// Axum extractors for app state. These allow the handler to just use
// pieces of the App state
// async fn handler(State(my_db): State<SqlitePool>)
impl FromRef<AppState> for SqlitePool {
fn from_ref(input: &AppState) -> Self {
input.db.clone()
}
}
impl FromRef<AppState> for BasicClient {
fn from_ref(input: &AppState) -> Self {
input.oauth_client.clone()
}
}

@ -13,7 +13,7 @@ use tower_sessions::Session;
use crate::error::{AppError, AppForbiddenResponse};
use crate::error::QueryExtractor;
use crate::app::state::AppState;
use crate::app::AppState;
use crate::session::{SessionUser, USER_SESSION};
use crate::db;
// This module is all the stuff related to authentication and authorization

@ -5,7 +5,7 @@ use axum::response::{IntoResponse, Response};
use axum::Router;
use axum::routing::get;
use axum::extract::FromRequestParts;
use crate::app::state::AppState;
use crate::app::AppState;
use crate::session::SessionUser;
// This module is all the stuff related to handling error responses

@ -1,4 +1,4 @@
use app::state::AppState;
use app::AppState;
use anyhow::{Context, Result};
use axum::Router;
use tokio::signal;

@ -1,6 +1,6 @@
use axum::Router;
use tower_http::services::ServeFile;
use crate::app::state::AppState;
use crate::app::AppState;
pub fn routes() -> Router<AppState> {
Router::new()

@ -10,13 +10,14 @@
aria-label="Search"
value='{{ query.search.as_deref().unwrap_or("") }}'
hx-get="/items"
hx-trigger="search, keyup delay:200ms changed"
hx-trigger="search, keyup delay:500ms changed"
hx-target="#items"
hx-push-url="true"
/>
</p>
<div id="items" class="container">
{% include "item_list_fragment.html" %}
{% include "catalog_item_fragment.html" %}
</div>
<form action="/catalog" method="post" enctype="multipart/form-data">

@ -2,7 +2,7 @@
{% for item in items %}
<article id="item-{{item.id}}-card">
<div class="grid">
<div><a href="/item/{{item.id}}/">{{ item.name }}</a></div>
<div><a href="/item/{{item.id}}/" hx-push-url="true">{{ item.name }}</a></div>
<div>Count: <span id="item-{{item.id}}-count" hx-get="/item/{{item.id}}/count" hx-trigger="load">0</span></div>
<div>Reorder Point: {{ item.reorder_point }}</div>
</div>

@ -8,7 +8,7 @@
<script src="/js/htmx.min.js"></script>
<title>{% block title %}Title{% endblock %}</title>
</head>
<body hx-boost="true">
<body>
<header class="container">
<nav class="container">
<ul>

Loading…
Cancel
Save

Powered by TurnKey Linux.