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 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};
use crate::app::SearchQueryArgs;
#[derive(Template)]
#[template(path = "catalog.html")]
struct CatalogTemplate {
items: Vec<DbInventoryItem>,
query: CatalogQueryArgs,
query: SearchQueryArgs,
}
#[derive(Template)]
@ -20,20 +20,10 @@ 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>,
QueryExtractor(query): QueryExtractor<SearchQueryArgs>,
HxRequest(hx_request): HxRequest,
State(db): State<SqlitePool>
) -> 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 oauth2::basic::BasicClient;
use axum::extract::FromRef;
use serde::Deserialize;
use crate::session::SessionUser;
pub mod item;
mod upload;
pub mod catalog;
mod home;
mod overview;
mod reports;
mod audit;
pub fn routes() -> Router<AppState> {
Router::new()
.route("/", get(catalog::catalog))
.route("/index.html", get(catalog::catalog))
.route("/items", get(catalog::catalog))
.route("/items/", get(catalog::catalog))
.route("/", get(home::home))
.route("/index.html", get(home::home))
.route("/home", get(home::home))
.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/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
// whether explicitly asked or not
.route_layer(from_extractor::<SessionUser>())
@ -49,4 +60,15 @@ impl FromRef<AppState> for BasicClient {
fn from_ref(input: &AppState) -> Self {
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::response::IntoResponse;
use sqlx::SqlitePool;
use std::format;
use askama_axum::{IntoResponse, Response};
use anyhow::anyhow;
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>,
user: SessionUser,
mut multipart: Multipart,
@ -27,4 +25,4 @@ pub async fn catalog(
ingest_catalog_bytes(data, db.clone(), user.id).await?;
}
Ok(format!("File {} uploaded successfully", filename).into_response())
}
}

@ -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>
<input id="search" type="search" name="q"
placeholder="Search"
placeholder="Filter"
aria-label="Search"
value='{{ query.search.as_deref().unwrap_or("") }}'
hx-get="/items"
@ -20,13 +20,5 @@
{% include "catalog_item_fragment.html" %}
</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 %}

@ -3,8 +3,6 @@
<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 %}

@ -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">
<nav class="container">
<ul>
<li><h1>Inventory App</h1></li>
<li><h1><a class="secondary" href="/home">Inventory App</a></h1></li>
</ul>
<ul>
<li><a class="secondary" href="#">Overview</a></li>
<li><a class="secondary" href="/items/">Items</a></li>
<li><a class="secondary" href="#">Receiving</a></li>
<li><a class="secondary" href="#">Reports</a></li>
<li><a class="secondary" href="#">Adjustments</a></li>
<li><a class="secondary" href="/overview">Overview</a></li>
<li><a class="secondary" href="/catalog">Catalog</a></li>
<li><a class="secondary" href="/upload">Upload</a></li>
<li><a class="secondary" href="/reports">Reports</a></li>
<li><a class="secondary" href="/audit">Audit</a></li>
<li><a class="contrast" href="/auth/logout">Logout</a></li>
</ul>
</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.