Minimal Leptos setup
This commit is contained in:
parent
1e77a7d831
commit
e03a16d13e
29 changed files with 1345 additions and 684 deletions
40
src/server/app_state.rs
Normal file
40
src/server/app_state.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use rand::prelude::*;
|
||||
use ring_compat::signature::ed25519::SigningKey;
|
||||
use rusqlite::Connection;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct AppState {
|
||||
pub db: Connection,
|
||||
pub last_sync: i64,
|
||||
pub id: u64,
|
||||
pub key: SigningKey,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
let db = Connection::open_in_memory()
|
||||
.expect("Failed to create DB.");
|
||||
db.execute(
|
||||
"CREATE TABLE swap (
|
||||
rand INTEGER,
|
||||
store INTEGER,
|
||||
account INTEGER,
|
||||
submitter INTEGER,
|
||||
time INTEGER,
|
||||
cancellation BOOL
|
||||
)",
|
||||
(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut rng = rand::thread_rng();
|
||||
let last_sync = i64::MIN;
|
||||
let id = rng.gen();
|
||||
let key = SigningKey::generate(&mut rng);
|
||||
AppState {
|
||||
db,
|
||||
last_sync,
|
||||
id,
|
||||
key,
|
||||
}
|
||||
}
|
||||
}
|
127
src/server/data_door.rs
Normal file
127
src/server/data_door.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
use chrono::offset::Utc;
|
||||
use curl::{easy, easy::Easy2};
|
||||
use rand::prelude::*;
|
||||
use rusqlite::{Connection, DatabaseName};
|
||||
use ring_compat::signature::ed25519::SigningKey;
|
||||
use tauri::{Manager, State};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::server::app_state::AppState;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Collector(Vec<u8>, Vec<u8>, usize);
|
||||
|
||||
impl easy::Handler for Collector {
|
||||
fn write(&mut self, data: &[u8]) -> Result<usize, easy::WriteError> {
|
||||
self.0.extend_from_slice(data);
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn read(&mut self, data: &mut [u8]) -> Result<usize, easy::ReadError> {
|
||||
let p = self.2;
|
||||
let src: &[u8] = self.1.as_ref();
|
||||
let n = usize::min(src.len() - p, data.len());
|
||||
data[..n].copy_from_slice(&src[p..(p + n)]);
|
||||
self.2 = n + p;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn seek(&mut self, whence: std::io::SeekFrom) -> easy::SeekResult {
|
||||
use std::io::SeekFrom::{Current, End, Start};
|
||||
match whence {
|
||||
Start(p) => {
|
||||
self.2 = p as usize;
|
||||
easy::SeekResult::Ok
|
||||
}
|
||||
End(d) => {
|
||||
let p = self.1.len() as i64;
|
||||
if p + d < 0 {
|
||||
easy::SeekResult::Fail
|
||||
} else {
|
||||
self.2 = (p + d) as usize;
|
||||
easy::SeekResult::Ok
|
||||
}
|
||||
}
|
||||
Current(d) => {
|
||||
let p = self.2 as i64;
|
||||
if p + d < 0 {
|
||||
easy::SeekResult::Fail
|
||||
} else {
|
||||
self.2 = (p + d) as usize;
|
||||
easy::SeekResult::Ok
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn data_client(file: &str) -> Result<Easy2<Collector>, ()> {
|
||||
let mut client = Easy2::new(Collector(Vec::new(), Vec::new(), 0));
|
||||
let url = format!(
|
||||
"https://cloud.seebruecke.org/public.php/webdav/data/{}",
|
||||
file
|
||||
);
|
||||
client.url(&url).map_err(|_| ())?;
|
||||
client.username(include_str!("cloud_user.txt")).map_err(|_| ())?;
|
||||
client.http_auth(easy::Auth::new().auto(true))
|
||||
.map_err(|_| ())?;
|
||||
client.ssl_cainfo_blob(include_bytes!("isrg-root-x1.pem"))
|
||||
.map_err(|_| ())?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
fn put_client(file: &str, payload: &[u8]) -> Result<Easy2<Collector>, ()> {
|
||||
let mut client = data_client(&file)?;
|
||||
client.put(true).map_err(|_| ())?;
|
||||
client.get_mut().1.extend_from_slice(payload);
|
||||
client.in_filesize(payload.len() as u64)
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
client.upload(true)
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn pull_data(
|
||||
_state: State<'_, Mutex<AppState>>,
|
||||
) -> Result<String, ()> {
|
||||
let mut client = data_client("")?;
|
||||
client.custom_request("PROPFIND").map_err(|_| ())?;
|
||||
client.perform().map_err(|_| ())?;
|
||||
let content = &client.get_ref().0;
|
||||
Ok(String::from_utf8_lossy(content).to_string())
|
||||
}
|
||||
|
||||
async fn push_key(id: &u64, key: &SigningKey) -> Result<(), ()> {
|
||||
let file = format!("{:016X}.key", id);
|
||||
let v_key = key.verifying_key();
|
||||
let client = put_client(&file, v_key.as_ref())?;
|
||||
let _perf = client.perform()
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_db(id: &u64, db: &Connection, app: tauri::AppHandle) -> Result<(), ()> {
|
||||
let filename = format!("{:016X}.sqlite", id);
|
||||
let path = app.path().resolve(&filename, tauri::path::BaseDirectory::Temp)
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
db.backup(DatabaseName::Main, &path, None)
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
let buf = std::fs::read(&path)
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
let client = put_client(&filename, buf.as_ref())?;
|
||||
let _perf = client.perform()
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn push_data(
|
||||
app: tauri::AppHandle,
|
||||
state: State<'_, Mutex<AppState>>,
|
||||
) -> Result<(), ()> {
|
||||
let state = state.lock().await;
|
||||
push_key(&state.id, &state.key).await?;
|
||||
push_db(&state.id, &state.db, app)?;
|
||||
Ok(())
|
||||
}
|
31
src/server/isrg-root-x1.pem
Normal file
31
src/server/isrg-root-x1.pem
Normal file
|
@ -0,0 +1,31 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
200
src/server/mod.rs
Normal file
200
src/server/mod.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
use chrono::offset::Utc;
|
||||
use rusqlite::{types::ToSqlOutput, ToSql};
|
||||
use tauri::{Manager, State};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
use tokio::sync::Mutex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod app_state;
|
||||
mod data_door;
|
||||
|
||||
use app_state::AppState;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Store {
|
||||
Aldi,
|
||||
Edeka,
|
||||
Dm,
|
||||
Lidl,
|
||||
Rewe,
|
||||
Tegut,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Account {
|
||||
Sumpf,
|
||||
Heinersyndikat,
|
||||
}
|
||||
|
||||
struct Inventory {
|
||||
acc: Account,
|
||||
cash: i64,
|
||||
vouchers: Vec<VoucherInventory>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct VoucherInventory {
|
||||
store: Store,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Store {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
match s {
|
||||
"aldi" => Ok(Store::Aldi),
|
||||
"edeka" => Ok(Store::Edeka),
|
||||
"dm" => Ok(Store::Dm),
|
||||
"lidl" => Ok(Store::Lidl),
|
||||
"rewe" => Ok(Store::Rewe),
|
||||
"tegut" => Ok(Store::Tegut),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Account {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
match s {
|
||||
"sumpf" => Ok(Account::Sumpf),
|
||||
"hs" => Ok(Account::Heinersyndikat),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Store {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||
match self {
|
||||
Store::Aldi => 0.to_sql(),
|
||||
Store::Edeka => 1.to_sql(),
|
||||
Store::Dm => 2.to_sql(),
|
||||
Store::Lidl => 3.to_sql(),
|
||||
Store::Rewe => 4.to_sql(),
|
||||
Store::Tegut => 5.to_sql(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Account {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||
match self {
|
||||
Account::Sumpf => 0.to_sql(),
|
||||
Account::Heinersyndikat => 1.to_sql(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_inventory(data: HashMap<String, String>) -> Result<Inventory, ()> {
|
||||
let a = data.get("cafe-inventory-acc").ok_or(())?;
|
||||
let acc: Account = Account::try_from(a.as_ref())?;
|
||||
let mut vouchers = Vec::new();
|
||||
for s in ["aldi", "dm", "lidl", "rewe", "tegut"] {
|
||||
let Ok(store) = s.try_into() else {
|
||||
println!("Did not find '{}' in inventory data.", s);
|
||||
continue;
|
||||
};
|
||||
match data.get(&format!("cafe-inventory-{}", s)) {
|
||||
None => (),
|
||||
Some(c) => {
|
||||
let c = if c == "" {"0"} else {c};
|
||||
let Ok(count) = c.parse() else {
|
||||
println!("Invalid count '{}' for '{}' in inventory data.", c, s);
|
||||
continue;
|
||||
};
|
||||
let v = VoucherInventory { store, count };
|
||||
vouchers.push(v);
|
||||
},
|
||||
}
|
||||
}
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn inventory(
|
||||
data: HashMap<String, String>,
|
||||
state: State<'_, Mutex<AppState>>,
|
||||
) -> Result<(), ()> {
|
||||
let now = Utc::now().timestamp();
|
||||
let state = state.lock().await;
|
||||
let inv = parse_inventory(data)?;
|
||||
for v in inv.vouchers {
|
||||
state.db.execute(
|
||||
"INSERT INTO voucher_inventory VALUES ()",
|
||||
(
|
||||
inv.acc,
|
||||
v.store,
|
||||
v.count,
|
||||
now,
|
||||
),
|
||||
)
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn swap(
|
||||
store: &str,
|
||||
acc: i64,
|
||||
state: State<'_, Mutex<AppState>>,
|
||||
) -> Result<(), ()> {
|
||||
let state = state.lock().await;
|
||||
let store: Store = store.try_into()?;
|
||||
state.db.execute(
|
||||
"INSERT INTO swap VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
(
|
||||
store,
|
||||
acc,
|
||||
i64::from_ne_bytes(state.id.to_ne_bytes()),
|
||||
Utc::now().timestamp(),
|
||||
false,
|
||||
),
|
||||
)
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn count(state: State<'_, Mutex<AppState>>) -> Result<String, ()> {
|
||||
let state = state.lock().await;
|
||||
let mut stmt =
|
||||
state.db.prepare("SELECT COUNT(*) FROM swap")
|
||||
.map_err(|e| println!("{:?}", e))?;
|
||||
let mut rows = stmt.query([]).map_err(|e| println!("{:?}", e))?;
|
||||
let row = rows.next().map_err(|e| println!("{:?}", e))?;
|
||||
let row = match row {
|
||||
Some(r) => Ok(r),
|
||||
None => {
|
||||
println!("No rows");
|
||||
Err(())
|
||||
}
|
||||
}?;
|
||||
let cnt: u64 = row.get(0).map_err(|e| println!("{:?}", e))?;
|
||||
Ok(cnt.to_string())
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
let state = AppState::new();
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.setup(|app| {
|
||||
app.manage(Mutex::new(state));
|
||||
let scope = app.fs_scope();
|
||||
let path = app.path();
|
||||
scope.allow_directory(path.temp_dir()?, false)?;
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
swap,
|
||||
count,
|
||||
inventory,
|
||||
data_door::pull_data,
|
||||
data_door::push_data,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue