diff options
author | Sebastian Reichel <sre@ring0.de> | 2012-10-02 01:05:51 +0200 |
---|---|---|
committer | Sebastian Reichel <sre@ring0.de> | 2012-10-02 01:05:51 +0200 |
commit | 186049b3ed33f025eeb87eb34c19a28e1d5ba70a (patch) | |
tree | 5d892564001404fe979e18eac0e65dfcad65ed5e /src | |
parent | 9713c98dbceb54d8d00c186ba8f41f3a5befcfd1 (diff) | |
download | serial-barcode-scanner-186049b3ed33f025eeb87eb34c19a28e1d5ba70a.tar.bz2 |
restructure code, switch from GTK to Web based UI
- move barcode generation scripts into generation/
- move code to src/
- remove database analysis from invoice/graph
- put database creation sql files into sql/
- remove glade builder file
- add new templates/ directory, which contains files
used by the Web-UI
Diffstat (limited to 'src')
-rw-r--r-- | src/db.vala | 740 | ||||
-rw-r--r-- | src/device.vala | 280 | ||||
-rw-r--r-- | src/graph-data.vala | 185 | ||||
-rw-r--r-- | src/main.vala | 113 | ||||
-rw-r--r-- | src/price.vapi | 9 | ||||
-rw-r--r-- | src/session.vala | 141 | ||||
-rw-r--r-- | src/template.vala | 103 | ||||
-rw-r--r-- | src/web.vala | 592 |
8 files changed, 2163 insertions, 0 deletions
diff --git a/src/db.vala b/src/db.vala new file mode 100644 index 0000000..43db62b --- /dev/null +++ b/src/db.vala @@ -0,0 +1,740 @@ +/* Copyright 2012, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public const int day_in_seconds = 24*60*60; + +public struct StockEntry { + public string id; + public string name; + public int amount; + public string memberprice; + public string guestprice; +} + +public struct PriceEntry { + public int64 valid_from; + public Price memberprice; + public Price guestprice; +} + +public struct RestockEntry { + public int64 timestamp; + public int amount; + public string price; +} + +public struct UserInfo { + public int id; + public string firstname; + public string lastname; + public string email; + public string gender; + public string street; + public int plz; + public string city; +} + +public struct UserAuth { + public int id; + public bool disabled; + public bool superuser; +} + +public struct Product { + public uint64 ean; + public string name; +} + +public struct InvoiceEntry { + public int64 timestamp; + Product product; + Price price; +} + +public struct StatsInfo { + public int count_articles; + public int count_users; + public Price stock_value; + public Price sales_total; + public Price profit_total; + public Price sales_today; + public Price profit_today; + public Price sales_this_month; + public Price profit_this_month; + public Price sales_per_day; + public Price profit_per_day; + public Price sales_per_month; + public Price profit_per_month; +} + +public class Database { + private class Statement { + private Sqlite.Statement stmt; + + public Statement(Sqlite.Database db, string query) { + int rc = db.prepare_v2(query, -1, out stmt); + + if(rc != Sqlite.OK) { + error("could not prepare statement: %s", query); + } + } + + public void reset() { + stmt.reset(); + } + + public int step() { + return stmt.step(); + } + + public int bind_int(int index, int value) { + return stmt.bind_int(index, value); + } + + public int bind_text(int index, string value) { + return stmt.bind_text(index, value); + } + + public int bind_int64(int index, int64 value) { + return stmt.bind_int64(index, value); + } + + public int column_int(int index) { + return stmt.column_int(index); + } + + public string column_text(int index) { + return stmt.column_text(index); + } + + public int64 column_int64(int index) { + return stmt.column_int64(index); + } + } + + private Sqlite.Database db; + private static Gee.HashMap<string,string> queries = new Gee.HashMap<string,string>(); + private static Gee.HashMap<string,Statement> statements = new Gee.HashMap<string,Statement>(); + + int32 user = 0; + bool logged_in = false; + + public Database(string file) { + int rc; + + rc = Sqlite.Database.open(file, out db); + if(rc != Sqlite.OK) { + error("could not open database!"); + } + + /* setup queries */ + queries["product_name"] = "SELECT name FROM products WHERE id = ?"; + queries["product_amount"] = "SELECT amount FROM products WHERE id = ?"; + queries["products"] = "SELECT id, name, amount FROM products ORDER BY name"; + queries["purchase"] = "INSERT INTO sells ('user', 'product', 'timestamp') VALUES (?, ?, ?)"; + queries["last_purchase"] = "SELECT product FROM sells WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; + queries["undo"] = "DELETE FROM sells WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; + queries["product_create"] = "INSERT INTO products ('id', 'name', 'amount') VALUES (?, ?, ?)"; + queries["price_create"] = "INSERT INTO prices ('product', 'valid_from', 'memberprice', 'guestprice') VALUES (?, ?, ?, ?)"; + queries["stock"] = "INSERT INTO restock ('user', 'product', 'amount', 'price', 'timestamp') VALUES (?, ?, ?, ?, ?)"; + queries["price"] = "SELECT memberprice, guestprice FROM prices WHERE product = ? AND valid_from <= ? ORDER BY valid_from DESC LIMIT 1"; + queries["prices"] = "SELECT valid_from, memberprice, guestprice FROM prices WHERE product = ? ORDER BY valid_from ASC;"; + queries["restocks"] = "SELECT timestamp, amount, price FROM restock WHERE product = ? ORDER BY timestamp ASC;"; + queries["profit_complex"] = "SELECT SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) FROM sells purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; + queries["sales_complex"] = "SELECT SUM(memberprice) FROM sells purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; + queries["stock_status"] = "SELECT id, name, amount, memberprice, guestprice FROM products, prices WHERE products.id = prices.product AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = products.id ORDER BY valid_from DESC LIMIT 1) ORDER BY name"; + queries["stock_amount"] = "SELECT timestamp, amount FROM restock WHERE product = ? UNION ALL SELECT timestamp, -1 AS amount FROM sells WHERE product = ? ORDER BY timestamp DESC"; + queries["session_set"] = "UPDATE authentication SET session=? WHERE user = ?"; + queries["session_get"] = "SELECT user FROM authentication WHERE session = ?"; + queries["username"] = "SELECT firstname, lastname FROM users WHERE id = ?"; + queries["password_get"] = "SELECT password FROM authentication WHERE user = ?"; + queries["userinfo"] = "SELECT firstname, lastname, email, gender, street, plz, city FROM users WHERE id = ?"; + queries["userauth"] = "SELECT disabled, superuser FROM authentication WHERE user = ?"; + queries["profit_by_product"] = "SELECT name, SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) AS price FROM sells purch, prices, products WHERE purch.product = products.id AND purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1) GROUP BY name ORDER BY price;"; + queries["invoice"] = "SELECT timestamp, productid, productname, price FROM invoice WHERE user = ? AND timestamp >= ? AND timestamp < ?;"; + queries["purchase_first"] = "SELECT timestamp FROM sells WHERE user = ? ORDER BY timestamp ASC LIMIT 1"; + queries["purchase_last"] = "SELECT timestamp FROM sells WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; + queries["count_articles"] = "SELECT COUNT(*) FROM products"; + queries["count_users"] = "SELECT COUNT(*) FROM users"; + queries["stock_value"] = "SELECT SUM(amount * price) FROM products INNER JOIN purchaseprices ON products.id = purchaseprices.product"; + queries["total_sales"] = "SELECT SUM(price) FROM invoice WHERE user >= 0 AND timestamp >= ?"; + queries["total_profit"] = "SELECT SUM(price - (SELECT price FROM purchaseprices WHERE product = productid)) FROM invoice WHERE user >= 0 AND timestamp >= ?"; + + /* compile queries into statements */ + foreach(var entry in queries.entries) { + statements[entry.key] = new Statement(db, entry.value); + } + } + + public bool login(int32 id) { + this.user = id; + this.logged_in = true; + return true; + } + + public bool logout() { + this.user = 0; + this.logged_in = false; + return true; + } + + public Gee.HashMap<string,string> get_products() { + var result = new Gee.HashMap<string,string>(null, null); + statements["products"].reset(); + + while(statements["products"].step() == Sqlite.ROW) + result[statements["products"].column_text(0)] = statements["products"].column_text(1); + + return result; + } + + public stock get_stats_stock() { + var result = new stock(); + var now = time_t(); + + /* init products */ + statements["products"].reset(); + while(statements["products"].step() == Sqlite.ROW) { + var id = uint64.parse(statements["products"].column_text(0)); + var name = statements["products"].column_text(1); + int amount = int.parse(statements["products"].column_text(2)); + var product = new stock.product(id, name); + result.add(product); + product.add(now, amount); + + statements["stock_amount"].reset(); + statements["stock_amount"].bind_text(1, "%llu".printf(id)); + statements["stock_amount"].bind_text(2, "%llu".printf(id)); + + while(statements["stock_amount"].step() == Sqlite.ROW) { + var timestamp = uint64.parse(statements["stock_amount"].column_text(0)); + var diff = statements["stock_amount"].column_int(1); + product.add(timestamp+1, amount); + amount -= diff; + product.add(timestamp, amount); + } + } + + return result; + } + + public profit_per_product get_stats_profit_per_products() { + var result = new profit_per_product(); + + statements["profit_by_product"].reset(); + statements["profit_by_product"].bind_int(1, 0); + statements["profit_by_product"].bind_text(2, "99999999999999"); + + while(statements["profit_by_product"].step() == Sqlite.ROW) { + var name = statements["profit_by_product"].column_text(0); + var profit = statements["profit_by_product"].column_int(1); + result.add(name, profit); + } + + return result; + } + + public profit_per_weekday get_stats_profit_per_weekday() { + var result = new profit_per_weekday(); + + var now = new DateTime.now_utc(); + var today = new DateTime.utc(now.get_year(), now.get_month(), now.get_day_of_month(), 8, 0, 0); + var tomorrow = today.add_days(1); + var weekday = tomorrow.get_day_of_week()-1; + + var to = tomorrow.to_unix(); + var from = to - day_in_seconds; + + var weeks = 8; + + for(int i=0; i<weeks*7; i++) { + statements["profit_complex"].reset(); + statements["profit_complex"].bind_text(1, "%llu".printf(from)); + statements["profit_complex"].bind_text(2, "%llu".printf(to)); + + if(statements["profit_complex"].step() == Sqlite.ROW) + result.day[weekday] += statements["profit_complex"].column_int(0); + + from-=day_in_seconds; + to-=day_in_seconds; + weekday = (weekday + 1) % 7; + } + + for(int i=0; i<7; i++) + result.day[i] /= weeks; + + return result; + } + + public profit_per_day get_stats_profit_per_day() { + var result = new profit_per_day(); + var to = time_t(); + var from = to - day_in_seconds; + + /* 8 weeks */ + for(int i=0; i<8*7; i++) { + statements["profit_complex"].reset(); + statements["profit_complex"].bind_text(1, "%llu".printf(from)); + statements["profit_complex"].bind_text(2, "%llu".printf(to)); + statements["sales_complex"].reset(); + statements["sales_complex"].bind_text(1, "%llu".printf(from)); + statements["sales_complex"].bind_text(2, "%llu".printf(to)); + + + if(statements["profit_complex"].step() == Sqlite.ROW) + result.add_profit(from, statements["profit_complex"].column_int(0)); + if(statements["sales_complex"].step() == Sqlite.ROW) + result.add_sales(from, statements["sales_complex"].column_int(0)); + + from-=day_in_seconds; + to-=day_in_seconds; + } + + return result; + } + + public Gee.List<StockEntry?> get_stock() { + var result = new Gee.ArrayList<StockEntry?>(); + statements["stock_status"].reset(); + + while(statements["stock_status"].step() == Sqlite.ROW) { + StockEntry entry = { + statements["stock_status"].column_text(0), + statements["stock_status"].column_text(1), + statements["stock_status"].column_int(2), + null, + null + }; + + Price mprice = statements["stock_status"].column_int(3); + Price gprice = statements["stock_status"].column_int(4); + + entry.memberprice = @"$mprice"; + entry.guestprice = @"$gprice"; + + result.add(entry); + } + + return result; + } + + public Gee.List<PriceEntry?> get_prices(uint64 product) { + var result = new Gee.ArrayList<PriceEntry?>(); + statements["prices"].reset(); + statements["prices"].bind_text(1, "%llu".printf(product)); + + while(statements["prices"].step() == Sqlite.ROW) { + PriceEntry entry = { + statements["prices"].column_int64(0), + statements["prices"].column_int(1), + statements["prices"].column_int(2) + }; + + result.add(entry); + } + + return result; + } + + public Gee.List<RestockEntry?> get_restocks(uint64 product) { + var result = new Gee.ArrayList<RestockEntry?>(); + statements["restocks"].reset(); + statements["restocks"].bind_text(1, "%llu".printf(product)); + + while(statements["restocks"].step() == Sqlite.ROW) { + RestockEntry entry = { + statements["restocks"].column_int64(0), + statements["restocks"].column_int(1) + }; + + Price p = statements["restocks"].column_int(2); + entry.price = @"$p"; + + result.add(entry); + } + + return result; + } + + public bool buy(uint64 article) { + if(is_logged_in()) { + int rc = 0; + int64 timestamp = (new DateTime.now_utc()).to_unix(); + + statements["purchase"].reset(); + statements["purchase"].bind_text(1, "%d".printf(user)); + statements["purchase"].bind_text(2, "%llu".printf(article)); + statements["purchase"].bind_text(3, "%llu".printf(timestamp)); + + rc = statements["purchase"].step(); + if(rc != Sqlite.DONE) + error("[interner Fehler: %d]".printf(rc)); + + return true; + } else { + return false; + } + } + + public string get_product_name(uint64 article) { + statements["product_name"].reset(); + statements["product_name"].bind_text(1, "%llu".printf(article)); + + int rc = statements["product_name"].step(); + + switch(rc) { + case Sqlite.ROW: + return statements["product_name"].column_text(0); + case Sqlite.DONE: + return "unbekanntes Produkt: %llu".printf(article); + default: + return "[interner Fehler: %d]".printf(rc); + } + } + + public int get_product_amount(uint64 article) { + statements["product_amount"].reset(); + statements["product_amount"].bind_text(1, "%llu".printf(article)); + + int rc = statements["product_amount"].step(); + + switch(rc) { + case Sqlite.ROW: + return statements["product_amount"].column_int(0); + case Sqlite.DONE: + warning("unbekanntes Produkt: %llu".printf(article)); + return -1; + default: + warning("[interner Fehler: %d]".printf(rc)); + return -1; + } + } + + public int get_product_price(uint64 article) { + int64 timestamp = (new DateTime.now_utc()).to_unix(); + bool member = user != 0; + + statements["price"].reset(); + statements["price"].bind_text(1, "%llu".printf(article)); + statements["price"].bind_text(2, "%lld".printf(timestamp)); + + int rc = statements["price"].step(); + + switch(rc) { + case Sqlite.ROW: + if(member) + return statements["price"].column_int(0); + else + return statements["price"].column_int(1); + case Sqlite.DONE: + write_to_log("unbekanntes Produkt: %llu\n", article); + return 0; + default: + write_to_log("[interner Fehler: %d]\n", rc); + return 0; + } + } + + public bool undo() { + if(is_logged_in()) { + uint64 pid = 0; + int rc = 0; + + statements["last_purchase"].reset(); + statements["last_purchase"].bind_text(1, "%d".printf(user)); + + rc = statements["last_purchase"].step(); + switch(rc) { + case Sqlite.ROW: + pid = uint64.parse(statements["last_purchase"].column_text(0)); + write_to_log("remove purchase of %llu", pid); + break; + case Sqlite.DONE: + write_to_log("undo not possible without purchases"); + return false; + default: + error("[interner Fehler: %d]".printf(rc)); + } + + statements["undo"].reset(); + statements["undo"].bind_text(1, "%d".printf(user)); + + rc = statements["undo"].step(); + if(rc != Sqlite.DONE) + error("[interner Fehler: %d]".printf(rc)); + + return true; + } + + return false; + } + + public bool restock(int user, uint64 product, uint amount, uint price) { + if(user > 0) { + int rc = 0; + int64 timestamp = (new DateTime.now_utc()).to_unix(); + + statements["stock"].reset(); + statements["stock"].bind_int(1, user); + statements["stock"].bind_text(2, @"$product"); + statements["stock"].bind_text(3, @"$amount"); + statements["stock"].bind_text(4, @"$price"); + statements["stock"].bind_int64(5, timestamp); + + rc = statements["stock"].step(); + if(rc != Sqlite.DONE) + error("[interner Fehler: %d]".printf(rc)); + + return true; + } + + return false; + } + + public bool new_product(uint64 id, string name, int memberprice, int guestprice) { + statements["product_create"].reset(); + statements["product_create"].bind_text(1, @"$id"); + statements["product_create"].bind_text(2, name); + statements["product_create"].bind_int(3, 0); + int rc = statements["product_create"].step(); + + if(rc != Sqlite.DONE) { + warning("[interner Fehler: %d]".printf(rc)); + return false; + } + + return new_price(id, 0, memberprice, guestprice); + } + + public bool new_price(uint64 product, int64 timestamp, int memberprice, int guestprice) { + statements["price_create"].reset(); + statements["price_create"].bind_text(1, @"$product"); + statements["price_create"].bind_int64(2, timestamp); + statements["price_create"].bind_int(3, memberprice); + statements["price_create"].bind_int(4, guestprice); + int rc = statements["price_create"].step(); + + if(rc != Sqlite.DONE) { + warning("[interner Fehler: %d]".printf(rc)); + return false; + } + + return true; + } + + public bool is_logged_in() { + return this.logged_in; + } + + public bool check_user_password(int32 user, string password) { + statements["password_get"].reset(); + statements["password_get"].bind_int(1, user); + + if(statements["password_get"].step() == Sqlite.ROW) { + var pwhash_db = statements["password_get"].column_text(0); + var pwhash_user = Checksum.compute_for_string(ChecksumType.SHA256, password); + + stdout.printf("tried login: %s\n", pwhash_user); + + return pwhash_db == pwhash_user; + } else { + return false; + } + } + + public void set_sessionid(int user, string sessionid) { + statements["session_set"].reset(); + statements["session_set"].bind_text(1, sessionid); + statements["session_set"].bind_int(2, user); + + int rc = statements["session_set"].step(); + if(rc != Sqlite.DONE) + error("[interner Fehler: %d]".printf(rc)); + } + + public int get_user_by_sessionid(string sessionid) throws WebSessionError { + statements["session_get"].reset(); + statements["session_get"].bind_text(1, sessionid); + + if(statements["session_get"].step() == Sqlite.ROW) { + return statements["session_get"].column_int(0); + } else { + throw new WebSessionError.SESSION_NOT_FOUND("No such session available in database!"); + } + } + + public UserInfo get_user_info(int user) { + var result = UserInfo(); + statements["userinfo"].reset(); + statements["userinfo"].bind_int(1, user); + + if(statements["userinfo"].step() == Sqlite.ROW) { + result.id = user; + result.firstname = statements["userinfo"].column_text(0); + result.lastname = statements["userinfo"].column_text(1); + result.email = statements["userinfo"].column_text(2); + result.gender = statements["userinfo"].column_text(3); + result.street = statements["userinfo"].column_text(4); + result.plz = statements["userinfo"].column_int(5); + result.city = statements["userinfo"].column_text(6); + } + + return result; + } + + public UserAuth get_user_auth(int user) { + var result = UserAuth(); + result.id = user; + + statements["userauth"].reset(); + statements["userauth"].bind_int(1, user); + if(statements["userauth"].step() == Sqlite.ROW) { + result.disabled = statements["userauth"].column_int(0) == 1; + result.superuser = statements["userauth"].column_int(1) == 1; + } + + return result; + } + + public string get_username(int user) throws WebSessionError { + statements["username"].reset(); + statements["username"].bind_int(1, user); + + if(statements["username"].step() == Sqlite.ROW) { + return statements["username"].column_text(0)+" "+statements["username"].column_text(1); + } else { + throw new WebSessionError.USER_NOT_FOUND("No such user available in database!"); + } + } + + public Gee.List<InvoiceEntry?> get_invoice(int user, int64 from=0, int64 to=-1) { + var result = new Gee.ArrayList<InvoiceEntry?>(); + + if(to == -1) { + to = time_t(); + } + + statements["invoice"].reset(); + statements["invoice"].bind_int(1, user); + statements["invoice"].bind_int64(2, from); + statements["invoice"].bind_int64(3, to); + + while(statements["invoice"].step() == Sqlite.ROW) { + InvoiceEntry entry = {}; + entry.timestamp = statements["invoice"].column_int64(0); + entry.product.ean = uint64.parse(statements["invoice"].column_text(1)); + entry.product.name = statements["invoice"].column_text(2); + entry.price = statements["invoice"].column_int(3); + result.add(entry); + } + + return result; + } + + public DateTime get_first_purchase(int user) { + statements["purchase_first"].reset(); + statements["purchase_first"].bind_int(1, user); + + if(statements["purchase_first"].step() == Sqlite.ROW) + return new DateTime.from_unix_utc(statements["purchase_first"].column_int64(0)); + else + return new DateTime.from_unix_utc(0); + } + + public DateTime get_last_purchase(int user) { + statements["purchase_last"].reset(); + statements["purchase_last"].bind_int(1, user); + + if(statements["purchase_last"].step() == Sqlite.ROW) + return new DateTime.from_unix_utc(statements["purchase_last"].column_int64(0)); + else + return new DateTime.from_unix_utc(0); + } + + public StatsInfo get_stats_info() { + var result = StatsInfo(); + + DateTime now = new DateTime.now_local(); + DateTime today = new DateTime.local(now.get_year(), now.get_month(), now.get_hour() < 8 ? now.get_day_of_month()-1 : now.get_day_of_month(), 8, 0, 0); + DateTime month = new DateTime.local(now.get_year(), now.get_day_of_month() < 16 ? now.get_month()-1 : now.get_month(), 16, 8, 0, 0); + + DateTime last4weeks = now.add_days(-28); + DateTime last4months = now.add_months(-4); + + statements["count_articles"].reset(); + if(statements["count_articles"].step() == Sqlite.ROW) + result.count_articles = statements["count_articles"].column_int(0); + + statements["count_users"].reset(); + if(statements["count_users"].step() == Sqlite.ROW) + result.count_users = statements["count_users"].column_int(0); + + statements["stock_value"].reset(); + if(statements["stock_value"].step() == Sqlite.ROW) + result.stock_value = statements["stock_value"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, 0); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_total = statements["total_sales"].column_int(0); + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, 0); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_total = statements["total_profit"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, today.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_today = statements["total_sales"].column_int(0); + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, today.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_today = statements["total_profit"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, month.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_this_month = statements["total_sales"].column_int(0); + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, month.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_this_month = statements["total_profit"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, last4weeks.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_per_day = statements["total_sales"].column_int(0) / 28; + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, last4weeks.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_per_day = statements["total_profit"].column_int(0) / 28; + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, last4months.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_per_month = statements["total_sales"].column_int(0) / 4; + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, last4months.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_per_month = statements["total_profit"].column_int(0) / 4; + + return result; + } +} diff --git a/src/device.vala b/src/device.vala new file mode 100644 index 0000000..5d7fc09 --- /dev/null +++ b/src/device.vala @@ -0,0 +1,280 @@ +/* Copyright 2012, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class Device { + private Posix.termios newtio; + private Posix.termios restoretio; + public int fd=-1; + private IOChannel io_read; + public int byterate; + private File lockfile; + + public signal void received_barcode(string barcode); + + public Device(string device, int rate, int bits, int stopbits) { + Posix.speed_t baudrate = Posix.B9600; + + try { + /* check lock file */ + lockfile = File.new_for_path("/var/lock/LCK.." + device.replace("/dev/", "")); + if(lockfile.query_exists()) { + int pid = -1; + uint8[] data; + if(lockfile.load_contents(null, out data, null)) { + pid = int.parse((string) data); + } else { + error("Can't read lock file!\n"); + } + + if(Posix.kill(pid, 0) == 0) { + error("serial device is locked!\n"); + } + } + + var pid = "%d\n".printf(Posix.getpid()); + lockfile.replace_contents(pid.data, null, false, FileCreateFlags.NONE, null); + + fd = Posix.open(device, Posix.O_RDWR /*| Posix.O_NONBLOCK*/); + + if(fd < 0) { + fd = -1; + lockfile.delete(); + error("Could not open device!\n"); + } + + } catch(Error e) { + error("Could not create lock file: %s!\n", e.message); + } + + + Posix.tcflush(fd, Posix.TCIOFLUSH); + + Posix.tcgetattr(fd, out restoretio); + + /* apply settings */ + switch(rate) { + case 300: + baudrate = Posix.B300; + break; + case 600: + baudrate = Posix.B600; + break; + case 1200: + baudrate = Posix.B1200; + break; + case 2400: + baudrate = Posix.B2400; + break; + case 4800: + baudrate = Posix.B4800; + break; + case 9600: + baudrate = Posix.B9600; + break; + case 19200: + baudrate = Posix.B19200; + break; + case 38400: + baudrate = Posix.B38400; + break; + case 57600: + baudrate = Posix.B57600; + break; + case 115200: + baudrate = Posix.B115200; + break; + case 230400: + baudrate = Posix.B230400; + break; + default: + /* not supported */ + rate = 9600; + break; + } + + Posix.cfsetospeed(ref newtio, baudrate); + Posix.cfsetispeed(ref newtio, baudrate); + + switch(bits) { + case 5: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS5; + break; + case 6: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS6; + break; + case 7: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS7; + break; + case 8: + default: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS8; + break; + } + + newtio.c_cflag |= Posix.CLOCAL | Posix.CREAD; + + newtio.c_cflag &= ~(Posix.PARENB | Posix.PARODD); + + /* TODO: parity */ + + newtio.c_cflag &= ~Linux.Termios.CRTSCTS; + + if(stopbits == 2) + newtio.c_cflag |= Posix.CSTOPB; + else + newtio.c_cflag &= ~Posix.CSTOPB; + + newtio.c_iflag = Posix.IGNBRK; + + newtio.c_lflag = 0; + newtio.c_oflag = 0; + + newtio.c_cc[Posix.VTIME]=1; + newtio.c_cc[Posix.VMIN]=1; + + newtio.c_lflag &= ~(Posix.ECHONL|Posix.NOFLSH); + + int mcs=0; + Posix.ioctl(fd, Linux.Termios.TIOCMGET, out mcs); + mcs |= Linux.Termios.TIOCM_RTS; + Posix.ioctl(fd, Linux.Termios.TIOCMSET, out mcs); + + Posix.tcsetattr(fd, Posix.TCSANOW, newtio); + + this.byterate = rate/bits; + + try { + io_read = new IOChannel.unix_new(fd); + io_read.set_line_term("\r\n", 2); + if(io_read.set_encoding(null) != IOStatus.NORMAL) + error("Failed to set encoding"); + if(!(io_read.add_watch(IOCondition.IN | IOCondition.HUP, device_read) != 0)) { + error("Could not bind IOChannel"); + } + } catch(IOChannelError e) { + error("IOChannel: %s", e.message); + } + } + + ~Device() { + /* restore old tty config */ + Posix.tcsetattr(fd, Posix.TCSANOW, restoretio); + + /* close file */ + Posix.close(fd); + + /* remove lock */ + lockfile.delete(); + } + + private bool device_read(IOChannel gio, IOCondition cond) { + IOStatus ret; + string msg; + size_t len, term_char; + + if((cond & IOCondition.HUP) == IOCondition.HUP) + error("Lost device"); + + try { + ret = gio.read_line(out msg, out len, out term_char); + msg = msg[0:(long)term_char]; + + if(msg.has_prefix("USER ") || msg.has_prefix("STOCK") || msg.has_prefix("AMOUNT ")) { + if(!check_code39_checksum(msg)) + received_barcode("SCANNER RETURNED INCORRECT DATA"); + else {/* remove checksum */ + msg = msg[0:-1]; + received_barcode(msg); + } + } + else + received_barcode(msg); + } + catch(IOChannelError e) { + stderr.printf("IOChannel Error: %s", e.message); + return false; + } + catch(ConvertError e) { + stderr.printf("Convert Error: %s", e.message); + return false; + } + return true; + } + + private ssize_t write(void *buf, size_t count) { + ssize_t size = Posix.write(fd, buf, count); + return size; + } + + private bool check_code39_checksum(string data) { + int result = 0; + + for(int i = 0; i<data.length-1; i++) { + if(data[i] >= '0' && data[i] <= '9') + result += data[i] - '0'; + else if(data[i] >= 'A' && data[i] <= 'Z') + result += data[i] - 'A' + 10; + else + switch(data[i]) { + case '-': + result += 36; break; + case '.': + result += 37; break; + case ' ': + result += 38; break; + case '$': + result += 39; break; + case '/': + result += 40; break; + case '+': + result += 41; break; + case '%': + result += 42; break; + default: + /* invalid character */ + return false; + } + + result %= 43; + } + + if(result < 10) + result = result + '0'; + else if(result < 36) + result = result - 10 + 'A'; + else + switch(result) { + case 36: result = '-'; break; + case 37: result = '.'; break; + case 38: result = ' '; break; + case 39: result = '$'; break; + case 40: result = '/'; break; + case 41: result = '+'; break; + case 42: result = '%'; break; + } + + return (data[data.length-1] == result); + } + + /** + * @param duration duration of the blink in 0.1 seconds + */ + public void blink(uint duration) { + uint size = byterate/10 * duration; + var msg = new uint8[size]; + Posix.memset(msg, 0xFF, msg.length); + this.write(msg, msg.length); + } +} diff --git a/src/graph-data.vala b/src/graph-data.vala new file mode 100644 index 0000000..045c6ad --- /dev/null +++ b/src/graph-data.vala @@ -0,0 +1,185 @@ +/* Copyright 2012, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class stock { + public class product { + public struct subitem { + public uint64 timestamp; + public int amount; + + public string json { + owned get { + return "[%llu, %d]".printf(timestamp*1000, amount); + } + } + } + + public uint64 id; + public string name; + subitem[] data; + + public product(uint64 id, string name) { + this.id = id; + this.name = name; + this.data = null; + } + + public void add(uint64 timestamp, int amount) { + subitem newitem = {timestamp, amount}; + this.data += newitem; + } + + public string json { + owned get { + var data_array = "["; + if(data != null) { + for(int i=0; i < data.length-1; i++) + data_array += data[i].json + ", "; + data_array += data[data.length-1].json; + } + data_array += "]"; + + return "{label: \"%s\", data: %s}".printf(name, data_array); + } + } + } + + Gee.HashMap<uint64?,product> data; + + public stock() { + data = new Gee.HashMap<uint64?,product>(); + } + + public void add(product i) { + data[i.id] = i; + } + + public product get_product(uint64 id) { + return data[id]; + } + + public string json { + owned get { + var result = "{"; + foreach(var entry in data.entries) { + uint64? id = entry.key; + string pdata = entry.value.json; + result += @"\"product_$id\": $pdata, "; + } + result = result.substring(0, result.length-2); + result += "}"; + return result; + } + } +} + +public class profit_per_day { + public struct subitem { + public uint64 timestamp; + public Price amount; + + public string json { + owned get { + return @"[$(timestamp*1000), $amount]"; + } + } + } + + private subitem[] profit_data; + private subitem[] sales_data; + + public void add_profit(uint64 timestamp, int amount) { + subitem newitem = {timestamp, amount}; + this.profit_data += newitem; + } + + public void add_sales(uint64 timestamp, int amount) { + subitem newitem = {timestamp, amount}; + this.sales_data += newitem; + } + + public string json { + owned get { + var result = "{\"profit\": {label: \"Profit\", data: ["; + if(profit_data != null) { + for(int i=0; i < profit_data.length-1; i++) + result += profit_data[i].json + ", "; + result += profit_data[profit_data.length-1].json; + } + result += "]},\"sales\": {label: \"Sales\", data:["; + if(sales_data != null) { + for(int i=0; i < sales_data.length-1; i++) + result += sales_data[i].json + ", "; + result += sales_data[sales_data.length-1].json; + } + result += "]}}"; + + return result; + } + } +} + +public class profit_per_weekday { + public Price[] day = new Price[7]; + + public profit_per_weekday() { + for(int i=0; i<day.length; i++) + day[i] = 0; + } + + public string json { + owned get { + var result = "["; + result += @"{ label: \"Monday\", data: $(day[0]) },"; + result += @"{ label: \"Tuesday\", data: $(day[1]) },"; + result += @"{ label: \"Wednesday\", data: $(day[2]) },"; + result += @"{ label: \"Thursday\", data: $(day[3]) },"; + result += @"{ label: \"Friday\", data: $(day[4]) },"; + result += @"{ label: \"Saturday\", data: $(day[5]) },"; + result += @"{ label: \"Sunday\", data: $(day[6]) }"; + result += "]"; + return result; + } + } +} + +public class profit_per_product { + Gee.HashMap<string,int> data; + + public profit_per_product() { + data = new Gee.HashMap<string,int>(); + } + + public void add(string product, int amount) { + if(data.has_key(product)) + data[product] = data[product] + amount; + else + data[product] = amount; + } + + public string json { + owned get { + var result = "["; + foreach(var e in data.entries) { + result += @"{ label: \"$(e.key)\", data: $((Price) e.value) },"; + } + if(result.length > 1) { + result = result.substring(0, result.length-1); + } + result += "]"; + return result; + } + } +} diff --git a/src/main.vala b/src/main.vala new file mode 100644 index 0000000..74736d2 --- /dev/null +++ b/src/main.vala @@ -0,0 +1,113 @@ +/* Copyright 2012, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public Device dev; +public Database db; + +public static int main(string[] args) { + if(args.length < 2) { + stderr.printf("%s <device>\n", args[0]); + return 1; + } + + dev = new Device(args[1], 9600, 8, 1); + db = new Database("shop.db"); + + dev.received_barcode.connect((data) => { + if(interpret(data)) + dev.blink(10); + }); + + write_to_log("KtT Shop System has been started"); + + /* attach webserver to mainloop */ + new WebServer(); + + /* run mainloop */ + new MainLoop().run(); + + /* call destructors */ + dev = null; + db = null; + + return 0; +} + +public void write_to_log(string format, ...) { + var arguments = va_list(); + var message = format.vprintf(arguments); + + stdout.printf(message + "\n"); +} + +public static bool interpret(string data) { + if(data.has_prefix("USER ")) { + string str_id = data.substring(5); + int32 id = int.parse(str_id); + + /* check if data has valid format */ + if(data != "USER %d".printf(id)) { + write_to_log("ungültige Benutzernummer: %s", data); + return false; + } + + if(db.is_logged_in()) { + write_to_log("Last User forgot to logout!"); + db.logout(); + } + + write_to_log("Login: %d", id); + return db.login(id); + } else if(data == "GUEST") { + if(db.is_logged_in()) { + write_to_log("Last User forgot to logout!"); + db.logout(); + } + + write_to_log("Login: Guest"); + return db.login(0); + } else if(data == "UNDO") { + if(!db.is_logged_in()) { + write_to_log("Can't undo if not logged in!"); + return false; + } else { + write_to_log("Undo last purchase!"); + return db.undo(); + } + } else if(data == "LOGOUT") { + if(db.is_logged_in()) { + write_to_log("Logout!"); + return db.logout(); + } + + return false; + } else { + uint64 id = uint64.parse(data); + + /* check if data has valid format */ + if(data != "%llu".printf(id)) { + write_to_log("ungültiges Produkt: %s", data); + return false; + } + + if(db.buy(id)) { + write_to_log("gekaufter Artikel: %s (%d,%02d €)", db.get_product_name(id), db.get_product_price(id)/100, db.get_product_price(id) % 100); + return true; + } else { + write_to_log("Kauf fehlgeschlagen!"); + return false; + } + } +} diff --git a/src/price.vapi b/src/price.vapi new file mode 100644 index 0000000..b60e2a6 --- /dev/null +++ b/src/price.vapi @@ -0,0 +1,9 @@ +[SimpleType] +[GIR (name = "gint")] +[CCode (cname = "gint", cheader_filename = "glib.h", type_id = "G_TYPE_INT", marshaller_type_name = "INT", get_value_function = "g_value_get_int", set_value_function = "g_value_set_int", default_value = "0", type_signature = "i")] +[IntegerType (rank = 6)] +public struct Price : int { + public new string to_string() { + return "%d.%02d".printf(this / 100, this % 100); + } +} diff --git a/src/session.vala b/src/session.vala new file mode 100644 index 0000000..da1a430 --- /dev/null +++ b/src/session.vala @@ -0,0 +1,141 @@ +/* Copyright 2012, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public errordomain WebSessionError { + SESSION_NOT_FOUND, + USER_NOT_FOUND +} + +public class WebSession { + public int user { + get; + private set; + default = 0; + } + public string name { + get; + private set; + default = "Guest"; + } + public bool failed { + get; + private set; + default = false; + } + public bool logged_in { + get; + private set; + default = false; + } + public bool superuser { + get; + private set; + default = false; + } + public bool disabled { + get; + private set; + default = false; + } + + private string generate_session_id(int user) { + const string charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; + string result = ""; + + Random.set_seed((uint32) time_t() + (uint32) Posix.getpid() + (uint32) user); + + for(int i=0; i<19; i++) { + int character_position = Random.int_range(0,charset.length); + string character = charset[character_position].to_string(); + result += character; + } + + /* TODO: make sure, that session id is unique */ + + return result; + } + + private void setup_auth(int user) { + var auth = db.get_user_auth(user); + this.disabled = auth.disabled; + this.superuser = auth.superuser; + this.logged_in = true; + } + + public void logout() { + if(logged_in) { + db.set_sessionid(user, ""); + logged_in = false; + } + } + + public WebSession(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) { + var cookies = Soup.cookies_from_request(msg); + + /* Check for existing session */ + foreach(var cookie in cookies) { + if(cookie.name == "session") { + var sessionid = cookie.value; + + try { + user = db.get_user_by_sessionid(sessionid); + name = db.get_username(user); + setup_auth(user); + return; + } catch(WebSessionError e) { + /* invalid session, ignore */ + } + } + } + + /* check for login query */ + if(query == null || !query.contains("user") || !query.contains("password")) + return; + + /* get credentials */ + var userid = int.parse(query["user"]); + var password = query["password"]; + + /* check credentials */ + if(db.check_user_password(userid, password)) { + /* generate session */ + var sessionid = generate_session_id(userid); + + /* set session in database */ + db.set_sessionid(userid, sessionid); + + /* set session in reply cookie */ + cookies = new SList<Soup.Cookie>(); + var sessioncookie = new Soup.Cookie("session", sessionid, "", "/", -1); + sessioncookie.domain = null; + cookies.append(sessioncookie); + Soup.cookies_to_response(cookies, msg); + + /* login successful */ + user = userid; + try { + name = db.get_username(user); + } catch(WebSessionError e) { + name = "Unknown User"; + } + + setup_auth(user); + } else { + /* login failed */ + failed=true; + } + } +} + diff --git a/src/template.vala b/src/template.vala new file mode 100644 index 0000000..ef7e43d --- /dev/null +++ b/src/template.vala @@ -0,0 +1,103 @@ +/* Copyright 2012, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public errordomain TemplateError { + NOT_FOUND, + NOT_LOADABLE, + NOT_ALLOWED, +} + +public class WebTemplate { + private string template; + public uint8[] data { get { return template.data; } } + + public WebTemplate(string file, WebSession login) throws TemplateError { + var b = File.new_for_path("templates/base.html"); + var m = File.new_for_path("templates/menu.html"); + var f = File.new_for_path("templates/"+file); + File fauth; + + if(login.logged_in) + fauth = File.new_for_path("templates/menu_logout.html"); + else + fauth = File.new_for_path("templates/menu_login.html"); + + uint8[] basis, menu, template, auth; + + if(!b.query_exists()) + throw new TemplateError.NOT_FOUND("templates/base.html not found!"); + + if(!m.query_exists()) + throw new TemplateError.NOT_FOUND("templates/menu.html not found!"); + + if(!fauth.query_exists()) + throw new TemplateError.NOT_FOUND(fauth.get_path()+" not found!"); + + if(!f.query_exists()) + throw new TemplateError.NOT_FOUND("templates/"+file+" not found!"); + + try { + if(!b.load_contents(null, out basis, null)) + throw new TemplateError.NOT_LOADABLE("templates/base.html could not be loaded!"); + if(!m.load_contents(null, out menu, null)) + throw new TemplateError.NOT_LOADABLE("templates/menu.html could not be loaded!"); + if(!fauth.load_contents(null, out auth, null)) + throw new TemplateError.NOT_LOADABLE(fauth.get_path()+" could not be loaded!"); + if(!f.load_contents(null, out template, null)) + throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!"); + } catch(Error e) { + throw new TemplateError.NOT_LOADABLE("could not load templates!"); + } + + this.template = ((string) basis).replace("{{{NAVBAR}}}", ((string) menu)); + this.template = this.template.replace("{{{AUTH}}}", ((string) auth)); + this.template = this.template.replace("{{{CONTENT}}}", ((string) template)); + this.template = this.template.replace("{{{USERNAME}}}", login.name); + this.template = this.template.replace("{{{USERID}}}", "%d".printf(login.user)); + } + + public WebTemplate.DATA(string file) throws TemplateError { + var f = File.new_for_path("templates/"+file); + uint8[] template; + + if(!f.query_exists()) + throw new TemplateError.NOT_FOUND("templates/"+file+" not found!"); + + try { + if(!f.load_contents(null, out template, null)) + throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!"); + } catch(Error e) { + throw new TemplateError.NOT_LOADABLE("could not load templates!"); + } + + this.template = (string) template; + } + + public void replace(string key, string value) { + template = template.replace("{{{"+key+"}}}", value); + } + + public void menu_set_active(string key) { + try { + var regex_active = new Regex("{{{MENU\\."+key+"}}}"); + var regex_other = new Regex("{{{MENU\\..*}}}"); + + template = regex_active.replace(template, -1, 0, "active"); + template = regex_other.replace(template, -1, 0, ""); + } catch(RegexError e) { + warning ("%s", e.message); + } + } +} diff --git a/src/web.vala b/src/web.vala new file mode 100644 index 0000000..562a1ba --- /dev/null +++ b/src/web.vala @@ -0,0 +1,592 @@ +/* Copyright 2012, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class WebServer { + private Soup.Server srv; + + void handler_default(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("index.html", l); + t.replace("TITLE", "KtT Shop System"); + t.menu_set_active("home"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_logout(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + l.logout(); + var t = new WebTemplate("logout.html", l); + t.replace("TITLE", "KtT Shop System"); + t.menu_set_active("home"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_users(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + string[] pathparts = path.split("/"); + + if(pathparts.length <= 2) { + handler_todo(server, msg, path, query, client); + } else { + int id = int.parse(pathparts[2]); + + if(pathparts.length <= 3) { + handler_user_entry(server, msg, path, query, client, id); + } else { + switch(pathparts[3]) { + case "invoice": + uint16 selectedyear = (pathparts.length >= 5 && pathparts[4] != "") ? (uint16) int.parse(pathparts[4]) : (uint16) (new DateTime.now_local()).get_year(); + uint8 selectedmonth = (pathparts.length >= 6 && pathparts[5] != "") ? (uint8) int.parse(pathparts[5]) : (uint8) (new DateTime.now_local()).get_month(); + uint8 selectedday = (pathparts.length >= 7 && pathparts[6] != "") ? (uint8) int.parse(pathparts[6]) : (uint8) (new DateTime.now_local()).get_day_of_month(); + handler_user_invoice(server, msg, path, query, client, id, selectedyear, selectedmonth, selectedday); + break; + case "stats": + handler_todo(server, msg, path, query, client); + break; + default: + handler_404(server, msg, path, query, client); + break; + } + } + } + } + + void handler_user_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id) { + try { + var l = new WebSession(server, msg, path, query, client); + if(id != l.user && !l.superuser) { + handler_403(server, msg, path, query, client); + return; + } + var t = new WebTemplate("users/entry.html", l); + t.replace("TITLE", "KtT Shop System: User Info %llu".printf(id)); + t.menu_set_active("users"); + + var userinfo = db.get_user_info(id); + + t.replace("UID", "%d".printf(userinfo.id)); + t.replace("FIRSTNAME", userinfo.firstname); + t.replace("LASTNAME", userinfo.lastname); + t.replace("EMAIL", userinfo.email); + t.replace("GENDER", userinfo.gender); + t.replace("STREET", userinfo.street); + t.replace("POSTALCODE", "%d".printf(userinfo.plz)); + t.replace("CITY", userinfo.city); + + var userauth = db.get_user_auth(id); + t.replace("DISABLED", userauth.disabled ? "true" : "false"); + t.replace("SUPERUSER", userauth.superuser ? "true" : "false"); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_user_invoice(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id, uint16 selectedyear, uint8 selectedmonth, uint8 selectedday) { + DateTime start, stop; + + DateYear y = (DateYear) selectedyear; + if(!y.valid() || y > 8000) { + selectedyear = (uint16) new DateTime.now_local().get_year(); + y = (DateYear) selectedyear; + } + + DateMonth m = (DateMonth) selectedmonth; + if(selectedmonth != 0 && !m.valid()) { + selectedmonth = (uint8) new DateTime.now_local().get_month(); + m = (DateMonth) selectedmonth; + } + + DateDay d = (DateDay) selectedday; + if(selectedday != 0 && !d.valid()) { + selectedday = (uint8) new DateTime.now_local().get_day_of_month(); + d = (DateDay) selectedday; + } + + try { + var l = new WebSession(server, msg, path, query, client); + if(id != l.user && !l.superuser) { + handler_403(server, msg, path, query, client); + return; + } + var t = new WebTemplate("users/invoice.html", l); + t.replace("TITLE", "KtT Shop System: User Invoice %llu".printf(id)); + t.menu_set_active("users"); + + /* years, in which something has been purchased by the user */ + var first = db.get_first_purchase(id); + var last = db.get_last_purchase(id); + string years = ""; + for(int i=first.get_year(); i <= last.get_year(); i++) { + years += @"<li><a href=\"/users/$id/invoice/$i/$selectedmonth/$selectedday\">$i</a></li>"; + } + t.replace("YEARS", years); + t.replace("SELECTEDYEAR", @"$selectedyear"); + + /* months, in which something has been purchased by the user */ + string[] monthnames = { "All Months", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; + string months = @"<li><a href=\"/users/$id/invoice/$selectedyear/0/0\">All Months</a></li>"; + for(int i=1; i<monthnames.length; i++) { + if(first.get_year() == selectedyear && i > 0 && i < first.get_month()) + months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>"; + else if(selectedyear < first.get_year()) + months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>"; + else if(last.get_year() == selectedyear && i > last.get_month()) + months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>"; + else if(selectedyear > last.get_year()) + months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>"; + else + months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\">$(monthnames[i])</a></li>"; + } + t.replace("MONTHS", months); + t.replace("SELECTEDMONTH", @"$(monthnames[selectedmonth])"); + + int dim = m.valid() ? Date.get_days_in_month(m, y) : 0; + string days = @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/0\">All Days</a></li>"; + for(int i=1; i<=dim; i++) { + if(first.get_year() == selectedyear && first.get_month() == selectedmonth && i < first.get_day_of_month()) + days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>"; + else if(selectedyear < first.get_year() || (selectedyear == first.get_year() && selectedmonth < first.get_month())) + days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>"; + else if(last.get_year() == selectedyear && last.get_month() == selectedmonth && i > last.get_day_of_month()) + days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>"; + else if(selectedyear > last.get_year() || (selectedyear == last.get_year() && selectedmonth > last.get_month())) + days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>"; + else + days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\">$i</a></li>"; + } + t.replace("DAYS", days); + if(selectedday > 0) + t.replace("SELECTEDDAY", @"$selectedday"); + else + t.replace("SELECTEDDAY", "All Days"); + + if(selectedday != 0) { + start = new DateTime.local(selectedyear, selectedmonth, selectedday, 8, 0, 0); + stop = start.add_days(1); + } else if(selectedmonth != 0) { + start = new DateTime.local(selectedyear, selectedmonth, 16, 8, 0, 0); + stop = start.add_months(1); + } else { + start = new DateTime.local(selectedyear, 1, 16, 8, 0, 0); + stop = start.add_years(1); + } + + string table = ""; + Price sum = 0; + foreach(var e in db.get_invoice(id, start.to_unix(), stop.to_unix())) { + var timestamp = new DateTime.from_unix_utc(e.timestamp); + var date = timestamp.format("%d.%m.%Y"); + var time = timestamp.format("%H:%M:%S"); + var product = e.product.name; + var price = e.price; + table += @"<tr><td>$date</td><td>$time</td><td>$product</td><td>$price€</td></tr>"; + sum += e.price; + } + + t.replace("DATA", table); + t.replace("SUM", @"$sum"); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_products(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + string[] pathparts = path.split("/"); + + if(pathparts.length <= 2 || pathparts[2] == "") { + handler_product_list(server, msg, path, query, client); + } else { + uint64 id = uint64.parse(pathparts[2]); + + if(pathparts.length <= 3) { + handler_product_entry(server, msg, path, query, client, id); + } else { + switch(pathparts[3]) { + case "restock": + handler_product_restock(server, msg, path, query, client, id); + break; + default: + handler_product_entry(server, msg, path, query, client, id); + break; + } + } + } + } + + void handler_product_list(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("products/index.html", l); + t.replace("TITLE", "KtT Shop System: Product List"); + t.menu_set_active("products"); + + string table = ""; + foreach(var e in db.get_stock()) { + table += "<tr><td><a href=\"/products/%s\">%s</a></td><td><a href=\"/products/%s\">%s</a></td><td>%d</td><td>%s€</td><td>%s€</td></tr>".printf( + e.id, e.id, e.id, e.name, e.amount, e.memberprice, e.guestprice + ); + } + + t.replace("DATA", table); + + if(l.superuser) + t.replace("NEWPRODUCT", "block"); + else + t.replace("NEWPRODUCT", "none"); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_product_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("products/entry.html", l); + t.replace("TITLE", "KtT Shop System: Product %llu".printf(id)); + t.menu_set_active("products"); + + /* ean */ + t.replace("EAN", "%llu".printf(id)); + + /* name */ + string name = db.get_product_name(id); + t.replace("NAME", name); + + /* amount */ + t.replace("AMOUNT", "%d".printf(db.get_product_amount(id))); + + if(l.superuser) + t.replace("RESTOCK", "block"); + else + t.replace("RESTOCK", "none"); + + /* prices */ + string prices = ""; + foreach(var e in db.get_prices(id)) { + var time = new DateTime.from_unix_local(e.valid_from); + prices += @"<tr><td>%s</td><td>$(e.memberprice)€</td><td>$(e.guestprice)€</td></tr>".printf( + time.format("%Y-%m-%d %H:%M") + ); + } + t.replace("PRICES", prices); + + /* restocks */ + string restocks = ""; + foreach(var e in db.get_restocks(id)) { + var time = new DateTime.from_unix_local(e.timestamp); + restocks += "<tr><td>%s</td><td>%d</td><td>%s€</td></tr>".printf( + time.format("%Y-%m-%d %H:%M"), e.amount, e.price + ); + } + t.replace("RESTOCKS", restocks); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_products_new(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + var template = new WebTemplate("products/new.html", session); + template.replace("TITLE", "KtT Shop System: New Product"); + template.menu_set_active("products"); + + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + + if(query != null && query.contains("name") && query.contains("id") && query.contains("memberprice") && query.contains("guestprice")) { + var name = query["name"]; + var ean = uint64.parse(query["id"]); + Price memberprice = int.parse(query["memberprice"]); + Price guestprice = int.parse(query["guestprice"]); + + if(ean > 0 && memberprice > 0 && guestprice > 0 && db.new_product(ean, name, memberprice, guestprice)) { + template.replace("NAME", name); + template.replace("EAN", @"$ean"); + template.replace("MEMBERPRICE", @"$memberprice€"); + template.replace("GUESTPRICE", @"$guestprice€"); + template.replace("NEW.OK", "block"); + template.replace("NEW.FAIL", "none"); + } else { + template.replace("NAME", "..."); + template.replace("NEW.OK", "none"); + template.replace("NEW.FAIL", "block"); + } + } else { + template.replace("NAME", "..."); + template.replace("NEW.OK", "none"); + template.replace("NEW.FAIL", "block"); + } + + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_product_restock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client, uint64 id) { + try { + var session = new WebSession(server, msg, path, query, client); + + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + + var template = new WebTemplate("products/restock.html", session); + template.replace("TITLE", "KtT Shop System: Restock Product %llu".printf(id)); + template.replace("NAME", db.get_product_name(id)); + template.menu_set_active("products"); + + if(query != null && query.contains("amount") && query.contains("price")) { + int amount = int.parse(query["amount"]); + Price price = int.parse(query["price"]); + + if(amount >= 1 && price >= 1) { + if(db.restock(session.user, id, amount, price)) { + template.replace("AMOUNT", @"$amount"); + template.replace("PRICE", @"$price"); + template.replace("RESTOCK.OK", "block"); + template.replace("RESTOCK.FAIL", "none"); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + return; + } + } + } + + template.replace("RESTOCK.OK", "none"); + template.replace("RESTOCK.FAIL", "block"); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + return; + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/index.html", l); + t.replace("TITLE", "KtT Shop System: Statistics"); + t.menu_set_active("stats"); + + var stats = db.get_stats_info(); + + t.replace("NUMBER_OF_ARTICLES", @"$(stats.count_articles)"); + t.replace("NUMBER_OF_USERS", @"$(stats.count_users)"); + t.replace("STOCK_VALUE", @"$(stats.stock_value)€"); + t.replace("TOTAL_SALES", @"$(stats.sales_total)€"); + t.replace("TOTAL_PROFIT", @"$(stats.profit_total)€"); + t.replace("SALES_TODAY", @"$(stats.sales_today)€"); + t.replace("PROFIT_TODAY", @"$(stats.profit_today)€"); + t.replace("SALES_THIS_MONTH", @"$(stats.sales_this_month)€"); + t.replace("PROFIT_THIS_MONTH", @"$(stats.profit_this_month)€"); + t.replace("SALES_PER_DAY", @"$(stats.sales_per_day)€"); + t.replace("PROFIT_PER_DAY", @"$(stats.profit_per_day)€"); + t.replace("SALES_PER_MONTH", @"$(stats.sales_per_month)€"); + t.replace("PROFIT_PER_MONTH", @"$(stats.profit_per_month)€"); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_stock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/stock.html", l); + string data = db.get_stats_stock().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Stock"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_profit_per_day(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/profit_per_day.html", l); + string data = db.get_stats_profit_per_day().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Profit"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_profit_per_weekday(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/profit_per_weekday.html", l); + string data = db.get_stats_profit_per_weekday().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Profit/Weekday"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_profit_per_product(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/profit_per_product.html", l); + string data = db.get_stats_profit_per_products().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Profit/Product"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_js(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var t = new WebTemplate.DATA(path); + msg.set_response("text/javascript", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_css(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var t = new WebTemplate.DATA(path); + msg.set_response("text/css", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_img(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var f = File.new_for_path("templates/"+path); + uint8[] data = null; + + if(f.query_exists() && f.load_contents(null, out data, null)) { + msg.set_response("image/png", Soup.MemoryUse.COPY, data); + return; + } + } catch(Error e) { + error("there has been some error: %s!\n", e.message); + } + + handler_404(server, msg, path, query, client); + return; + } + + void handler_404(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + string result = "Page not Found\n"; + msg.set_status(404); + msg.set_response("text/plain", Soup.MemoryUse.COPY, result.data); + } + + void handler_403(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + var template = new WebTemplate("errors/403.html", session); + template.replace("TITLE", "Access Denied"); + template.menu_set_active(""); + msg.set_status(403); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } } + + void handler_todo(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + var template = new WebTemplate("errors/todo.html", session); + template.replace("TITLE", "KtT Shop System: ToDo"); + template.menu_set_active(""); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + public WebServer(int port = 8080) { + srv = new Soup.Server(Soup.SERVER_PORT, port); + + /* index */ + srv.add_handler("/", handler_default); + + /* logout */ + srv.add_handler("/logout", handler_logout); + + /* data (js, css, img) */ + srv.add_handler("/js", handler_js); + srv.add_handler("/css", handler_css); + srv.add_handler("/img", handler_img); + + /* products */ + srv.add_handler("/products", handler_products); + srv.add_handler("/products/new", handler_products_new); + + /* stats */ + srv.add_handler("/stats", handler_stats); + srv.add_handler("/stats/stock", handler_stats_stock); + srv.add_handler("/stats/profit_per_day", handler_stats_profit_per_day); + srv.add_handler("/stats/profit_per_weekday", handler_stats_profit_per_weekday); + srv.add_handler("/stats/profit_per_product", handler_stats_profit_per_product); + + /* users */ + srv.add_handler("/users", handler_users); + + srv.run(); + } +} |