Bläddra i källkod

working deser

Johann Woelper 7 år sedan
förälder
incheckning
5ff30a28d8
6 ändrade filer med 478 tillägg och 317 borttagningar
  1. 28 2
      accounts.json
  2. 3 3
      src/main.rs
  3. 144 301
      src/query.rs
  4. 10 11
      src/server.rs
  5. 144 0
      webapp/app.js
  6. 149 0
      webapp/index.html

+ 28 - 2
accounts.json

@@ -1,8 +1,34 @@
 {
   "offline": {
     "queries": {
-      "http://localhost:8002": {},
+      "http://localhost:8002": {
+        "auctions": {
+          "http://egun.de/market/http://egun.de/market/item.php?id=888888": {
+            "price": 88.88,
+            "desc": "Harris Zweibein old",
+            "gcal": "yo",
+            "thumb": "http://egun.de/market/images/picture.gif",
+            "remaining": 789000,
+            "url": "http://egun.de/market/http://egun.de/market/item.php?id=88888888",
+            "timestamp": 1562264532,
+            "is_price_final": false
+          }
+        }
+      },
       "http://localhost:8001": {}
-  }
+    }
+  },
+  "johann": {
+    "queries": {
+      "http://www.egun.de/market/list_items.php?mode=qry&query=m1a&plusdescr=off&wheremode=and&ewb=1&status=&minprice=&maxprice=1900": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&query=aics&plusdescr=off&wheremode=and&ewb=&status=0&minprice=300&maxprice=2800": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&plusdescr=off&wheremode=and&query=shr+970&quick=1": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&query=smith+wesson+r8&plusdescr=off&wheremode=and&ewb=1": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&query=m305&plusdescr=off&wheremode=and&ewb=1": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&plusdescr=off&wheremode=and&maxprice=2200&query=m14+luxdef&quick=1": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&query=accuracy&plusdescr=off&wheremode=and&ewb=&status=&minprice=300&maxprice=2100": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&plusdescr=off&wheremode=and&query=harris+1A2-L&quick=1": {},
+      "http://www.egun.de/market/list_items.php?mode=qry&query=308+enfield&plusdescr=off&wheremode=and&ewb=1": {}
+    }
   }
 }

+ 3 - 3
src/main.rs

