Scaffold out main endpoints

demo-mode
Wes Holland 1 year ago
parent 68ec57eac0
commit 12d8bc0e21

@ -0,0 +1,12 @@
use askama::Template;
use askama_axum::{IntoResponse, Response};
use crate::error::{AppError};
#[derive(Template)]
#[template(path = "audit.html")]
struct AuditLogTemplate;
pub async fn audit_log_handler() -> Result<Response, AppError> {
Ok(AuditLogTemplate.into_response())
}

@ -1,17 +1,17 @@
use askama::Template; use askama::Template;
use serde::Deserialize;
use axum_htmx::HxRequest; use axum_htmx::HxRequest;
use axum::extract::State; use axum::extract::State;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use askama_axum::{IntoResponse, Response}; use askama_axum::{IntoResponse, Response};
use crate::db::inventory_item::{inventory_item_get_all, inventory_item_get_search, DbInventoryItem}; use crate::db::inventory_item::{inventory_item_get_all, inventory_item_get_search, DbInventoryItem};
use crate::error::{AppError, QueryExtractor}; use crate::error::{AppError, QueryExtractor};
use crate::app::SearchQueryArgs;
#[derive(Template)] #[derive(Template)]
#[template(path = "catalog.html")] #[template(path = "catalog.html")]
struct CatalogTemplate { struct CatalogTemplate {
items: Vec<DbInventoryItem>, items: Vec<DbInventoryItem>,
query: CatalogQueryArgs, query: SearchQueryArgs,
} }
#[derive(Template)] #[derive(Template)]
@ -20,20 +20,10 @@ struct CatalogItemFragmentTemplate {
items: Vec<DbInventoryItem> 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] #[axum::debug_handler]
pub async fn catalog( pub async fn catalog(
QueryExtractor(query): QueryExtractor<CatalogQueryArgs>, QueryExtractor(query): QueryExtractor<SearchQueryArgs>,
HxRequest(hx_request): HxRequest, HxRequest(hx_request): HxRequest,
State(db): State<SqlitePool> State(db): State<SqlitePool>
) -> Result<Response, AppError> { ) -> Result<Response, AppError> {

@ -0,0 +1,44 @@
use askama::Template;
use axum::extract::State;
use sqlx::SqlitePool;
use askama_axum::{IntoResponse, Response};
use axum_htmx::HxRequest;
use crate::db::inventory_item::{inventory_item_get_search, DbInventoryItem};
use crate::error::{AppError, QueryExtractor};
use crate::app::SearchQueryArgs;
#[derive(Template)]
#[template(path = "home.html")]
struct HomeTemplate {
items: Vec<DbInventoryItem>,
query: SearchQueryArgs,
}
#[derive(Template)]
#[template(path = "home_search_item_fragment.html")]
struct HomeSearchItemFragmentTemplate {
items: Vec<DbInventoryItem>,
}
pub async fn home(
QueryExtractor(query): QueryExtractor<SearchQueryArgs>,
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() {
None => Vec::new(),
Some(s) => inventory_item_get_search(&db, s.as_str(), page_size, page).await?,
};
if hx_request {
Ok(HomeSearchItemFragmentTemplate { items }.into_response())
}
else {
Ok(HomeTemplate { items, query }.into_response())
}
}

@ -5,22 +5,33 @@ use axum_htmx::AutoVaryLayer;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use oauth2::basic::BasicClient; use oauth2::basic::BasicClient;
use axum::extract::FromRef; use axum::extract::FromRef;
use serde::Deserialize;
use crate::session::SessionUser; use crate::session::SessionUser;
pub mod item; pub mod item;
mod upload; mod upload;
pub mod catalog; pub mod catalog;
mod home;
mod overview;
mod reports;
mod audit;
pub fn routes() -> Router<AppState> { pub fn routes() -> Router<AppState> {
Router::new() Router::new()
.route("/", get(catalog::catalog)) .route("/", get(home::home))
.route("/index.html", get(catalog::catalog)) .route("/index.html", get(home::home))
.route("/items", get(catalog::catalog)) .route("/home", get(home::home))
.route("/items/", get(catalog::catalog)) .route("/home/search", get(home::home))
.route("/catalog", get(catalog::catalog))
.route("/catalog/", get(catalog::catalog))
.route("/item/:item_id", get(item::item)) .route("/item/:item_id", get(item::item))
.route("/item/:item_id/", get(item::item)) .route("/item/:item_id/", get(item::item))
.route("/item/:item_id/count", get(item::item_count)) .route("/item/:item_id/count", get(item::item_count))
.route("/catalog", post(upload::catalog)) .route("/upload", get(upload::index::upload_index_handler))
.route("/upload/catalog", post(upload::catalog::catalog_import))
.route("/overview", get(overview::overview_handler))
.route("/reports", get(reports::reports_handler))
.route("/audit", get(audit::audit_log_handler))
// Ensure that all routes here require an authenticated user // Ensure that all routes here require an authenticated user
// whether explicitly asked or not // whether explicitly asked or not
.route_layer(from_extractor::<SessionUser>()) .route_layer(from_extractor::<SessionUser>())
@ -50,3 +61,14 @@ impl FromRef<AppState> for BasicClient {
input.oauth_client.clone() input.oauth_client.clone()
} }
} }
/// Common query args for text search
#[derive(Debug, Deserialize)]
pub struct SearchQueryArgs {
#[serde(rename = "q")]
pub search: Option<String>,
#[serde(alias = "p")]
pub page: Option<i64>,
#[serde(rename = "size")]
pub page_size: Option<i64>,
}

@ -0,0 +1,12 @@
use askama::Template;
use askama_axum::{IntoResponse, Response};
use crate::error::{AppError};
#[derive(Template)]
#[template(path = "overview.html")]
struct OverviewTemplate;
pub async fn overview_handler() -> Result<Response, AppError> {
Ok(OverviewTemplate.into_response())
}

@ -0,0 +1,12 @@
use askama::Template;
use askama_axum::{IntoResponse, Response};
use crate::error::{AppError};
#[derive(Template)]
#[template(path = "reports.html")]
struct ReportsTemplate;
pub async fn reports_handler() -> Result<Response, AppError> {
Ok(ReportsTemplate.into_response())
}

@ -1,15 +1,13 @@
use crate::error::{AppError};
use crate::ingest::{ingest_catalog_bytes};
use crate::session::SessionUser;
use anyhow::anyhow;
use askama_axum::Response;
use axum::extract::{Multipart, State}; use axum::extract::{Multipart, State};
use axum::response::IntoResponse;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::format; use askama_axum::{IntoResponse, Response};
use anyhow::anyhow;
use tracing::info; use tracing::info;
use crate::error::AppError;
use crate::ingest::ingest_catalog_bytes;
use crate::session::SessionUser;
pub async fn catalog( pub async fn catalog_import(
State(db): State<SqlitePool>, State(db): State<SqlitePool>,
user: SessionUser, user: SessionUser,
mut multipart: Multipart, mut multipart: Multipart,

@ -0,0 +1,11 @@
use askama::Template;
use askama_axum::{IntoResponse, Response};
use crate::error::{AppError};
#[derive(Template)]
#[template(path = "upload.html")]
struct UploadIndexTemplate;
pub async fn upload_index_handler() -> Result<Response, AppError> {
Ok(UploadIndexTemplate.into_response())
}

@ -0,0 +1,2 @@
pub mod catalog;
pub mod index;

@ -0,0 +1,9 @@
{% extends "main.html" %}
{% block title %} Audit Log {% endblock %}
{% block content %}
<h1>Audit Log (Coming soon)</h1>
{% endblock %}

@ -6,7 +6,7 @@
<p> <p>
<input id="search" type="search" name="q" <input id="search" type="search" name="q"
placeholder="Search" placeholder="Filter"
aria-label="Search" aria-label="Search"
value='{{ query.search.as_deref().unwrap_or("") }}' value='{{ query.search.as_deref().unwrap_or("") }}'
hx-get="/items" hx-get="/items"
@ -20,13 +20,5 @@
{% include "catalog_item_fragment.html" %} {% include "catalog_item_fragment.html" %}
</div> </div>
<form action="/catalog" method="post" enctype="multipart/form-data">
<label>
Upload file:
<input type="file" name="file" multiple>
</label>
<input type="submit" value="Upload files">
</form>
{% endblock %} {% endblock %}

@ -3,8 +3,6 @@
<article id="item-{{item.id}}-card"> <article id="item-{{item.id}}-card">
<div class="grid"> <div class="grid">
<div><a href="/item/{{item.id}}/" hx-push-url="true">{{ 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> </div>
</article> </article>
{% endfor %} {% endfor %}

@ -0,0 +1,23 @@
{% extends "main.html" %}
{% block title %} Home {% endblock %}
{% block content %}
<p>
<input id="search" type="search" name="q"
placeholder="Search"
aria-label="Search"
value='{{ query.search.as_deref().unwrap_or("") }}'
hx-get="/home/search"
hx-trigger="search, keyup delay:500ms changed"
hx-target="#items"
hx-push-url="true"
/>
</p>
<div id="items" class="container">
{% include "home_search_item_fragment.html" %}
</div>
{% endblock %}

@ -0,0 +1,9 @@
{% for item in items %}
<article id="item-{{item.id}}-card">
<div class="grid">
<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>
</article>
{% endfor %}

@ -12,14 +12,14 @@
<header class="container"> <header class="container">
<nav class="container"> <nav class="container">
<ul> <ul>
<li><h1>Inventory App</h1></li> <li><h1><a class="secondary" href="/home">Inventory App</a></h1></li>
</ul> </ul>
<ul> <ul>
<li><a class="secondary" href="#">Overview</a></li> <li><a class="secondary" href="/overview">Overview</a></li>
<li><a class="secondary" href="/items/">Items</a></li> <li><a class="secondary" href="/catalog">Catalog</a></li>
<li><a class="secondary" href="#">Receiving</a></li> <li><a class="secondary" href="/upload">Upload</a></li>
<li><a class="secondary" href="#">Reports</a></li> <li><a class="secondary" href="/reports">Reports</a></li>
<li><a class="secondary" href="#">Adjustments</a></li> <li><a class="secondary" href="/audit">Audit</a></li>
<li><a class="contrast" href="/auth/logout">Logout</a></li> <li><a class="contrast" href="/auth/logout">Logout</a></li>
</ul> </ul>
</nav> </nav>

@ -0,0 +1,9 @@
{% extends "main.html" %}
{% block title %} Overview {% endblock %}
{% block content %}
<h1>Overview (Coming soon)</h1>
{% endblock %}

@ -0,0 +1,9 @@
{% extends "main.html" %}
{% block title %} Reports {% endblock %}
{% block content %}
<h1>Reports (Coming soon)</h1>
{% endblock %}

@ -0,0 +1,15 @@
{% extends "main.html" %}
{% block title %} Upload {% endblock %}
{% block content %}
<form action="/upload/catalog" method="post" enctype="multipart/form-data">
<h3>Catalog Import</h3>
<fieldset role="group">
<input type="file" name="file" />
<input type="submit" value="Upload">
</fieldset>
</form>
{% endblock %}
Loading…
Cancel
Save

Powered by TurnKey Linux.