From 4961908376db687a74ff4a6b134430f1a57ce57b Mon Sep 17 00:00:00 2001 From: "Riley L." Date: Sun, 3 Nov 2024 18:31:13 +0100 Subject: css and rebrad --- abrechenbarkeit.lua | 400 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100755 abrechenbarkeit.lua (limited to 'abrechenbarkeit.lua') diff --git a/abrechenbarkeit.lua b/abrechenbarkeit.lua new file mode 100755 index 0000000..dc3685b --- /dev/null +++ b/abrechenbarkeit.lua @@ -0,0 +1,400 @@ +#!/usr/bin/env luajit + +local function escape(s) + return s:gsub("<", "<"):gsub("<", "<") +end + +local function urldecode(s) + if s == nil then return nil end + return s:gsub("+", " "):gsub("%%20", " ") +end + +local function urlencode(s) + if s == nil then return nil end + return s:gsub(" ", "%%20") +end + +local function parse_query(q) + if q == nil then return {} end + local data = {} + for pair in string.gmatch(q, "([^&]+)") do + local flag = string.match(pair, "^([^=]+)$") + if flag ~= nil then + data[flag] = "1" + else + local key, value = string.match(pair, "^([^=]+)=([^=]*)$") + if key ~= nil and value ~= nil then + data[key] = urldecode(value) + end + end + end + return data +end + +local function load_config() + local log = io.open("config", "r") + if log == nil then return {} end + local config = {} + for l in log:lines("l") do + if l ~= "" and l[0] ~= "#" then + local key, value = string.match(l, "^([^=]+)=([^=]*)") + if key ~= nil and value ~= nil then + config[key] = value + end + end + end + return config +end + +local config = load_config() +local path = os.getenv("PATH_INFO") +local method = os.getenv("REQUEST_METHOD") +local query = parse_query(os.getenv("QUERY_STRING")) + +local stylesheet = io.open("a.css"):read("a") +-- local stylesheet = [[ +-- /* body { background-color: #161616; } +-- h1, h2, h3, h4, h5, h6, p, label, a { color: #e2e2e2; } */ +-- .amount-presets form { display: inline-block; width: 60px } +-- .amount-pos { color: green; } +-- .amount-neg { color: red; } +-- nav h2 { display: inline-block } +-- .notif { padding: 0.5em; margin: 0.5em; background-color: #ddd; } +-- .notif.error { background-color: #faa; } +-- .notif p { margin: 5px; } +-- form.box { border: 2px solid grey; padding: 0.5em; margin: 0.5em; display: inline-block; } +-- form h3 { margin: 5px; } +-- ]] + +local script = [[ + document.addEventListener("keypress", ev => { + if (!(document.activeElement instanceof HTMLInputElement)) { + if (ev.code.startsWith("Digit")) + document.forms.buy_product.product.value += ev.code.substring(5) + if (ev.code == "Enter") + document.forms.buy_product.submit() + } + }) +]] + +local function respond(status, title, body) + print(string.format("Status: %d", status)) + print("Content-Type: text/html") + print("") + print(string.format([[ + + + %s + + + + %s + + + + ]], escape(title), stylesheet, script, config.head_extra or "")) + body() + print("") +end + +local function respond_error(message) + respond(400, "Error", function() + print(string.format("

Error: %s

", escape(message))) + end) +end + +local function redirect(path) + print("Status: 307") + print(string.format("Location: %s", path)) + print() +end + +local function form_data() + return parse_query(io.read()) +end + +local function format_duration(t) + if t > 86400 then return string.format("%d day%s", t / 86400, math.floor(t / 86400) ~= 1 and "s" or "") end + if t > 3600 then return string.format("%d hour%s", t / 3600, math.floor(t / 3600) ~= 1 and "s" or "") end + if t > 60 then return string.format("%d minute%s", t / 60, math.floor(t / 60) ~= 1 and "s" or "") end + return string.format("%d seconds", t) +end + +local function read_log() + local log = io.open("log", "r") + if log == nil then + return function() return nil end + end + local lines = log:lines("l") + return function() + local l = lines() + if l == "" or l == nil then + return nil + end + local time, username, amount, comment = string.match(l, "(%d+),([%w_ -]+),(-?%d+),([%w_ -]*)") + return tonumber(time), username, tonumber(amount), comment + end +end + +local function read_products() + local log = io.open("products", "r") + if log == nil then + return function() return nil end + end + local lines = log:lines("l") + return function() + local l = lines() + if l == "" or l == nil then + return nil + end + local barcode, amount, name = string.match(l, "([%w_-]+),(-?%d+),([%w_ -]*)") + return barcode, tonumber(amount), name + end +end + +local function balances() + local users = {} + for _, username, amount, _ in read_log() do + users[username] = (users[username] or 0) + amount + end + return users +end + +local function last_txns() + local users = {} + for time, username, _, _ in read_log() do + users[username] = time + end + return users +end + +local function error_box(message) + return string.format([[

