mirror of
https://codeberg.org/metamuffin/abrechenbarkeit.git
synced 2024-12-29 00:04:35 +00:00
a
This commit is contained in:
parent
7dc2354e49
commit
c78ca2592d
2 changed files with 90 additions and 63 deletions
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
]]--
|
]] --
|
||||||
|
|
||||||
local function escape(s)
|
local function escape(s)
|
||||||
return s:gsub("<", "<"):gsub("<", "<")
|
return s:gsub("<", "<"):gsub("<", "<")
|
||||||
|
@ -63,17 +63,50 @@ local function load_config()
|
||||||
return config
|
return config
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function load_translations(langs)
|
||||||
|
local t = {}
|
||||||
|
for _, lcode in ipairs(langs) do
|
||||||
|
local file = io.open(string.format("locale/%s.ini", lcode), "r")
|
||||||
|
if file ~= nil then
|
||||||
|
for l in file:lines("l") do
|
||||||
|
if l ~= "" then
|
||||||
|
local key, value = string.match(l, "^([^=]+)=([^=]*)")
|
||||||
|
if key ~= nil and value ~= nil then
|
||||||
|
t[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
local config = load_config()
|
local config = load_config()
|
||||||
local path = os.getenv("PATH_INFO")
|
local path = os.getenv("PATH_INFO")
|
||||||
local method = os.getenv("REQUEST_METHOD")
|
local method = os.getenv("REQUEST_METHOD")
|
||||||
local query = parse_query(os.getenv("QUERY_STRING"))
|
local query = parse_query(os.getenv("QUERY_STRING"))
|
||||||
|
local translations = load_translations({ "en", config.language })
|
||||||
|
|
||||||
local stylesheet = io.open("style.css"):read("a")
|
local stylesheet = io.open("style.css"):read("a")
|
||||||
local script = io.open("script.js"):read("a")
|
local script = io.open("script.js"):read("a")
|
||||||
|
|
||||||
|
local function format(template, params)
|
||||||
|
return string.gsub(template, "{([%w\\.!]+)}", function(n)
|
||||||
|
local raw = n:sub(1,1) ~= "!"
|
||||||
|
if raw then n = n:sub(2) end
|
||||||
|
local s = format(params[n] or translations[n] or "NIL TEMPLATE", params)
|
||||||
|
return raw and s or escape(s)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function format_amount(amount, tag, classes)
|
||||||
|
local s = string.format("%.02f%s", amount / 100, config.unit or "€")
|
||||||
|
if tag == nil then return s end
|
||||||
|
return string.format([[<%s class="amount-%s %s">%s</%s>"]], tag, amount >= 0 and "pos" or "neg", s, tag, classes or "")
|
||||||
|
end
|
||||||
|
|
||||||
local function get_user_theme(username)
|
local function get_user_theme(username)
|
||||||
local c = ""
|
local c = ""
|
||||||
local js = ""
|
|
||||||
if username == "_jeb" then
|
if username == "_jeb" then
|
||||||
c = "html { animation: 2s jeb infinite; }"
|
c = "html { animation: 2s jeb infinite; }"
|
||||||
c = c .. "@keyframes jeb {\n"
|
c = c .. "@keyframes jeb {\n"
|
||||||
|
@ -84,24 +117,29 @@ local function get_user_theme(username)
|
||||||
elseif username == "Dinnerbone" then
|
elseif username == "Dinnerbone" then
|
||||||
c = "html { transform: scale(-1); } "
|
c = "html { transform: scale(-1); } "
|
||||||
end
|
end
|
||||||
return c, js
|
return c
|
||||||
|
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
|
end
|
||||||
|
|
||||||
local function respond(status, title, body)
|
local function respond(status, title, body)
|
||||||
local themecss, themejs = get_user_theme(path and path:sub(2))
|
|
||||||
|
|
||||||
print(string.format("Status: %d", status))
|
print(string.format("Status: %d", status))
|
||||||
print("Content-Type: text/html")
|
print("Content-Type: text/html")
|
||||||
print("")
|
print("")
|
||||||
print(string.format([[
|
print(format([[
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html><head>
|
<html><head>
|
||||||
<title>%s</title>
|
<title>{title}</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<style>%s</style>
|
<style>{style}</style>
|
||||||
<style>%s</style>
|
<style>{user_style}</style>
|
||||||
<script>%s</script>
|
<script>{script}</script>
|
||||||
%s
|
{head_extra}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav>
|
||||||
|
@ -110,23 +148,21 @@ local function respond(status, title, body)
|
||||||
<path d="M3 4.5a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5M11.5 4a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z"/>
|
<path d="M3 4.5a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5M11.5 4a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z"/>
|
||||||
<path d="M2.354.646a.5.5 0 0 0-.801.13l-.5 1A.5.5 0 0 0 1 2v13H.5a.5.5 0 0 0 0 1h15a.5.5 0 0 0 0-1H15V2a.5.5 0 0 0-.053-.224l-.5-1a.5.5 0 0 0-.8-.13L13 1.293l-.646-.647a.5.5 0 0 0-.708 0L11 1.293l-.646-.647a.5.5 0 0 0-.708 0L9 1.293 8.354.646a.5.5 0 0 0-.708 0L7 1.293 6.354.646a.5.5 0 0 0-.708 0L5 1.293 4.354.646a.5.5 0 0 0-.708 0L3 1.293zm-.217 1.198.51.51a.5.5 0 0 0 .707 0L4 1.707l.646.647a.5.5 0 0 0 .708 0L6 1.707l.646.647a.5.5 0 0 0 .708 0L8 1.707l.646.647a.5.5 0 0 0 .708 0L10 1.707l.646.647a.5.5 0 0 0 .708 0L12 1.707l.646.647a.5.5 0 0 0 .708 0l.509-.51.137.274V15H2V2.118z"/>
|
<path d="M2.354.646a.5.5 0 0 0-.801.13l-.5 1A.5.5 0 0 0 1 2v13H.5a.5.5 0 0 0 0 1h15a.5.5 0 0 0 0-1H15V2a.5.5 0 0 0-.053-.224l-.5-1a.5.5 0 0 0-.8-.13L13 1.293l-.646-.647a.5.5 0 0 0-.708 0L11 1.293l-.646-.647a.5.5 0 0 0-.708 0L9 1.293 8.354.646a.5.5 0 0 0-.708 0L7 1.293 6.354.646a.5.5 0 0 0-.708 0L5 1.293 4.354.646a.5.5 0 0 0-.708 0L3 1.293zm-.217 1.198.51.51a.5.5 0 0 0 .707 0L4 1.707l.646.647a.5.5 0 0 0 .708 0L6 1.707l.646.647a.5.5 0 0 0 .708 0L8 1.707l.646.647a.5.5 0 0 0 .708 0L10 1.707l.646.647a.5.5 0 0 0 .708 0L12 1.707l.646.647a.5.5 0 0 0 .708 0l.509-.51.137.274V15H2V2.118z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Abrechenbarkeit</a>
|
{appname}
|
||||||
<a href="/?log">Log</a>
|
</a>
|
||||||
<a href="/?products">Products</a>
|
<a href="/?log">{log}</a>
|
||||||
<a href="/?about">About</a>
|
<a href="/?products">{products}</a>
|
||||||
|
<a href="/?about">{about}</a>
|
||||||
</nav>
|
</nav>
|
||||||
]],
|
]], {
|
||||||
escape(title),
|
title = escape(title),
|
||||||
stylesheet, -- style.css
|
style = stylesheet,
|
||||||
themecss, -- theme for user
|
user_style = get_user_theme(path and path:sub(2)),
|
||||||
script, -- script.js
|
script = script,
|
||||||
config.head_extra or ""
|
head_extra = config.head_extra or ""
|
||||||
))
|
}))
|
||||||
body()
|
body()
|
||||||
print(string.format(
|
print("</body></html>")
|
||||||
"<script>%s</script></body></html>",
|
|
||||||
themejs
|
|
||||||
))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function error_box(message)
|
local function error_box(message)
|
||||||
|
@ -149,13 +185,6 @@ local function form_data()
|
||||||
return parse_query(io.read())
|
return parse_query(io.read())
|
||||||
end
|
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 function read_log()
|
||||||
local log = io.open("log", "r")
|
local log = io.open("log", "r")
|
||||||
if log == nil then
|
if log == nil then
|
||||||
|
@ -285,14 +314,15 @@ local function r_user_post(username)
|
||||||
end
|
end
|
||||||
log:flush()
|
log:flush()
|
||||||
log:close()
|
log:close()
|
||||||
return string.format([[
|
return format([[
|
||||||
<div class="notif"><p>Transaction successful: <strong class="amount-%s">%.02f€</strong> (%s)</p></div>
|
<div class="notif"><p>Transaction successful: {amount} ({!comment})</p></div>
|
||||||
<audio src="%s" autoplay></audio>
|
<audio src="{sound}" autoplay></audio>
|
||||||
]],
|
]], {
|
||||||
amount >= 0 and "pos" or "neg", amount / 100,
|
sign = amount >= 0 and "pos" or "neg",
|
||||||
escape(comment),
|
amount = format_amount(amount, "strong"),
|
||||||
config.transaction_sound or ""
|
comment = escape(comment),
|
||||||
)
|
sound = config.transaction_sound or ""
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local function r_user(username)
|
local function r_user(username)
|
||||||
|
@ -301,24 +331,20 @@ local function r_user(username)
|
||||||
notif = r_user_post(username)
|
notif = r_user_post(username)
|
||||||
end
|
end
|
||||||
return respond(200, string.format("Abrechenbarheit: %s", username), function()
|
return respond(200, string.format("Abrechenbarheit: %s", username), function()
|
||||||
print(string.format("<h1>%s</h1>", username))
|
print(format("<h1>{username}</h1>", { username = username }))
|
||||||
local balance = balances()[username]
|
local balance = balances()[username]
|
||||||
local last_txn = last_txns()[username]
|
local last_txn = last_txns()[username]
|
||||||
local new_user = balance == nil
|
local new_user = balance == nil
|
||||||
balance = balance or 0
|
balance = balance or 0
|
||||||
if notif then print(notif) end
|
if notif then print(notif) end
|
||||||
if new_user then
|
if new_user then
|
||||||
print([[
|
print([[<div class="notif"><p><i>{user.lazy_creation}</i></p></div>]])
|
||||||
<div class="notif"><p><i>This user account does not exist yet. It will only be created after the first transaction.</i></p></div>
|
|
||||||
]])
|
|
||||||
else
|
else
|
||||||
print([[<div class="backgroundbox userinfo">]])
|
print([[<div class="backgroundbox userinfo">]])
|
||||||
print(string.format([[
|
print(format([[{user.balance}: <br>{amount}<br>]],
|
||||||
Current balance:<br><span class="amount-%s balance-value">%.02f€</span><br>
|
{ sign = balance >= 0 and "pos" or "neg", amount = format_amount(balance, "span", "balance-value") }))
|
||||||
]], balance >= 0 and "pos" or "neg", balance / 100))
|
print(format([[{user.last_txn} <a href="/{username}?log">{user.view_log}</a>]],
|
||||||
print(string.format([[
|
{ time = format_duration(os.time() - last_txn), username = urlencode(username) }))
|
||||||
Last transaction added %s ago. <a href="/%s?log">View user log</a>
|
|
||||||
]], format_duration(os.time() - last_txn), username))
|
|
||||||
print([[</div>]])
|
print([[</div>]])
|
||||||
end
|
end
|
||||||
print([[<div class="transactions container firstchildlarge">]])
|
print([[<div class="transactions container firstchildlarge">]])
|
||||||
|
@ -336,35 +362,35 @@ local function r_user(username)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
print("</div>")
|
print("</div>")
|
||||||
print([[
|
print(format([[
|
||||||
<form class="transaction box backgroundbox" action="" method="POST">
|
<form class="transaction box backgroundbox" action="" method="POST">
|
||||||
<h3>Create Transaction</h3>
|
<h3>{user.form.transaction}</h3>
|
||||||
<label for="amount">Amount (ct): </label>
|
<label for="amount">Amount (ct): </label>
|
||||||
<input type="number" name="amount" id="amount" />
|
<input type="number" name="amount" id="amount" />
|
||||||
<label for="comment">Comment: </label>
|
<label for="comment">Comment: </label>
|
||||||
<input type="text" name="comment" id="comment" />
|
<input type="text" name="comment" id="comment" />
|
||||||
<input type="submit" value="Update" class="amount-ntr button" />
|
<input type="submit" value="{user.form.transaction.submit}" class="amount-ntr button" />
|
||||||
</form>
|
</form>
|
||||||
<form class="transaction box backgroundbox" action="" method="POST" id="buy_product">
|
<form class="transaction box backgroundbox" action="" method="POST" id="buy_product">
|
||||||
<h3>Buy Product</h3>
|
<h3>{user.form.buy}</h3>
|
||||||
<input type="text" name="negate_pcount" value="1" hidden />
|
<input type="text" name="negate_pcount" value="1" hidden />
|
||||||
<label for="pcount">Count: </label>
|
<label for="pcount">Count: </label>
|
||||||
<input type="number" name="pcount" id="pcount" value="1" />
|
<input type="number" name="pcount" id="pcount" value="1" />
|
||||||
<label for="pcode">Product Barcode: </label>
|
<label for="pcode">{field.barcode}: </label>
|
||||||
<input type="text" name="pcode" id="pcode" />
|
<input type="text" name="pcode" id="pcode" />
|
||||||
<input class="amount-neg button" type="submit" value="Buy" />
|
<input class="amount-neg button" type="submit" value="{user.form.buy.submit}" />
|
||||||
</form>
|
</form>
|
||||||
<form class="transaction box backgroundbox" action="" method="POST" id="buy_product">
|
<form class="transaction box backgroundbox" action="" method="POST" id="buy_product">
|
||||||
<h3>Restock Product</h3>
|
<h3>{user.form.restock}</h3>
|
||||||
<label for="pcount">Count: </label>
|
<label for="pcount">{field.count}: </label>
|
||||||
<input type="number" name="pcount" id="pcount" value="1" />
|
<input type="number" name="pcount" id="pcount" value="1" />
|
||||||
<label for="amount">Upstream price: </label>
|
<label for="amount">{field.upstream_price}: </label>
|
||||||
<input type="number" name="amount" id="amount" />
|
<input type="number" name="amount" id="amount" />
|
||||||
<label for="pcode">Product Barcode: </label>
|
<label for="pcode">{field.barcode}: </label>
|
||||||
<input type="text" name="pcode" id="pcode" />
|
<input type="text" name="pcode" id="pcode" />
|
||||||
<input type="submit" value="Restock" class="button amount-pos" />
|
<input type="submit" value="{user.form.restock.submit}" class="button amount-pos" />
|
||||||
</form>
|
</form>
|
||||||
]])
|
]]))
|
||||||
print("</div>")
|
print("</div>")
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,3 +35,4 @@ user.form.transaction.submit=Update
|
||||||
user.form.transaction=Create Transaction
|
user.form.transaction=Create Transaction
|
||||||
user.last_txn=Last transaction added %s.
|
user.last_txn=Last transaction added %s.
|
||||||
user.view_log=View user log
|
user.view_log=View user log
|
||||||
|
user.lazy_creation=This user account does not exist yet. It will only be created after the first transaction.
|
||||||
|
|
Loading…
Reference in a new issue