commiting old work
parent
087916a9bc
commit
f6bad75438
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
.env
|
||||
Session.vim
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod models;
|
||||
pub mod schema;
|
||||
pub mod report;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use diesel::prelude::*;
|
||||
|
||||
#[derive(Queryable, Selectable, Debug)]
|
||||
#[derive(Identifiable, Queryable, Selectable, Debug)]
|
||||
#[diesel(table_name = crate::schema::shifts)]
|
||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||
pub struct Shift {
|
||||
|
@ -9,7 +9,8 @@ pub struct Shift {
|
|||
pub end: Option<chrono::NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Selectable, Debug)]
|
||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Selectable, Debug)]
|
||||
#[diesel(belongs_to(Shift, foreign_key = shift))]
|
||||
#[diesel(table_name = crate::schema::drinks)]
|
||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||
pub struct Drink {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
use crate::models::Drink;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct DrinkReport {
|
||||
pub summary: HashMap<u32, u32>,
|
||||
}
|
||||
|
||||
impl DrinkReport {
|
||||
pub fn into_sorted(self) -> impl Iterator<Item = (u32, u32)> {
|
||||
let mut array: Vec<(u32, u32)> = self.summary.into_iter().collect();
|
||||
|
||||
array.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
array.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GenerateDrinkReport {
|
||||
fn generate_report(&self) -> DrinkReport;
|
||||
}
|
||||
|
||||
impl GenerateDrinkReport for Vec<Drink> {
|
||||
fn generate_report(&self) -> DrinkReport {
|
||||
let mut summary: HashMap<u32, u32> = HashMap::new();
|
||||
|
||||
// init with all default drink prices
|
||||
for price in [2u32, 3u32, 5u32, 8u32, 15u32].into_iter() {
|
||||
summary.insert(price, 0);
|
||||
}
|
||||
|
||||
for drink in self {
|
||||
summary
|
||||
.entry(drink.price)
|
||||
.and_modify(|v| *v += drink.quantity)
|
||||
.or_insert(drink.quantity);
|
||||
}
|
||||
|
||||
DrinkReport { summary }
|
||||
}
|
||||
}
|
52
src/main.rs
52
src/main.rs
|
@ -1,22 +1,37 @@
|
|||
mod api;
|
||||
mod axum_ructe;
|
||||
|
||||
use axum::extract::Path;
|
||||
use axum_ructe::render;
|
||||
|
||||
use axum::{extract::State, response::IntoResponse, routing::get, Router};
|
||||
use cm_lib::models::Shift;
|
||||
use diesel::result::OptionalExtension;
|
||||
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
|
||||
use cm_lib::{
|
||||
models::{Drink, Shift},
|
||||
report::GenerateDrinkReport,
|
||||
schema::shifts,
|
||||
};
|
||||
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
|
||||
use diesel::{
|
||||
result::OptionalExtension, BelongingToDsl, ExpressionMethods, QueryDsl, SelectableHelper,
|
||||
};
|
||||
|
||||
use diesel_async::{
|
||||
pooled_connection::{deadpool::Pool, AsyncDieselConnectionManager},
|
||||
AsyncMysqlConnection, RunQueryDsl,
|
||||
};
|
||||
|
||||
use dotenvy::dotenv;
|
||||
use std::net::SocketAddr;
|
||||
use tower_http::services::{ServeDir, ServeFile};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
use crate::templates::shift_reports_html;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
||||
|
||||
async fn establish_connection() -> Pool<AsyncMysqlConnection> {
|
||||
|
@ -63,7 +78,9 @@ async fn main() {
|
|||
let app = Router::new()
|
||||
.nest("/api", api::router())
|
||||
.route("/", get(root))
|
||||
.route("/shift_reports", get(shift_reports))
|
||||
.route("/shifts/:id/drinks", get(drinks))
|
||||
.route("/shifts/:id/report", get(shift_report))
|
||||
.fallback_service(fallback_handler)
|
||||
.with_state(state);
|
||||
|
||||
|
@ -100,3 +117,28 @@ async fn root(State(state): State<AppState>) -> impl IntoResponse {
|
|||
async fn drinks(Path(id): Path<u32>) -> impl IntoResponse {
|
||||
render!(templates::drinks_html, id)
|
||||
}
|
||||
|
||||
async fn shift_report(State(state): State<AppState>, Path(id): Path<u32>) -> impl IntoResponse {
|
||||
let mut conn = state.connection.get().await.unwrap();
|
||||
let shift: Shift = shifts::table.find(id).first(&mut conn).await.unwrap();
|
||||
let drinks: Vec<Drink> = Drink::belonging_to(&shift).load(&mut conn).await.unwrap();
|
||||
|
||||
render!(
|
||||
crate::templates::shift_report_html,
|
||||
drinks.generate_report()
|
||||
)
|
||||
}
|
||||
|
||||
async fn shift_reports(State(state): State<AppState>) -> impl IntoResponse {
|
||||
let mut conn = state.connection.get().await.unwrap();
|
||||
|
||||
let mut shifts: Vec<Shift> = shifts::table
|
||||
.select(Shift::as_select())
|
||||
.load(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
shifts.sort_by(|a, b| b.start.cmp(&a.start));
|
||||
|
||||
render!(shift_reports_html, shifts)
|
||||
}
|
||||
|
|
|
@ -11,12 +11,17 @@
|
|||
@:shift_button_html(open_shift.as_ref())
|
||||
</div>
|
||||
@if open_shift.is_some() {
|
||||
<div hx-boost="true">
|
||||
<a href="/shifts/@open_shift.unwrap().id/drinks">
|
||||
<button>Drinks</button>
|
||||
</a>
|
||||
</div>
|
||||
<div hx-boost="true">
|
||||
<a href="/shifts/@open_shift.unwrap().id/drinks">
|
||||
<button>Drinks</button>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
<div hx-boost="true">
|
||||
<a href="/shift_reports">
|
||||
<button>Shift Reports</button>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
})
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
@use super::base_html;
|
||||
@use cm_lib::report::DrinkReport;
|
||||
|
||||
@(drinks: DrinkReport)
|
||||
|
||||
|
||||
@:base_html({
|
||||
|
||||
<div style="display: flex; gap: 2rem;">
|
||||
<div hx-boost="true">
|
||||
<a href="/">
|
||||
<button>Return to Main Page</button>
|
||||
</a>
|
||||
</div>
|
||||
<div hx-boost="true">
|
||||
<a href="/shift_reports">
|
||||
<button>Return to Shift Reports</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (price, quantity) in drinks.into_sorted() {
|
||||
|
||||
<tr>
|
||||
<td>@price</td>
|
||||
<td>@quantity</td>
|
||||
</tr>
|
||||
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
})
|
|
@ -0,0 +1,47 @@
|
|||
@use super::base_html;
|
||||
@use cm_lib::models::Shift;
|
||||
|
||||
@(shifts: Vec<Shift>)
|
||||
|
||||
@:base_html({
|
||||
|
||||
|
||||
<div hx-boost="true">
|
||||
<a href="/">
|
||||
<button>Return to Main Page</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shift ID</th>
|
||||
<th>Shift Start</th>
|
||||
<th>Shift End</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for shift in shifts {
|
||||
|
||||
<tr>
|
||||
<td>@shift.id</td>
|
||||
<td>@shift.start.format("%Y-%m-%d %H:%M:%S")</td>
|
||||
<td>
|
||||
@if let Some(end) = shift.end {
|
||||
@end.format("%Y-%m-%d %H:%M:%S")
|
||||
} else {
|
||||
Currently Open
|
||||
}
|
||||
</td>
|
||||
<td hx-boost="true">
|
||||
<a href="/shifts/@shift.id/report">
|
||||
<button>View Shift</button>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
})
|
Loading…
Reference in New Issue