feat: drinks

This commit is contained in:
Zynh0722 2023-09-30 14:51:11 -07:00
parent 1c35f2f81f
commit 20b214e262
13 changed files with 157 additions and 7 deletions

13
Cargo.lock generated
View file

@ -125,6 +125,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core",
"axum-macros",
"bitflags 1.3.2", "bitflags 1.3.2",
"bytes", "bytes",
"futures-util", "futures-util",
@ -166,6 +167,18 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "axum-macros"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.37",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.69"

View file

@ -10,7 +10,7 @@ path = "src/lib/mod.rs"
[dependencies] [dependencies]
axum = "0.6.20" axum = { version = "0.6.20", features = ["macros"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68" serde_json = "1.0.68"
tokio = { version = "1.32.0", features = ["full"] } tokio = { version = "1.32.0", features = ["full"] }

View file

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE drinks

View file

@ -0,0 +1,10 @@
-- Your SQL goes here
CREATE TABLE drinks
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
price INT UNSIGNED NOT NULL,
quantity INT UNSIGNED NOT NULL,
shift INT UNSIGNED NOT NULL,
time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (shift) REFERENCES shifts(id) ON DELETE CASCADE
)

View file

@ -1,15 +1,18 @@
use axum::extract::Path; use axum::extract::Path;
use axum::Form;
use axum::{extract::State, response::IntoResponse, routing::post}; use axum::{extract::State, response::IntoResponse, routing::post};
use cm_lib::models::Shift; use cm_lib::models::{NewDrink, Shift};
use cm_lib::schema::shifts; use cm_lib::schema::shifts;
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper}; use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper};
use diesel_async::{scoped_futures::ScopedFutureExt, AsyncConnection, RunQueryDsl}; use diesel_async::{scoped_futures::ScopedFutureExt, AsyncConnection, RunQueryDsl};
use serde::Deserialize;
use crate::axum_ructe::render; use crate::axum_ructe::render;
use crate::AppState; use crate::AppState;
pub(crate) fn router() -> axum::Router<AppState> { pub(crate) fn router() -> axum::Router<AppState> {
axum::Router::new() axum::Router::new()
.route("/drinks", post(add_drink))
.route("/shifts/open", post(open_shift)) .route("/shifts/open", post(open_shift))
.route("/shifts/:id/close", post(close_shift)) .route("/shifts/:id/close", post(close_shift))
} }
@ -52,3 +55,42 @@ async fn close_shift(State(state): State<AppState>, Path(id): Path<u32>) -> impl
render!(crate::templates::home_html, None) render!(crate::templates::home_html, None)
} }
#[derive(Deserialize, Debug)]
struct DrinkForm {
shift_id: u32,
price: u32,
quantity: u32,
}
impl Into<NewDrink> for DrinkForm {
fn into(self) -> NewDrink {
NewDrink {
price: self.price,
quantity: self.quantity,
shift: self.shift_id,
}
}
}
async fn add_drink(
State(state): State<AppState>,
Form(form): Form<DrinkForm>,
) -> impl IntoResponse {
let mut conn = state.connection.get().await.unwrap();
tracing::debug!("{form:?}");
async {
use cm_lib::schema::drinks::dsl::*;
diesel::insert_into(drinks)
.values::<NewDrink>(form.into())
.execute(&mut conn)
.await
.unwrap()
}
.await;
axum::response::Redirect::to("/")
}

View file

@ -8,3 +8,23 @@ pub struct Shift {
pub start: chrono::NaiveDateTime, pub start: chrono::NaiveDateTime,
pub end: Option<chrono::NaiveDateTime>, pub end: Option<chrono::NaiveDateTime>,
} }
#[derive(Queryable, Selectable, Debug)]
#[diesel(table_name = crate::schema::drinks)]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct Drink {
pub id: u32,
pub price: u32,
pub quantity: u32,
pub shift: u32,
pub time: chrono::NaiveDateTime,
}
#[derive(Insertable, Debug)]
#[diesel(table_name = crate::schema::drinks)]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct NewDrink {
pub price: u32,
pub quantity: u32,
pub shift: u32,
}

View file

@ -1,5 +1,15 @@
// @generated automatically by Diesel CLI. // @generated automatically by Diesel CLI.
diesel::table! {
drinks (id) {
id -> Unsigned<Integer>,
price -> Unsigned<Integer>,
quantity -> Unsigned<Integer>,
shift -> Unsigned<Integer>,
time -> Datetime,
}
}
diesel::table! { diesel::table! {
shifts (id) { shifts (id) {
id -> Unsigned<Integer>, id -> Unsigned<Integer>,
@ -7,3 +17,10 @@ diesel::table! {
end -> Nullable<Datetime>, end -> Nullable<Datetime>,
} }
} }
diesel::joinable!(drinks -> shifts (shift));
diesel::allow_tables_to_appear_in_same_query!(
drinks,
shifts,
);

View file

@ -1,6 +1,7 @@
mod api; mod api;
mod axum_ructe; mod axum_ructe;
use axum::extract::Path;
use axum_ructe::render; use axum_ructe::render;
use axum::{extract::State, response::IntoResponse, routing::get, Router}; use axum::{extract::State, response::IntoResponse, routing::get, Router};
@ -59,6 +60,7 @@ async fn main() {
let app = Router::new() let app = Router::new()
.nest("/api", api::router()) .nest("/api", api::router())
.route("/", get(root)) .route("/", get(root))
.route("/shifts/:id/drinks", get(drinks))
.with_state(state); .with_state(state);
// run our app with hyper // run our app with hyper
@ -90,3 +92,7 @@ async fn root(State(state): State<AppState>) -> impl IntoResponse {
render!(templates::home_html, open_shift) render!(templates::home_html, open_shift)
} }
async fn drinks(Path(id): Path<u32>) -> impl IntoResponse {
render!(templates::drinks_html, id)
}

View file

@ -1,7 +1,7 @@
@use cm_lib::models::Shift; @use cm_lib::models::Shift;
@(open_shift: Shift) @(open_shift: &Shift)
<button hx-post="/api/shifts/@open_shift.id/close" hx-swap="outerHTML" type="button"> <button hx-post="/api/shifts/@open_shift.id/close" hx-swap="outerHTML" hx-target="body" type="button">
Close Shift Close Shift
</button> </button>

View file

@ -1,5 +1,5 @@
@() @()
<button hx-post="/api/shifts/open" hx-swap="outerHTML" type="button"> <button hx-post="/api/shifts/open" hx-swap="outerHTML" hx-target="body" type="button">
Open Shift Open Shift
</button> </button>

View file

@ -2,7 +2,7 @@
@use super::open_shift_button_html; @use super::open_shift_button_html;
@use cm_lib::models::Shift; @use cm_lib::models::Shift;
@(open_shift: Option<Shift>) @(open_shift: Option<&Shift>)
@if open_shift.is_none() { @if open_shift.is_none() {
@:open_shift_button_html() @:open_shift_button_html()

33
templates/drinks.rs.html Normal file
View file

@ -0,0 +1,33 @@
@use super::base_html;
@(shift_id: u32)
@:base_html({
<form action="/api/drinks" method="post">
<input required type="radio" id="two" name="price" value="2" />
<label for="two">$2</label>
<input type="radio" id="three" name="price" value="3" />
<label for="three">$3</label>
<input type="radio" id="five" name="price" value="5" />
<label for="five">$5</label>
<input type="radio" id="eight" name="price" value="8" />
<label for="eight">$8</label>
<input type="radio" id="fifteen" name="price" value="15" />
<label for="fifteen">$15</label>
<label for="quantity">Quantity: </label>
<input type="number" id="quantity" name="quantity" value="1" />
<input type="hidden" id="shift_id" name="shift_id" value="@shift_id" />
<button type="submit">
submit
</button>
</form>
})

View file

@ -8,8 +8,15 @@
<main> <main>
<div> <div>
@:shift_button_html(open_shift) @:shift_button_html(open_shift.as_ref())
</div> </div>
@if open_shift.is_some() {
<div hx-boost="true">
<a href="/shifts/@open_shift.unwrap().id/drinks">
<button>Drinks</button>
</a>
</div>
}
</main> </main>
}) })