mirror of
https://codeberg.org/metamuffin/abrechenbarkeit.git
synced 2024-12-28 07:54: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
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
]]--
|
||||
]] --
|
||||
|
||||
local function escape(s)
|
||||
return s:gsub("<", "<"):gsub("<", "<")
|
||||
|
@ -63,17 +63,50 @@ local function load_config()
|
|||
return config
|
||||
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 path = os.getenv("PATH_INFO")
|
||||
local method = os.getenv("REQUEST_METHOD")
|
||||
local query = parse_query(os.getenv("QUERY_STRING"))
|
||||
local translations = load_translations({ "en", config.language })
|
||||
|
||||
local stylesheet = io.open("style.css"):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 c = ""
|
||||
local js = ""
|
||||
if username == "_jeb" then
|
||||
c = "html { animation: 2s jeb infinite; }"
|
||||
c = c .. "@keyframes jeb {\n"
|
||||
|
@ -84,24 +117,29 @@ local function get_user_theme(username)
|
|||
elseif username == "Dinnerbone" then
|
||||
c = "html { transform: scale(-1); } "
|
||||
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
|
||||
|
||||
local function respond(status, title, body)
|
||||
local themecss, themejs = get_user_theme(path and path:sub(2))
|
||||
|
||||
print(string.format("Status: %d", status))
|
||||
print("Content-Type: text/html")
|
||||
print("")
|
||||
print(string.format([[
|
||||
print(format([[
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<title>%s</title>
|
||||
<title>{title}</title>
|
||||
<meta charset="utf-8" />
|
||||
<style>%s</style>
|
||||
<style>%s</style>
|
||||
<script>%s</script>
|
||||
%s
|
||||
<style>{style}</style>
|
||||
<style>{user_style}</style>
|
||||
<script>{script}</script>
|
||||
{head_extra}
|
||||
</head>
|
||||
<body>
|
||||
<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="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>
|
||||
Abrechenbarkeit</a>
|
||||
<a href="/?log">Log</a>
|
||||
<a href="/?products">Products</a>
|
||||
<a href="/?about">About</a>
|
||||
{appname}
|
||||
</a>
|
||||
<a href="/?log">{log}</a>
|
||||
<a href="/?products">{products}</a>
|
||||
<a href="/?about">{about}</a>
|
||||
</nav>
|
||||
]],
|
||||
escape(title),
|
||||
stylesheet, -- style.css
|
||||
themecss, -- theme for user
|
||||
script, -- script.js
|
||||
config.head_extra or ""
|
||||
))
|
||||
]], {
|
||||
title = escape(title),
|
||||
style = stylesheet,
|
||||
user_style = get_user_theme(path and path:sub(2)),
|
||||
script = script,
|
||||
head_extra = config.head_extra or ""
|
||||
}))
|
||||
body()
|
||||
print(string.format(
|
||||
"<script>%s</script></body></html>",
|
||||
themejs
|
||||
))
|
||||
print("</body></html>")
|
||||
end
|
||||
|
||||
local function error_box(message)
|
||||
|
@ -149,13 +185,6 @@ 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
|
||||
|
@ -285,14 +314,15 @@ local function r_user_post(username)
|
|||
end
|
||||
log:flush()
|
||||
log:close()
|
||||
return string.format([[
|
||||
<div class="notif"><p>Transaction successful: <strong class="amount-%s">%.02f€</strong> (%s)</p></div>
|
||||
<audio src="%s" autoplay></audio>
|
||||
]],
|
||||
amount >= 0 and "pos" or "neg", amount / 100,
|
||||
escape(comment),
|
||||
config.transaction_sound or ""
|
||||
)
|
||||
return format([[
|
||||
<div class="notif"><p>Transaction successful: {amount} ({!comment})</p></div>
|
||||
<audio src="{sound}" autoplay></audio>
|
||||
]], {
|
||||
sign = amount >= 0 and "pos" or "neg",
|
||||
amount = format_amount(amount, "strong"),
|
||||
comment = escape(comment),
|
||||
sound = config.transaction_sound or ""
|
||||
})
|
||||
end
|
||||
|
||||
local function r_user(username)
|
||||
|
@ -301,24 +331,20 @@ local function r_user(username)
|
|||
notif = r_user_post(username)
|
||||
end
|
||||
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 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([[
|
||||
<div class="notif"><p><i>This user account does not exist yet. It will only be created after the first transaction.</i></p></div>
|
||||
]])
|
||||
print([[<div class="notif"><p><i>{user.lazy_creation}</i></p></div>]])
|
||||
else
|
||||
print([[<div class="backgroundbox userinfo">]])
|
||||
print(string.format([[
|
||||
Current balance:<br><span class="amount-%s balance-value">%.02f€</span><br>
|
||||
]], balance >= 0 and "pos" or "neg", balance / 100))
|
||||
print(string.format([[
|
||||
Last transaction added %s ago. <a href="/%s?log">View user log</a>
|
||||
]], format_duration(os.time() - last_txn), username))
|
||||
print(format([[{user.balance}: <br>{amount}<br>]],
|
||||
{ sign = balance >= 0 and "pos" or "neg", amount = format_amount(balance, "span", "balance-value") }))
|
||||
print(format([[{user.last_txn} <a href="/{username}?log">{user.view_log}</a>]],
|
||||
{ time = format_duration(os.time() - last_txn), username = urlencode(username) }))
|
||||
print([[</div>]])
|
||||
end
|
||||
print([[<div class="transactions container firstchildlarge">]])
|
||||
|
@ -336,35 +362,35 @@ local function r_user(username)
|
|||
end
|
||||
end
|
||||
print("</div>")
|
||||
print([[
|
||||
print(format([[
|
||||
<form class="transaction box backgroundbox" action="" method="POST">
|
||||
<h3>Create Transaction</h3>
|
||||
<h3>{user.form.transaction}</h3>
|
||||
<label for="amount">Amount (ct): </label>
|
||||
<input type="number" name="amount" id="amount" />
|
||||
<label for="comment">Comment: </label>
|
||||
<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 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 />
|
||||
<label for="pcount">Count: </label>
|
||||
<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 class="amount-neg button" type="submit" value="Buy" />
|
||||
<input class="amount-neg button" type="submit" value="{user.form.buy.submit}" />
|
||||
</form>
|
||||
<form class="transaction box backgroundbox" action="" method="POST" id="buy_product">
|
||||
<h3>Restock Product</h3>
|
||||
<label for="pcount">Count: </label>
|
||||
<h3>{user.form.restock}</h3>
|
||||
<label for="pcount">{field.count}: </label>
|
||||
<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" />
|
||||
<label for="pcode">Product Barcode: </label>
|
||||
<label for="pcode">{field.barcode}: </label>
|
||||
<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>
|
||||
]])
|
||||
]]))
|
||||
print("</div>")
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -35,3 +35,4 @@ user.form.transaction.submit=Update
|
|||
user.form.transaction=Create Transaction
|
||||
user.last_txn=Last transaction added %s.
|
||||
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