#!/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 path = os.getenv("PATH_INFO") local method = os.getenv("REQUEST_METHOD") local query = parse_query(os.getenv("QUERY_STRING")) 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 p { margin: 5px } ]] local script = [[ ]] local function respond(status, title, body) print(string.format("Status: %d", status)) print("Content-Type: text/html") print("") print(string.format([[ %s ]], escape(title), stylesheet, script)) 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 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 r_user() 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 respond_error("username invalid") end local notif = nil if method == "POST" then local data = form_data() local amount = nil local comment = "" if data.barcode then for p_barcode, p_amount, p_name in read_products() do if p_barcode == data.barcode then amount = p_amount comment = p_name end end else amount = tonumber(data.amount) comment = data.comment or "" end if amount == nil then return respond_error("amount invalid") end if comment:match("^[%w_ -]*$") == nil then return respond_error("comment invalid") end local log = io.open("log", "a+") if log == nil then return respond_error("failed to open log") end local time = os.time() log:write(string.format("%d,%s,%s,%s\n", time, username, amount, comment)) log:flush() log:close() notif = string.format( "

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

", amount >= 0 and "pos" or "neg", amount / 100, escape(comment) ) end return respond(200, username, function() print(string.format("

%s

", username)) local balance = balances()[username] local new_user = balance == nil balance = balance or 0 if new_user then print([[

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

]]) end if notif then print(notif) end print(string.format("

Current balance: %.02f€

", balance >= 0 and "pos" or "neg", balance / 100)) 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 print("
") end print("
") end) end local function r_log() return respond(200, "Log", function() print("") print("") for time, username, amount, comment in read_log() do print(string.format("", time, escape(username), amount / 100, escape(comment))) end print("
TimeUsernameAmountComment
%d%s%.02f€%s
") end) end local function r_index() return respond(200, "Users", function() print([[

]]) 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 if path == "/" then if query.log then return r_log() elseif query.create_user then return r_create_user() else return r_index() end else return r_user() end