Add automatic generation of favourite buttons

favourite buttons are based on how often a good i added to list
finished implementing setting category
made number of days until button an environment variable

rebase on main and fix

ran cargo fmt
This commit is contained in:
ide
2026-08-19 23:48:19 +02:00
parent c7782c82c4
commit 22cf928852
10 changed files with 314 additions and 36 deletions
+67 -7
View File
@@ -1,16 +1,16 @@
#[macro_use]
extern crate rocket;
use std::{env, path::Path};
use inkopslista_lib::{Item, ItemData};
use clap::Parser;
use inkopslista_lib::{ButtonItem, Item, ItemData};
use rocket::{
State,
fs::{FileServer, NamedFile},
serde::json::Json,
shield::Shield,
State,
};
use sqlite::ConnectionThreadSafe;
use std::path::{Path, PathBuf};
/// The directory where the web files for the gui is stored.
fn www_path() -> &'static Path {
@@ -47,6 +47,44 @@ fn get_list(db: &State<ConnectionThreadSafe>) -> Json<Vec<Item>> {
Json(list)
}
#[get("/api/favorites")]
fn get_favorites(db: &State<ConnectionThreadSafe>) -> Json<Vec<ButtonItem>> {
let opt = Opt::parse();
let query = format!("WITH favorites as (SELECT itemname, COUNT(date_added) day_count
FROM adds
WHERE (date(julianday('now')) - date(date_added, 'unixepoch')) < 184
GROUP BY itemname
HAVING day_count > {num_days})
SELECT favorites.itemname, category FROM favorites LEFT OUTER JOIN categories ON favorites.itemname = categories.itemname", num_days = opt.num_days);
let list: Vec<ButtonItem> = db
.prepare(query)
.unwrap()
.into_iter()
.map(|row| {
let row = row.unwrap();
ButtonItem {
name: row.read::<&str, _>("itemname").to_string(),
category: row
.try_read::<&str, _>("category")
.ok()
.map(ToString::to_string),
}
})
.collect();
Json(list)
}
#[put("/api/favourites/<itemname>/category", data = "<category>")]
fn put_category(db: &State<ConnectionThreadSafe>, itemname: &str, category: &str) {
let query = "INSERT INTO categories VALUES (?, ?) ON CONFLICT DO UPDATE SET category = ?";
let mut statement = db.prepare(query).unwrap();
statement.bind((1, itemname)).unwrap();
statement.bind((2, category)).unwrap();
statement.bind((3, category)).unwrap();
while let Ok(sqlite::State::Row) = statement.next() {}
}
#[put("/api/list/<item>", data = "<data>")]
fn put_list(item: &str, data: Json<ItemData>, db: &State<ConnectionThreadSafe>) {
// TODO: errorhandling
@@ -60,6 +98,10 @@ fn put_list(item: &str, data: Json<ItemData>, db: &State<ConnectionThreadSafe>)
statement.bind((4, data.checked as i64)).unwrap();
statement.bind((5, data.amount)).unwrap();
while let Ok(sqlite::State::Row) = statement.next() {}
let query = "INSERT INTO adds VALUES(?, strftime('%s','now')) ON CONFLICT DO NOTHING";
let mut statement = db.prepare(query).unwrap();
statement.bind((1, item)).unwrap();
while let Ok(sqlite::State::Row) = statement.next() {}
}
#[delete("/api/list/<item>")]
@@ -71,10 +113,18 @@ fn delete_item(item: &str, db: &State<ConnectionThreadSafe>) {
while let Ok(sqlite::State::Row) = statement.next() {}
}
#[derive(Parser)]
struct Opt {
#[clap(long, env = "DB_PATH")]
db_path: PathBuf,
#[clap(long, env = "NUM_DAYS", default_value = "5")]
num_days: u32,
}
#[launch]
fn rocket() -> _ {
let db_path = env::var("DB_PATH").unwrap();
let db = sqlite::Connection::open_thread_safe(db_path).unwrap();
let opt = Opt::parse();
let db = sqlite::Connection::open_thread_safe(opt.db_path).unwrap();
db.execute(include_str!("db/init.sql"))
.expect("unable to initialize database");
rocket::build()
@@ -84,6 +134,16 @@ fn rocket() -> _ {
.disable::<rocket::shield::Frame>(),
)
.manage(db)
.mount("/", routes![index, get_list, put_list, delete_item])
.mount(
"/",
routes![
index,
get_list,
put_list,
delete_item,
get_favorites,
put_category
],
)
.mount("/", FileServer::from(www_path()))
}