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:
parent
4ec74e18ed
commit
9caeb1d5f6
7 changed files with 532 additions and 207 deletions
573
Cargo.lock
generated
573
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"] }
|
|
@ -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
|
||||||
);
|
);
|
26
src/data.rs
26
src/data.rs
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -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())
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue