mirror of
https://github.com/hulthe/inkopslista.git
synced 2026-09-18 18:03:39 +02:00
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
150 lines
4.7 KiB
Rust
150 lines
4.7 KiB
Rust
#[macro_use]
|
|
extern crate rocket;
|
|
|
|
use clap::Parser;
|
|
use inkopslista_lib::{ButtonItem, Item, ItemData};
|
|
use rocket::{
|
|
State,
|
|
fs::{FileServer, NamedFile},
|
|
serde::json::Json,
|
|
shield::Shield,
|
|
};
|
|
use sqlite::ConnectionThreadSafe;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
/// The directory where the web files for the gui is stored.
|
|
fn www_path() -> &'static Path {
|
|
Path::new("./www")
|
|
}
|
|
|
|
/// Serve `index.html` from [`www_path`].
|
|
#[get("/")]
|
|
async fn index() -> NamedFile {
|
|
NamedFile::open(www_path().join("index.html"))
|
|
.await
|
|
.expect("index.html")
|
|
}
|
|
|
|
#[get("/api/list")]
|
|
fn get_list(db: &State<ConnectionThreadSafe>) -> Json<Vec<Item>> {
|
|
// TODO errorhandling
|
|
let query = "SELECT * FROM list;";
|
|
let list = db
|
|
.prepare(query)
|
|
.unwrap()
|
|
.into_iter()
|
|
.map(|row| {
|
|
let row = row.unwrap();
|
|
Item {
|
|
name: row.read::<&str, _>("itemname").to_string(),
|
|
data: ItemData {
|
|
checked: row.read::<i64, _>("checked") > 0,
|
|
amount: row.read::<i64, _>("amount"),
|
|
},
|
|
}
|
|
})
|
|
.collect();
|
|
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
|
|
let query =
|
|
"INSERT INTO list VALUES(?, ?, ?) ON CONFLICT DO UPDATE SET checked = ?, amount = ?;";
|
|
let mut statement = db.prepare(query).unwrap();
|
|
let data = data.into_inner();
|
|
statement.bind((1, item)).unwrap();
|
|
statement.bind((2, data.checked as i64)).unwrap();
|
|
statement.bind((3, data.amount)).unwrap();
|
|
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>")]
|
|
fn delete_item(item: &str, db: &State<ConnectionThreadSafe>) {
|
|
// TODO: Eror handling
|
|
let query = "DELETE FROM list WHERE itemname = ?";
|
|
let mut statement = db.prepare(query).unwrap();
|
|
statement.bind((1, item)).unwrap();
|
|
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 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()
|
|
.attach(
|
|
Shield::default()
|
|
// Allow content to be served through iframes
|
|
.disable::<rocket::shield::Frame>(),
|
|
)
|
|
.manage(db)
|
|
.mount(
|
|
"/",
|
|
routes![
|
|
index,
|
|
get_list,
|
|
put_list,
|
|
delete_item,
|
|
get_favorites,
|
|
put_category
|
|
],
|
|
)
|
|
.mount("/", FileServer::from(www_path()))
|
|
}
|