mirror of
https://github.com/hulthe/inkopslista.git
synced 2026-09-18 18:03:39 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4315ed07cc | ||
|
|
495b0de7a7 |
@@ -3,3 +3,5 @@ target/
|
||||
**/*.rs.bk
|
||||
|
||||
*.sqlite
|
||||
|
||||
dabase
|
||||
Generated
+1650
-1161
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -18,6 +18,8 @@ pub use fs::*;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod fs {
|
||||
use crate::runtime::spawn;
|
||||
|
||||
use super::Config;
|
||||
use anyhow::{Context, Ok};
|
||||
use serde_json;
|
||||
@@ -34,7 +36,7 @@ mod fs {
|
||||
.place_config_file(CONFIG_FILE_NAME)
|
||||
.context("unable to create config directory")?;
|
||||
|
||||
tokio::spawn(async {
|
||||
spawn(async {
|
||||
let res = tokio::fs::write(path, conf)
|
||||
.await
|
||||
.context("unable to write config file");
|
||||
|
||||
+34
-1
@@ -17,7 +17,7 @@ use std::{
|
||||
|
||||
use inkopslista_lib::{ButtonItem, Item, ItemData};
|
||||
use reqwest::Url;
|
||||
use slint::{Model, ModelRc, SharedString, ToSharedString, VecModel};
|
||||
use slint::{Model, ModelRc, ToSharedString, VecModel};
|
||||
|
||||
use crate::{config::Config, runtime::spawn};
|
||||
|
||||
@@ -60,6 +60,26 @@ async fn delete_item(item: &str, conf: &Mutex<Config>) -> Result<(), anyhow::Err
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn put_category(
|
||||
itemname: &str,
|
||||
category: &str,
|
||||
conf: &Mutex<Config>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let client = reqwest::Client::new();
|
||||
let url: Url = format!(
|
||||
"{address}/api/favourites/{itemname}/category",
|
||||
address = conf.lock().unwrap().address
|
||||
)
|
||||
.parse()?;
|
||||
let _response = client
|
||||
.put(url)
|
||||
.body(category.to_string())
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert shopping list to slint types
|
||||
fn update_slint_list(new_list: Vec<Item>, list: &VecModel<SItem>) {
|
||||
list.clear();
|
||||
@@ -322,6 +342,19 @@ pub fn run() -> Result<(), anyhow::Error> {
|
||||
}
|
||||
});
|
||||
|
||||
let conf = conf_shared.clone();
|
||||
state.on_set_cat(move |itemname, cat| {
|
||||
let conf = conf.clone();
|
||||
|
||||
spawn(async move {
|
||||
let res = put_category(&itemname, &cat, &conf).await;
|
||||
if let Err(err) = res {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
state.on_set_server_address(move |address| {
|
||||
conf_shared.lock().unwrap().address = address.to_string();
|
||||
#[cfg(target_os = "linux")]
|
||||
|
||||
+6
-2
@@ -13,8 +13,6 @@ import {
|
||||
import { SettingsView } from "settings.slint";
|
||||
import { State, SItem, SButtonItem } from "state.slint";
|
||||
export { State, SItem }
|
||||
|
||||
|
||||
component GoodListItem inherits Rectangle {
|
||||
in-out property <SItem> item;
|
||||
background: #ffffff10;
|
||||
@@ -281,16 +279,22 @@ component ShopView inherits VerticalLayout {
|
||||
|
||||
component CatItem inherits HorizontalBox {
|
||||
in-out property <SButtonItem> item;
|
||||
|
||||
Text {
|
||||
text: item.name;
|
||||
vertical-alignment: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
model: ["kyl", "skafferi", "frys", "övrigt"];
|
||||
current-value: item.category;
|
||||
width: 60%;
|
||||
selected(category) => { State.set-cat(item.name, category) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
component CatView inherits VerticalLayout {
|
||||
in-out property <[SButtonItem]> cats;
|
||||
|
||||
|
||||
+2
-3
@@ -4,9 +4,7 @@ export struct SItem {
|
||||
amount: int,
|
||||
}
|
||||
|
||||
export struct SButtonItem{
|
||||
name: string,
|
||||
category: string}
|
||||
export struct SButtonItem { name: string, category: string }
|
||||
|
||||
export global State {
|
||||
in-out property <[SItem]> list: [
|
||||
@@ -20,6 +18,7 @@ export global State {
|
||||
callback delete-checked();
|
||||
callback inc-amount(string);
|
||||
callback dec-amount(string);
|
||||
callback set-cat(string, string);
|
||||
in-out property <string> server-address;
|
||||
in property <[SButtonItem]> favorites: [
|
||||
{ name: "mjölk", category: "kyl" },
|
||||
|
||||
@@ -9,3 +9,4 @@ serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
inkopslista-lib = { path = "../lib" }
|
||||
sqlite = "0.37.0"
|
||||
clap = { version = "4.6.6", features = ["derive", "env"] }
|
||||
|
||||
+20
-10
@@ -1,8 +1,7 @@
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use std::{env, path::Path};
|
||||
|
||||
use clap::Parser;
|
||||
use inkopslista_lib::{ButtonItem, Item, ItemData};
|
||||
use rocket::{
|
||||
fs::{FileServer, NamedFile},
|
||||
@@ -11,6 +10,7 @@ use rocket::{
|
||||
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 {
|
||||
@@ -49,12 +49,14 @@ fn get_list(db: &State<ConnectionThreadSafe>) -> Json<Vec<Item>> {
|
||||
|
||||
#[get("/api/favorites")]
|
||||
fn get_favorites(db: &State<ConnectionThreadSafe>) -> Json<Vec<ButtonItem>> {
|
||||
let query = "WITH favorites as (SELECT itemname, COUNT(date_added) day_count
|
||||
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 > 0)
|
||||
SELECT favorites.itemname, category FROM favorites LEFT OUTER JOIN categories ON favorites.itemname = categories.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()
|
||||
@@ -73,8 +75,8 @@ fn get_favorites(db: &State<ConnectionThreadSafe>) -> Json<Vec<ButtonItem>> {
|
||||
Json(list)
|
||||
}
|
||||
|
||||
#[put("/api/favourites/<itemname>", data = "<category>")]
|
||||
fn set_categories(db: &State<ConnectionThreadSafe>, itemname: &str, category: &str) {
|
||||
#[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();
|
||||
@@ -111,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()
|
||||
@@ -132,7 +142,7 @@ fn rocket() -> _ {
|
||||
put_list,
|
||||
delete_item,
|
||||
get_favorites,
|
||||
set_categories
|
||||
put_category
|
||||
],
|
||||
)
|
||||
.mount("/", FileServer::from(www_path()))
|
||||
|
||||
Reference in New Issue
Block a user