4
0
Fork 0
mirror of https://github.com/zzzzDev4/ias-tea-axum.git synced 2025-04-21 07:41:21 +02:00

general improvements and support for tea notes

This commit is contained in:
zzzz 2024-06-11 21:45:17 +02:00
parent 4ec74e18ed
commit 9caeb1d5f6
7 changed files with 532 additions and 207 deletions

573
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,11 +6,13 @@ edition = "2021"
[dependencies] [dependencies]
axum = "0.7.3" axum = "0.7.3"
chrono = "0.4.38"
uuid = { version = "1.8.0", features = ["v4"] }
serde = { version = "1.0.188", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] }
shuttle-axum = "0.45.0" shuttle-axum = "0.45.0"
shuttle-runtime = "0.45.0" shuttle-runtime = "0.45.0"
shuttle-shared-db = { version = "0.45.0", features = ["postgres", "sqlx"] } shuttle-shared-db = { version = "0.45.0", features = ["postgres", "sqlx"] }
sqlx = "0.7.1" sqlx = { version = "0.7.1", features = [ "chrono", "uuid" ] }
tokio = "1.28.2" tokio = "1.28.2"
tower-http = { version = "0.5.0", features = ["fs"] } tower-http = { version = "0.5.0", features = ["fs"] }

View file

@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS tea (
id serial PRIMARY KEY, id serial PRIMARY KEY,
tea_name VARCHAR(128) NOT NULL, tea_name VARCHAR(128) NOT NULL,
rfid_code VARCHAR(128) NOT NULL, rfid_code VARCHAR(128) NOT NULL,
tea_notes: VARCHAR(512),
water_temp INT, water_temp INT,
steeping_seconds INT NOT NULL, steeping_seconds INT NOT NULL,
registration_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP registration_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
@ -15,6 +16,6 @@ CREATE TABLE IF NOT EXISTS steepinglog (
); );
CREATE TABLE IF NOT EXISTS config ( CREATE TABLE IF NOT EXISTS config (
default_steeping_time INT NOT NULL DEFAULT 120, default_steeping_time INT NOT NULL,
feedback_timeout BIGINT NOT NULL DEFAULT 60 feedback_timeout BIGINT NOT NULL
); );

View file