Error: %s

]], message) +end + +local function r_user_post(username) + local data = form_data() + local amount = nil + local comment = "" + if data.product then + for p_barcode, p_amount, p_name in read_products() do + if p_barcode == data.product then + amount = p_amount + comment = p_name + end + end + if amount == nil then + return error_box("unknown product") + end + else + amount = tonumber(data.amount) + comment = data.comment or "" + end + if amount == nil then + return error_box("amount invalid") + end + if comment:match("^[%w_ -]*$") == nil then + return error_box("comment invalid") + end + local log = io.open("log", "a+") + if log == nil then + return error_box("failed to open log") + end + local time = os.time() + log:write(string.format("%d,%s,%d,%s\n", time, username, amount, comment)) + log:flush() + log:close() + return string.format([[ +

Transaction successful: %.02f€ (%s)

+ + ]], + amount >= 0 and "pos" or "neg", amount / 100, + escape(comment), + config.transaction_sound or "" + ) +end + +local function r_user(username) + local notif = nil + if method == "POST" then + notif = r_user_post(username) + end + return respond(200, string.format("Abrechenbarheit: %s", username), function() + print(string.format("

%s

", username)) + local balance = balances()[username] + local last_txn = last_txns()[username] + local new_user = balance == nil + balance = balance or 0 + if notif then print(notif) end + if new_user then + print([[ +

This user account does not exist yet. It will only be created after the first transaction.

+ ]]) + else + print(string.format([[ +

Current balance:%.02f€

+ ]], balance >= 0 and "pos" or "neg", balance / 100)) + print(string.format([[ +

Last transaction added %s ago. View user log + ]], format_duration(os.time() - last_txn), username)) + end + print([[

]]) + print([[
]]) + for _, type in ipairs({ 1, -1 }) do + for _, amount in ipairs({ 50, 100, 150, 200, 500, 1000 }) do + print(string.format([[ +
+ + + +
+ ]], amount * type, ({ [-1] = "-", [1] = "+" })[type], amount / 100, + ({ [-1] = "neg", [1] = "pos" })[type])) + end + end + print("
") + print([[ +
+

Create Transaction

+ + + + + +
+
+

Buy Product

+ + + +
+ ]]) + print("
") + end) +end + +local function r_log(filter) + return respond(200, "Abrechnungen", function() + print("") + print("") + for time, username, amount, comment in read_log() do + if filter == nil or filter == username then + print(string.format([[ + + + + + + + + ]], + time, format_duration(os.time() - time), + escape(username), + amount >= 0 and "pos" or "neg", amount / 100, + escape(comment), + escape(username), + -amount, + escape(comment) + )) + end + end + print("
TimeUsernameAmountComment
%d (%s ago)%s%.02f€%s +
+ + + +
+
") + end) +end + +local function r_index() + return respond(200, "Abrechenbarkeit", function() + print([[ +
+

User Creation

+ + + +
+ ]]) + print("") + end) +end + +local function r_create_user() + local username = query.create_user + if username:match("^([%w_ -]+)$") == nil then + return respond_error("invalid username " .. username) + end + return redirect(string.format("/%s", urlencode(username))) +end + +local function r_products() + respond(200, "Abrechenbare Products", function() + print("

Product List

") + print("") + for barcode, price, name in read_products() do + print(string.format([[ + + ]], + name, + price >= 0 and "pos" or "neg", price / 100, + barcode + )) + end + print("
NamePriceBarcode
%s%.02f€%s
") + end) +end + +local function extract_username() + if path == nil then + return respond_error("no path") + end + local username = urldecode(path:sub(2)) + if username == nil or username:match("^([%w_ -]+)$") == nil then + return nil + end + return username +end + +if path == "/" then + if query.products then + return r_products() + elseif query.log then + return r_log() + elseif query.create_user then + return r_create_user() + else + return r_index() + end +else + local username = extract_username() + if username == nil then + return respond_error("username invalid") + elseif query.log then + return r_log(username) + else + return r_user(username) + end +end -- cgit v1.2.3-70-g09d2