@@ -32,16 +32,16 @@ lazy_static! {
 
 fn daemon() {
     loop {
-
+        dbg!("loop");
         let mut accounts_unlocked = ACCOUNTS.lock().unwrap();
 
+        // dbg!(&temp_accounts);
         for (account_name, account) in accounts_unlocked.clone() {
-            dbg!(&account.updated());
             accounts_unlocked.insert(account_name.to_string(), account.updated());
         }
-
         drop(accounts_unlocked);
 
+
         // let pause = time::Duration::from_secs(30);
         // thread::sleep(pause);
         break;

+ 144 - 301
src/query.rs

@@ -1,12 +1,9 @@
-
-
-use std::collections::HashMap;
-use chrono::{Utc, Duration, Local, DateTime};
-use select::document::Document;
-use select::predicate::Name;
+use chrono::{DateTime, Duration, Local, Utc};
 use parse_duration::parse;
 use reqwest;
-
+use select::document::Document;
+use select::predicate::Name;
+use std::collections::HashMap;
 
 #[derive(Serialize, Deserialize, Debug, Default, Clone)]
 pub struct Auction {
@@ -17,374 +14,217 @@ pub struct Auction {
     pub remaining: i64,
     pub url: String,
     pub timestamp: i64,
-    pub is_price_final: bool
+    pub is_price_final: bool,
+}
+
+impl Auction {
+    fn update(&mut self) {
+        // println!("now: {:?} auction: {:?}", Utc::now().with_timezone(&Local).timestamp(), self.timestamp);
+        self.is_price_final = Utc::now().with_timezone(&Local).timestamp() > self.timestamp
+    }
 }
 
 #[derive(Serialize, Deserialize, Debug, Default, Clone)]
 pub struct Query {
     #[serde(default)]
     pub auctions: HashMap<String, Auction>,
-    #[serde(default)]
-    avg_price: f32
+    #[serde(skip_deserializing)]
+    avg_price: f32,
 }
 
 impl Query {
     fn get_avg_price(&mut self) -> f32 {
         self.auctions.iter().map(|(_u, a)| a.price).sum::<f32>() / self.auctions.len() as f32
     }
-}
 
+    fn detect_expired(&self) -> HashMap<String, Auction> {
+        self.auctions
+            .iter()
+            .map(|(u, mut a)| {
+                let mut a1 = a.clone();
+                a1.update();
+                (u.clone(), a1)
+            })
+            .collect()
+    }
+}
 
 #[derive(Serialize, Deserialize, Debug, Default, Clone)]
 pub struct Account {
-    // SEARCH URL: {AUCTION_URL: AUCTION} 
+    // SEARCH URL: {AUCTION_URL: AUCTION}
     // pub queries: HashMap<String, HashMap<String, Auction>>,
-    pub queries: HashMap<String, Query>
+    pub queries: HashMap<String, Query>,
 }
 
 impl Account {
-
-
-    pub fn run_queries(&self) -> HashMap<String, HashMap<String, Auction>> {
+    pub fn run_queries(&self) -> HashMap<String, Query> {
         self.queries
-        .clone()
-        .into_iter()
-        .map(|(url, mut query)| (url.clone(), {
-            query.auctions.extend(auctions_from_url(&url));
-            query.avg_price = query.get_avg_price();
-            query
-            }))
-        .map(|(url, query)| {
-
-            (url, query.auctions
+            .clone()
             .into_iter()
-            .map(|(u, mut a)| {
-                if Utc::now().with_timezone(&Local).timestamp() > a.timestamp {
-                // auction has expired, mark price as final
-                a.is_price_final = true;
-                }
-                (u,a)
-            }).collect())
-        })
-        .collect()
+            .map(|(url, mut query)| {
+                (url.clone(), {
+                    query.auctions.extend(auctions_from_url(&url));
+                    query.avg_price = query.get_avg_price();
+                    query
+                })
+            })
+            .map(|(url, mut query)| {
+                query.auctions = query.detect_expired();
+
+                (url, query)
+            })
+            .collect()
     }
 
-
-    // pub fn run_query(&self) -> HashMap<String, Query> {
-    //     self.queries
-    //     .clone()
-    //     .into_iter()
-    //     .map(|(url, mut auction_map)| (url.clone(), {
-    //         auction_map.extend(auctions_from_url(&url));
-    //         auction_map
-    //         }))
-    //     .map(|(url, mut auction_map)| {
-
-    //         (url, auction_map.into_iter().map(|(u, mut a)| {
-    //             if Utc::now().with_timezone(&Local).timestamp() > a.timestamp {
-    //             // auction has expired, mark price as final
-    //             a.is_price_final = true;
-    //             }
-    //             (u, Query{ auctions: HashMap::new()})
-    //         }).collect())
-    //     })
-    //     .collect()
-    // }
-
     pub fn updated(&self) -> Account {
         Account {
-            // queries: self.run_queries(),
-            queries: self.run_queries()
-            .iter()
-            .map(|(k,v)| (k.clone(), Query{auctions: v.clone(), .. Default::default()}))
-            .collect()
-
-            }
+            queries: self.run_queries(), // .iter()
+                                         // .map(|(k,v)| (k.clone(), Query{auctions: v.clone(), .. Default::default()}))
+                                         // .collect()
+        }
     }
-
 }
 
-// #[derive(Serialize, Deserialize, Debug, Clone)]
-// pub struct Query {
-//     pub url: String,
-//     pub auctions: Vec<Auction>,
-//     pub frequency: i64,
-// }
-
-// impl Query {
-//     pub fn run(&mut self) -> Query {
-//         self.auctions = parse_url(&self.url);
-//         self.clone()
-//     }
-
-
-
-//     fn detect_frequency(&self) {
-//         let _a = &self.auctions
-//         .iter()
-//         .map(|x| x)
-//         .collect::<Vec<_>>();
-//     }
-// }
-
-// impl Default for Query {
-//     fn default () -> Query {
-//         Query{
-//             frequency: Duration::minutes(5).num_seconds(),
-//             url: "".to_string(),
-//             auctions: vec![]
-//         }
-//     }
-// }
-
-
-
 fn date_to_gcal(date: DateTime<Local>) -> String {
-    format!("{}",
-        date.format("%Y%m%dT%H%M00/%Y%m%dT%H%M00")
-    )
+    format!("{}", date.format("%Y%m%dT%H%M00/%Y%m%dT%H%M00"))
 }
 
-
 /// Parse a relative offset "x Tage HH:MM" into proper time
 fn parse_remaining(timestring: &String) -> Option<Duration> {
-    match parse(&format!("{} minutes", timestring.replace(":", " hours ").replace("Tage", "days"))).ok() {
+    match parse(&format!(
+        "{} minutes",
+        timestring.replace(":", " hours ").replace("Tage", "days")
+    ))
+    .ok()
+    {
         Some(old_duration) => Duration::from_std(old_duration).ok(),
-        None => None
+        None => None,
     }
 }
 
-
 fn parse_price(price: &str) -> Option<f32> {
-    price.to_string()
-    .replace(".", "")
-    .replace(",", ".")
-    .replace(" EUR", "")
-    .parse().ok()
+    price
+        .to_string()
+        .replace(".", "")
+        .replace(",", ".")
+        .replace(" EUR", "")
+        .parse()
+        .ok()
 }
 
 
-fn parse_url(url: &str) -> Vec<Auction> {
-    let mut auctions = vec![];
-    // dbg!(&url);
-
-    if let Ok(mut resp) = reqwest::get(url) {
-
-        if !resp.status().is_success() {
-            println!("ERR {:?}", resp.text());
-            return auctions;
-        }
-
-        let text = resp.text().unwrap_or("".to_string());
-
 
+fn auctions_from_url(url: &str) -> HashMap<String, Auction> {
+    let mut auctions = HashMap::new();
+    dbg!(&url);
 
-    for node in Document::from_read(text.as_bytes())
-        .unwrap()
-        // .find(Name("a"))
-        // .filter(|n| n.attr("href").is_some())
-        // .filter(|n| n.attr("href").unwrap().contains("item.php?id="))
-        .find(Name("tr"))
-        .filter(|x| x.attr("bgcolor").is_some())
-        .filter(|x| x.attr("align").is_none())
+    let client = reqwest::Client::builder()
+        .timeout(Some(core::time::Duration::from_secs(2)))
+        .build()
+        .unwrap();
 
-        {
 
-            // Get auction name
-            if let Some(name) = node
-                .children().into_iter()
-                .filter(|x| x.name() == Some("td"))
-                .filter(|x| x.attr("align") == Some("LEFT"))
-                .flat_map(|x| x.children())
-                .filter(|x| x.name() == Some("a"))
-                .flat_map(|x| x.children())
-                .map(|x| x.text())
-                // .filter(|x| x.name() == Some("Text"))
-                .collect::<Vec<_>>().get(0) {
+    match client.get(url).send() {
+        Ok(mut resp) => {
+            if !resp.status().is_success() {
+                // println!("ERR {:?}", resp.text_with_charset("windows-1252"));
+                
+                return auctions;
+            }
 
+            // dbg!(&resp.text_with_charset("windows-1252"));
+            // dbg!(&resp.text());
+            let text = resp.text().unwrap_or("".to_string());
+            // let text = resp.text_with_charset("windows-1252").unwrap_or("".to_string());
+
+            for node in Document::from_read(text.as_bytes())
+                .unwrap()
+                // .find(Name("a"))
+                // .filter(|n| n.attr("href").is_some())
+                // .filter(|n| n.attr("href").unwrap().contains("item.php?id="))
+                .find(Name("tr"))
+                .filter(|x| x.attr("bgcolor").is_some())
+                .filter(|x| x.attr("align").is_none())
+            {
+                // Get auction name
+                if let Some(name) = node
+                    .children()
+                    .into_iter()
+                    .filter(|x| x.name() == Some("td"))
+                    .filter(|x| x.attr("align") == Some("LEFT"))
+                    .flat_map(|x| x.children())
+                    .filter(|x| x.name() == Some("a"))
+                    .flat_map(|x| x.children())
+                    .map(|x| x.text())
+                    // .filter(|x| x.name() == Some("Text"))
+                    .collect::<Vec<_>>()
+                    .get(0)
+                {
                     // If we have the name, go on finding other details
                     // instantiate mutable Auction
                     let mut auction = Auction::default();
                     auction.desc = name.clone();
                     auction.thumb = format!("http://egun.de/market/images/picture.gif");
 
-
-                    // get image                
+                    // get image
                     if let Some(img) = node
-                    .children().into_iter()
-                    .filter(|x| x.name() == Some("td"))
-                    .filter(|x| x.attr("align") == Some("center"))
-                    .flat_map(|x| x.descendants())
-                    .filter(|x| x.name() == Some("img"))
-                    .filter(|x| match x.attr("src") {
-                        Some(src) => src.contains("cache"),
-                        None => false
-                    }  )
-                    .map(|x| x.attr("src").unwrap()) // we just tested
-
-                    .collect::<Vec<_>>().get(0) {
-                        auction.thumb = format!("http://egun.de/market/{}", img);
-                    }
-
-
-                    // get price                
-                    if let Some(price) = node
-                    .children()
-                    .filter(|x| x.text().contains("EUR"))
-                    .flat_map(|x| x.children())
-                    .map(|x| parse_price(&x.text()))
-                    .flat_map(|x| x)
-                    .collect::<Vec<_>>().get(0) {
-                        auction.price = price.clone();
-                    }
-
-
-                    // get article url
-                    if let Some(url) = node
-                    .children().into_iter()
-                        .filter(|x| x.name() == Some("td"))
-                        .filter(|x| x.attr("align") == Some("LEFT"))
-                        .flat_map(|x| x.children())
-                        .filter(|x| x.name() == Some("a"))
-                        .map(|x| x.attr("href"))
-                        .filter_map(|x| x)
-                    .collect::<Vec<_>>().get(0) {
-                        auction.url = format!("http://egun.de/market/{}", url);
-                    }
-                
-                    // TODO: check if https://doc.rust-lang.org/std/time/struct.SystemTime.html works too
-                    if let Some(remaining) = parse_remaining(
-                        &node
                         .children()
+                        .into_iter()
+                        .filter(|x| x.name() == Some("td"))
                         .filter(|x| x.attr("align") == Some("center"))
-                        .filter(|x| x.attr("nowrap").is_some())
-                        .flat_map(|x| x.children())
-                        .filter(|x| !x.text().is_empty())
-                        .map(|x| x.text())
+                        .flat_map(|x| x.descendants())
+                        .filter(|x| x.name() == Some("img"))
+                        .filter(|x| match x.attr("src") {
+                            Some(src) => src.contains("cache"),
+                            None => false,
+                        })
+                        .map(|x| x.attr("src").unwrap()) // we just tested
                         .collect::<Vec<_>>()
-                        .join(" ")
-                    ){
-                        // dbg!(&t_remaining.children());
-
-                        let end_date = Utc::now().with_timezone(&Local) + remaining;
-                        auction.gcal = format!("http://www.google.com/calendar/event?action=TEMPLATE&dates={}&text={}&location=&details=", date_to_gcal(end_date), auction.desc);
-                        auction.remaining = remaining.num_seconds();
-                        auction.timestamp = end_date.timestamp();
-                        // println!("ENDS\t{:?}", date_to_gcal(remaining));
-
-                    }
-
-                    auctions.push(auction);
-                }
-        }
-
-    }
-    auctions
-}
-
-
-
-
-fn auctions_from_url(url: &str) -> HashMap<String, Auction> {
-    let mut auctions = HashMap::new();
-    // dbg!(&url);
-
-    if let Ok(mut resp) = reqwest::get(url) {
-
-        if !resp.status().is_success() {
-            println!("ERR {:?}", resp.text());
-            return auctions;
-        }
-
-        let text = resp.text().unwrap_or("".to_string());
-
-
-
-    for node in Document::from_read(text.as_bytes())
-        .unwrap()
-        // .find(Name("a"))
-        // .filter(|n| n.attr("href").is_some())
-        // .filter(|n| n.attr("href").unwrap().contains("item.php?id="))
-        .find(Name("tr"))
-        .filter(|x| x.attr("bgcolor").is_some())
-        .filter(|x| x.attr("align").is_none())
-
-        {
-
-            // Get auction name
-            if let Some(name) = node
-                .children().into_iter()
-                .filter(|x| x.name() == Some("td"))
-                .filter(|x| x.attr("align") == Some("LEFT"))
-                .flat_map(|x| x.children())
-                .filter(|x| x.name() == Some("a"))
-                .flat_map(|x| x.children())
-                .map(|x| x.text())
-                // .filter(|x| x.name() == Some("Text"))
-                .collect::<Vec<_>>().get(0) {
-
-                    // If we have the name, go on finding other details
-                    // instantiate mutable Auction
-                    let mut auction = Auction::default();
-                    auction.desc = name.clone();
-                    auction.thumb = format!("http://egun.de/market/images/picture.gif");
-
-
-                    // get image                
-                    if let Some(img) = node
-                    .children().into_iter()
-                    .filter(|x| x.name() == Some("td"))
-                    .filter(|x| x.attr("align") == Some("center"))
-                    .flat_map(|x| x.descendants())
-                    .filter(|x| x.name() == Some("img"))
-                    .filter(|x| match x.attr("src") {
-                        Some(src) => src.contains("cache"),
-                        None => false
-                    }  )
-                    .map(|x| x.attr("src").unwrap()) // we just tested
-
-                    .collect::<Vec<_>>().get(0) {
+                        .get(0)
+                    {
                         auction.thumb = format!("http://egun.de/market/{}", img);
                     }
 
-
-                    // get price                
+                    // get price
                     if let Some(price) = node
-                    .children()
-                    .filter(|x| x.text().contains("EUR"))
-                    .flat_map(|x| x.children())
-                    .map(|x| parse_price(&x.text()))
-                    .flat_map(|x| x)
-                    .collect::<Vec<_>>().get(0) {
+                        .children()
+                        .filter(|x| x.text().contains("EUR"))
+                        .flat_map(|x| x.children())
+                        .map(|x| parse_price(&x.text()))
+                        .flat_map(|x| x)
+                        .collect::<Vec<_>>()
+                        .get(0)
+                    {
                         auction.price = price.clone();
                     }
 
-
                     // get article url
                     if let Some(url) = node
-                    .children().into_iter()
+                        .children()
+                        .into_iter()
                         .filter(|x| x.name() == Some("td"))
                         .filter(|x| x.attr("align") == Some("LEFT"))
                         .flat_map(|x| x.children())
                         .filter(|x| x.name() == Some("a"))
                         .map(|x| x.attr("href"))
                         .filter_map(|x| x)
-                    .collect::<Vec<_>>().get(0) {
+                        .collect::<Vec<_>>()
+                        .get(0)
+                    {
                         auction.url = format!("http://egun.de/market/{}", url);
                     }
-                
                     // TODO: check if https://doc.rust-lang.org/std/time/struct.SystemTime.html works too
                     if let Some(remaining) = parse_remaining(
                         &node
-                        .children()
-                        .filter(|x| x.attr("align") == Some("center"))
-                        .filter(|x| x.attr("nowrap").is_some())
-                        .flat_map(|x| x.children())
-                        .filter(|x| !x.text().is_empty())
-                        .map(|x| x.text())
-                        .collect::<Vec<_>>()
-                        .join(" ")
-                    ){
+                            .children()
+                            .filter(|x| x.attr("align") == Some("center"))
+                            .filter(|x| x.attr("nowrap").is_some())
+                            .flat_map(|x| x.children())
+                            .filter(|x| !x.text().is_empty())
+                            .map(|x| x.text())
+                            .collect::<Vec<_>>()
+                            .join(" "),
+                    ) {
                         // dbg!(&t_remaining.children());
 
                         let end_date = Utc::now().with_timezone(&Local) + remaining;
@@ -392,13 +232,16 @@ fn auctions_from_url(url: &str) -> HashMap<String, Auction> {
                         auction.remaining = remaining.num_seconds();
                         auction.timestamp = end_date.timestamp();
                         // println!("ENDS\t{:?}", date_to_gcal(remaining));
-
                     }
 
                     auctions.insert(auction.url.clone(), auction);
                 }
+            }
         }
-
+        Err(e) => println!("{:?}", e),
     }
+
+    // if let Ok(mut resp) = client.get(url).send() {
+
     auctions
-}
+}

+ 10 - 11
src/server.rs

@@ -2,6 +2,7 @@ use rocket_contrib::serve::StaticFiles;
 use rocket_contrib::json::Json;
 use rocket::response::content;
 use std::sync::Mutex;
+use std::collections::HashMap;
 use super::ACCOUNTS;
 use super::query::*;
 
@@ -15,7 +16,6 @@ lazy_static! {
     static ref TASKS: Mutex<Vec<Task>> = Mutex::new(vec![]);
 }
 
-
 #[get("/")]
 fn index() -> &'static str {
     let accounts = ACCOUNTS.lock().unwrap();
@@ -24,12 +24,11 @@ fn index() -> &'static str {
     "Base url"
 }
 
-// receive json and make tasks from it (deserialize)
-#[post("/save", data = "<tasks>")] //<tasks> means that the function below will have a var called tasks
-fn save(tasks: Json<Vec<Task>>) -> &'static str {
-
-    let mut old_tasks = TASKS.lock().unwrap();
-    * old_tasks = tasks.to_vec();
+#[post("/save", data = "<account>")] //<tasks> means that the function below will have a var called tasks
+fn save(account: Json<HashMap<String, Account>>) -> &'static str {
+    dbg!(&account);
+    // let mut old_tasks = TASKS.lock().unwrap();
+    // * old_tasks = tasks.to_vec();
     "nice..."
 }
 
@@ -42,7 +41,7 @@ fn load(account: String) -> content::Json<String> {
     // serialize all tasks
     match guard.get(&account) {
         Some(accountdata) => {
-            let json_string = serde_json::to_string(&accountdata).unwrap();
+            let json_string = serde_json::to_string(&accountdata.queries).unwrap();
             content::Json(json_string)
         },
         None => content::Json("{}".to_string())
@@ -53,9 +52,9 @@ fn load(account: String) -> content::Json<String> {
 
 pub fn run() {
     rocket::ignite()
-    .mount("/", routes![index])
-    // .mount("/", routes![save])
+    // .mount("/", routes![index])
+    .mount("/", routes![save])
     .mount("/", routes![load])
-    // .mount("/static", StaticFiles::from("webapp"))
+    .mount("/", StaticFiles::from("webapp"))
     .launch();
 }

+ 144 - 0
webapp/app.js

@@ -0,0 +1,144 @@
+Number.prototype.currency = function() { return '$' + this.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,') };
+
+function seconds_to_time(s) {
+    var duration = moment.duration(s, 'seconds');
+    return duration.format("DD-hh:mm");
+}
+
+function ts_to_remaining(ts) {
+    return moment.unix(ts).fromNow();
+}
+
+
+function log() {
+    console.log('LOG >>>', arguments);
+}
+
+// function get_items() {
+//     fetch('load/' + app.account)
+//     .then(function(response) {
+//       return response.json();
+//     })
+//     .then(function(myJson) {
+//       app.queries = myJson;
+      
+//     });
+// }
+
+var app = new Vue({
+    el: '#app',
+    data: {
+        title: "egunner",
+        queries: {},
+        debug: false,
+        account: false,
+        new_account: "",
+        search_visible: true,
+        query: {
+            url: null,
+            search: null,
+            maxprice: null,
+            minprice: null,
+            restricted: null,
+        }
+    },
+
+    mounted: function () {
+        // hide splash
+        var splash = document.getElementById("splash");
+        splash.style.display = "none";
+        // unhide main container after mount
+        this.$el.style.display = "block";
+        this.account = window.location.hash.slice(1)
+        // get_items();
+        this.load_account();
+
+    },
+    computed: {
+        query_url: {
+            get(){
+
+                if (this.query.search || this.query.maxprice || this.query.minprice || this.query.restricted) {
+                    
+                    var query = "http://www.egun.de/market/list_items.php?mode=qry&plusdescr=off";
+
+                    if (this.query.search) {
+                        query += "&query=" + this.query.search.replace(" ", "+");
+                    }
+                    
+                    if (this.query.restricted == true) {
+                        query += "&ewb=1";
+                    }
+
+                    if (this.query.maxprice) {
+                        query += "&maxprice=" + this.query.maxprice;
+                    }
+                    
+                    if (this.query.minprice) {
+                        query += "&minprice=" + this.query.minprice;
+                    }
+
+                    return query;
+
+                } else {
+                    return this.query.url
+                }
+            },
+            set(newVal){
+                //this function will run whenever the input changes
+                this.query.url = newVal;
+            }
+        }
+    },
+    
+    methods: {
+        load_account: function () {
+            var self = this;
+            fetch('load/' + self.account)
+            .then(function(response) {
+                return response.json();
+            })
+            .then(function(myJson) {
+                self.queries = myJson;
+            });
+        
+        },
+        add_query: function () {
+            this.$set(this.queries, this.query_url, {});
+        },
+        save_query: function () {
+
+            let account = {}
+            account[this.account] = {queries: this.queries};
+
+            console.log(JSON.stringify(account) );
+            
+
+            //     this.account: {
+            //         queries: this.queries
+            //     }
+            // };
+            var self = this; 
+            console.log("Saving");
+            fetch("http://localhost:8000/save", {
+              method: "POST", 
+              body: JSON.stringify(account)
+            }).then(res => {
+              console.log("Request complete! response:", res);
+            });
+        }
+
+    },
+    
+    watch: {
+    },
+    
+});
+
+
+
+// window.setInterval(function(){
+//     // console.log('refresh');
+//     // get_items();
+//     app.load_account()
+// }, 5000);

+ 149 - 0
webapp/index.html

@@ -0,0 +1,149 @@
+<!doctype html>
+
+<head>
+  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+  <meta content="utf-8" http-equiv="encoding">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <meta name="mobile-web-app-capable" content="yes">
+  <meta name="theme-color" content="#dddddd">
+  <link rel="icon" sizes="128x128" href="icon.png">
+
+
+
+  <style>
+    * {
+      margin: 0;
+      padding: 0;
+    }
+
+    #splash {
+      background-color: rgb(61, 61, 61);
+      color: rgb(207, 207, 207);
+      padding: 10%;
+      margin: 0%;
+      font-size: 4em;
+      font-weight: bolder;
+      padding-bottom: 200%;
+      height: 100%;
+      font-family: Arial, Helvetica, sans-serif;
+    }
+  </style>
+</head>
+
+
+<body>
+  <div id="splash">
+    loading
+  </div>
+
+
+  <div id="app" style="display: none">
+    <nav>
+      <!-- <img class="brand" src="icon-white.png"> -->
+      <div class="logo">{{title}}</div>
+      <!-- <template v-for="item in items" v-if="item.price == null">
+        <div style="font-size:0.6em; margin-left:70%;">
+          Updated: {{item.desc}}
+        </div>
+      </template> -->
+      <div style="font-size:0.7em; margin-left:90%;">
+        {{account}}
+        <button @click="search_visible = !search_visible">Neue Suche</button>
+      </div>
+
+    </nav>
+
+
+    <div class="content">
+
+
+      <div class="panel" v-if="search_visible">
+          <h1>Neue Suche</h1>
+          <form>
+            <div class="form-group">
+              <label>Suchbegriff(e), durch Leerzeichen getrennt</label>
+              <input v-model="query.search">
+            </div>
+            <div class="form-group">
+              <label>Maximalpreis</label>
+              <input type="number" v-model="query.maxprice">
+            </div>
+            <div class="form-group">
+              <label>Mindestpreis</label>
+              <input type="number" v-model="query.minprice">
+            </div>
+            <div class="form-group">
+              <input type="checkbox" v-model="query.restricted">
+              <label>WBK noetig?</label>
+            </div>
+            <div class="form-group">
+              <label>Such-URL (Kann man alternativ von eGun-Suche kopieren)</label>
+              <input v-model="query_url">
+            </div>
+          </form>
+          <div>
+            <a v-if="query_url" v-bind:href="query_url" target="_">Suche testen</a>
+          </div>
+
+          <button @click="add_query()">Suche hinzufuegen</button>
+          <button @click="save_query()">Alle Suchen permanent speichern</button>
+
+          <div v-for="(query, url) in queries" style="font-size: 0.8em">
+            {{url}}
+          </div>
+
+      </div>
+
+
+
+
+
+      <div v-for="query in queries">
+
+        <div v-for="auction in query.auctions" v-if="auction.price != null && !auction.is_price_final">
+          <div class="grid-auto">
+            <div>
+              <img :src="auction.thumb" style="vertical-align:middle;">
+            </div>
+            <div><a :href="auction.url">{{auction.desc}}</a></div>
+            <div>
+              <div class="badge">{{auction.price}} EUR</div>
+            </div>
+            <div>{{ts_to_remaining(auction.timestamp)}}</div>
+            <a v-bind:href="auction.gcal">Add to cal</a>
+          </div>
+        </div>
+      </div>
+
+      <template>
+        <div class="panel" v-if="!account">
+          {{account}}
+          Hi! Du hast noch keinen Account oder es ist keiner geladen. Macht aber nichts, gib einfach Deinen ein oder
+          denke Dir einen aus.
+          <input v-model="new_account">
+          <button @click="account = new_account; window.location.hash = new_account">OK</button>
+        </div>
+
+      </template>
+
+
+    </div>
+  </div>
+</body>
+<link rel="stylesheet" href="https://rawgit.com/woelper/lilac/master/lilac.min.css">
+
+<!-- Moment.js library -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
+
+<!-- moment-duration-format plugin -->
+<script
+  src="https://cdnjs.cloudflare.com/ajax/libs/moment-duration-format/1.3.0/moment-duration-format.min.js"></script>
+
+<script defer src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.min.js"></script>
+<script defer src="app.js"></script>
+
+<style>
+  :root {
+    --main-color: #2e442f;
+  }
+</style>