@ -1,11 +1,25 @@
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgPool}; use sqlx::{FromRow, PgPool};
use uuid::Uuid;
#[derive(Clone)] #[derive(Clone)]
pub struct MyState { pub struct MyState {
pub pool: PgPool, pub pool: PgPool,
} }
#[derive(Deserialize)]
pub struct TeaMeta {
pub tea_name: String,
pub tea_notes: String,
pub water_temp: i32,
}
#[derive(Deserialize)]
pub struct TeaSteepingTimeChange {
pub new_steeping_seconds: i32,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct TeaNew { pub struct TeaNew {
pub tea_name: String, pub tea_name: String,
@ -19,12 +33,22 @@ pub struct Tea {
pub id: i32, pub id: i32,
pub tea_name: String, pub tea_name: String,
pub rfid_code: String, pub rfid_code: String,
pub tea_notes: String,
pub water_temp: i32, pub water_temp: i32,
pub steeping_seconds: i32, pub steeping_seconds: i32,
pub registration_timestamp: NaiveDateTime,
}
#[derive(Serialize, FromRow)]
pub struct SteepingLog {
pub change_id: Uuid,
pub tea_id: i32,
pub steeping_seconds: i32,
pub steeping_tested_time: NaiveDateTime,
} }
#[derive(Serialize, Deserialize, FromRow)] #[derive(Serialize, Deserialize, FromRow)]
pub struct Config { pub struct Config {
pub default_steeping_time: i32, pub default_steeping_time: i32,
pub feedback_timeout: i32, pub feedback_timeout: i64,
} }

View file

@ -1,4 +1,6 @@
// Invoke-WebRequest -Uri https://ias-tea-axum.shuttleapp.rs/addtea -Method POST -Body '{ "teaname": DemoTea, "rfidcode": "lo1rfId42", "watertemp": 200, "steepingseconds": 10}' -ContentType 'application/json' // Invoke-WebRequest -Uri https://ias-tea-axum.shuttleapp.rs/addtea -Method POST -Body '{ "teaname": DemoTea, "rfidcode": "lo1rfId42", "watertemp": 200, "steepingseconds": 10}' -ContentType 'application/json'
// cargo shuttle resource delete database::shared::postgres
// cargo shuttle resource list --show-secrets
use axum::{ use axum::{
routing::{get, post}, routing::{get, post},
@ -29,15 +31,18 @@ async fn main(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_axum::Shut
.route("/brewing-events", get(retrieve_brewing_events)) .route("/brewing-events", get(retrieve_brewing_events))
// types of tea // types of tea
.route("/types-of-tea", get(retrieve_types_of_tea)) .route("/types-of-tea", get(retrieve_types_of_tea))
.route("/types-of-tea/:id", post(update_tea_by_id)) .route("/types-of-tea/:id", post(update_tea_meta_by_id))
.route("/types-of-tea/:id", get(retrieve_tea_by_id)) .route("/types-of-tea/:id", get(retrieve_tea_by_id))
// update steeping time and trigger log entry // update steeping time and trigger log entry
.route("/tea-steeping-time/:id", post(update_tea_steeping_time_by_id)) .route(
"/tea-steeping-time/:id",
post(update_tea_steeping_time_by_id),
)
// teavail-device // teavail-device
.route("/add-tea", post(add_tea))
.route("/update-tea", post(update_tea_by_rfid)) // probably already covered by "update_tea_by_id"
.route("/delete-tea", get(delete_tea_by_id))
.route("/tea-by-rfid/:rfid", get(retrieve_tea_by_rfid)) .route("/tea-by-rfid/:rfid", get(retrieve_tea_by_rfid))
.route("/add-tea", post(add_tea))
.route("/delete-tea", get(delete_tea_by_id))
//.route("/update-tea", post(update_tea_by_rfid)) // probably already covered by "update_tea_by_id"
.with_state(state); .with_state(state);
Ok(router.into()) Ok(router.into())

View file

@ -1,3 +1,4 @@
use crate::data::{Config, MyState, SteepingLog, Tea};
use axum::{ use axum::{
extract::{Path, State}, extract::{Path, State},
http::StatusCode, http::StatusCode,
@ -5,13 +6,11 @@ use axum::{
Json, Json,
}; };
use crate::data::{MyState, Tea};
pub async fn retrieve_config( pub async fn retrieve_config(
State(state): State<MyState>, State(state): State<MyState>,
) -> Result<impl IntoResponse, impl IntoResponse> { ) -> Result<impl IntoResponse, impl IntoResponse> {
match sqlx::query_as::<_, Tea>("SELECT * FROM config") match sqlx::query_as::<_, Config>("SELECT * FROM config")
.fetch_all(&state.pool) .fetch_one(&state.pool)
.await .await
{ {
Ok(config) => Ok((StatusCode::OK, Json(config))), Ok(config) => Ok((StatusCode::OK, Json(config))),
@ -45,15 +44,20 @@ pub async fn retrieve_tea_by_id(
} }
} }
// TODO: implement this correctly
pub async fn retrieve_brewing_events( pub async fn retrieve_brewing_events(
State(state): State<MyState>, State(state): State<MyState>,
) -> Result<impl IntoResponse, impl IntoResponse> { ) -> Result<impl IntoResponse, impl IntoResponse> {
match sqlx::query_as::<_, Tea>("SELECT * FROM steepinglog GROUPBY tea_id") match sqlx::query_as::<_, SteepingLog>(
.fetch_optional(&state.pool) "
.await SELECT *
FROM steepinglog
ORDER BY tea_id
",
)
.fetch_all(&state.pool)
.await
{ {
Ok(tea) => Ok((StatusCode::OK, Json(tea))), Ok(steeping_log) => Ok((StatusCode::OK, Json(steeping_log))),
Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())), Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
} }
} }
@ -82,7 +86,7 @@ pub async fn delete_tea_by_id(
match sqlx::query_as::<_, Tea>( match sqlx::query_as::<_, Tea>(
" "
DELETE FROM tea WHERE id = $1 DELETE FROM tea WHERE id = $1
RETURNING id, teaname, rfidcode, watertemp, steepingseconds RETURNING id, teaname, rfidcode, tea_notes, water_temp, steeping_seconds, registration_timestamp
", ",
) )
.bind(id) .bind(id)

View file

@ -1,4 +1,4 @@
use crate::data::{Config, MyState, Tea, TeaNew}; use crate::data::{Config, MyState, Tea, TeaMeta, TeaNew, TeaSteepingTimeChange};
use axum::{ use axum::{
extract::{Path, State}, extract::{Path, State},
http::StatusCode, http::StatusCode,
@ -10,7 +10,7 @@ pub async fn update_config(
State(state): State<MyState>, State(state): State<MyState>,
Json(config): Json<Config>, Json(config): Json<Config>,
) -> Result<impl IntoResponse, impl IntoResponse> { ) -> Result<impl IntoResponse, impl IntoResponse> {
match sqlx::query_as::<_, Tea>( match sqlx::query_as::<_, Config>(
" "
UPDATE config UPDATE config
SET default_steeping_time = $1, feedback_timeout = $2 SET default_steeping_time = $1, feedback_timeout = $2
@ -28,22 +28,22 @@ pub async fn update_config(
} }
// TODO: should be used by web-interface to update metadata // TODO: should be used by web-interface to update metadata
pub async fn update_tea_by_id( pub async fn update_tea_meta_by_id(
State(state): State<MyState>, State(state): State<MyState>,
Path(id): Path<i32>, Path(id): Path<i32>,
Json(data): Json<TeaNew>, Json(tea_meta): Json<TeaMeta>,
) -> Result<impl IntoResponse, impl IntoResponse> { ) -> Result<impl IntoResponse, impl IntoResponse> {
match sqlx::query_as::<_, Tea>( match sqlx::query_as::<_, Tea>(
" "
UPDATE tea UPDATE tea
SET tea_name = $1, water_temp = $2, steeping_seconds = $3 SET tea_name = $1, water_temp = $2, tea_notes = $3
WHERE id = $4 WHERE id = $4
RETURNING id, tea_name, rfid_code, water_temp, steeping_seconds RETURNING id, tea_name, rfid_code, tea_notes, water_temp, steeping_seconds, registration_timestamp
", ",
) )
.bind(&data.tea_name) .bind(&tea_meta.tea_name)
.bind(data.water_temp) .bind(tea_meta.water_temp)
.bind(data.steeping_seconds) .bind(&tea_meta.tea_notes)
.bind(id) .bind(id)
.fetch_one(&state.pool) .fetch_one(&state.pool)
.await .await
@ -57,7 +57,7 @@ pub async fn update_tea_by_id(
pub async fn update_tea_steeping_time_by_id( pub async fn update_tea_steeping_time_by_id(
State(state): State<MyState>, State(state): State<MyState>,
Path(id): Path<i32>, Path(id): Path<i32>,
Json(tea_new): Json<TeaNew>, Json(steeping_time_change): Json<TeaSteepingTimeChange>,
) -> Result<impl IntoResponse, impl IntoResponse> { ) -> Result<impl IntoResponse, impl IntoResponse> {
match sqlx::query_as::<_, Tea>( match sqlx::query_as::<_, Tea>(
" "
@ -66,7 +66,7 @@ pub async fn update_tea_steeping_time_by_id(
RETURNING id, tea_name, rfid_code, water_temp, steeping_seconds RETURNING id, tea_name, rfid_code, water_temp, steeping_seconds
", ",
) )
.bind(tea_new.steeping_seconds) .bind(steeping_time_change.new_steeping_seconds)
.bind(id) .bind(id)
.fetch_one(&state.pool) .fetch_one(&state.pool)
.await .await
@ -80,7 +80,7 @@ pub async fn update_tea_steeping_time_by_id(
", ",
) )
.bind(id) .bind(id)
.bind(tea_new.steeping_seconds) .bind(steeping_time_change.new_steeping_seconds)
.execute(&state.pool) .execute(&state.pool)
.await .await
{ {
@ -92,7 +92,48 @@ pub async fn update_tea_steeping_time_by_id(
} }
} }
// used by teavail-device // registers new tea and triggers log entry for initial steeping time
pub async fn add_tea(
State(state): State<MyState>,
Json(tea_new): Json<TeaNew>,
) -> Result<impl IntoResponse, impl IntoResponse> {
match sqlx::query_as::<_, Tea>(
"
INSERT INTO tea (tea_name, rfid_code, water_temp, steeping_seconds)
VALUES ($1, $2, $3, $4)
RETURNING id, tea_name, rfid_code, water_temp, steeping_seconds
",
)
.bind(&tea_new.tea_name)
.bind(&tea_new.rfid_code)
.bind(tea_new.water_temp)
.bind(tea_new.steeping_seconds)
.fetch_one(&state.pool)
.await
{
Ok(tea) => {
// add steeping time change to steeping log
match sqlx::query(
"
INSERT INTO steepinglog (tea_id, steeping_seconds)
VALUES ($1, $2)
",
)
.bind(tea.id)
.bind(tea.steeping_seconds)
.execute(&state.pool)
.await
{
Ok(_) => Ok((StatusCode::OK, Json(tea))),
Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
}
}
Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
}
}
/*
// used by teavail-device - deprecated
pub async fn update_tea_by_rfid( pub async fn update_tea_by_rfid(
State(state): State<MyState>, State(state): State<MyState>,
Json(data): Json<TeaNew>, Json(data): Json<TeaNew>,
@ -115,27 +156,4 @@ pub async fn update_tea_by_rfid(
Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())), Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
} }
} }
*/
pub async fn add_tea(
State(state): State<MyState>,
Json(tea_new): Json<TeaNew>,
) -> Result<impl IntoResponse, impl IntoResponse> {
println!("add");
match sqlx::query_as::<_, Tea>(
"
INSERT INTO tea (tea_name, rfid_code, water_temp, steeping_seconds)
VALUES ($1, $2, $3, $4)
RETURNING id, tea_name, rfid_code, water_temp, steeping_seconds
",
)
.bind(&tea_new.tea_name)
.bind(&tea_new.rfid_code)
.bind(tea_new.water_temp)
.bind(tea_new.steeping_seconds)
.fetch_one(&state.pool)
.await
{
Ok(tea) => Ok((StatusCode::CREATED, Json(tea))),
Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
}
}