diff options
Diffstat (limited to 'abrechenbarkeit.lua')
-rwxr-xr-x | abrechenbarkeit.lua | 396 |
1 files changed, 221 insertions, 175 deletions
diff --git a/abrechenbarkeit.lua b/abrechenbarkeit.lua index c175526..7de97ac 100755 --- a/abrechenbarkeit.lua +++ b/abrechenbarkeit.lua @@ -17,18 +17,36 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. ]] -- +-- replace german chars with a better %w that allows unicode +local matchers = { + time = "(%d+)", + user = "([%w_@ -öäüÖÄÜßẞ]+)", + amount = "(-?%d+)", + comment = "([%w_ -öäüÖÄÜßẞ]*)", + barcode = "([%w_-]*)", + name = "([%w_ -öäüÖÄÜßẞ]*)", +} +local matchers_global = (function() + local s = {} + for k, v in pairs(matchers) do s[k] = ("^%s$"):format(v) end + return s +end)() + 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", " ") + local t, _ = s:gsub("+", " "):gsub("%%(%x%x)", + function(cap) return string.char(tonumber(cap, 16)) end) + return t end local function urlencode(s) if s == nil then return nil end - return s:gsub(" ", "%%20") + return s:gsub("[^%w]", + function(cap) return string.format("%02x", string.byte(cap, 1)) end) end local function parse_query(q) @@ -72,7 +90,7 @@ local function load_translations(langs) if l ~= "" then local key, value = string.match(l, "^([^=]+)=([^=]*)") if key ~= nil and value ~= nil then - t[key] = value + t["+" .. key] = value end end end @@ -93,18 +111,23 @@ local script = io.open("script.js"):read("a") local function format(template, params) params = params or {} if template == nil then return "NIL TEMPLATE" end - return string.gsub(template, "{([%w_\\.!]+)}", function(n) - local raw = n:sub(1, 1) ~= "!" - if not raw then n = n:sub(2) end - local s = format(params[n] or translations[n] or "NIL PARAM", params) - return raw and s or escape(s) + local s, _ = string.gsub(template, "{([%w\\+_\\.!]+)}", function(n) + local esc = n:sub(1, 1) == "!" + if esc then n = n:sub(2) end + local s = params[n] or translations[n] or "NIL PARAM" + if not esc then s = format(s, params) end + return esc and escape(s) or s end) + return s end local function format_amount(amount, tag, classes) - local s = string.format("%.02f%s", amount / 100, config.unit or "€") + local s = string.format("%s%.02f%s", amount > 0 and "+" or "", 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", classes or "", s, tag) + return format( + [[<{tag} class="amount-{sign} {classes}">{content}</{tag}>]], + { tag = tag, sign = amount >= 0 and "pos" or "neg", classes = classes or "", content = s } + ) end local function get_user_theme(username) @@ -123,10 +146,23 @@ local function get_user_theme(username) 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) + local unit = nil + local n = nil + if t > 86400 then + n = math.floor(t / 86400) + unit = "day" + elseif t > 3600 then + n = math.floor(t / 3600) + unit = "hour" + elseif t > 60 then + n = math.floor(t / 60) + unit = "minute" + else + n = t + unit = "second" + end + return format("{+time.delta_past}", + { n = n, unit = translations["+time." .. unit .. (n ~= 1 and "s" or "")] }) end local function respond(status, title, body) @@ -150,11 +186,11 @@ 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> - {appname} + {+appname} </a> - <a href="/?log">{log}</a> - <a href="/?products">{products}</a> - <a href="/?about">{about}</a> + <a href="/?log">{+log}</a> + <a href="/?products">{+products}</a> + <a href="/?about">{+about}</a> </nav> ]], { title = escape(title), @@ -168,7 +204,7 @@ local function respond(status, title, body) end local function error_box(message) - return string.format([[<div class="notif error"><p>Error: %s</p></div>]], message) + return string.format([[<div class="notif error"><p>Error: %s</p></div>]], escape(message)) end local function respond_error(message) @@ -198,9 +234,9 @@ local function read_log() if l == "" or l == nil then return nil end - local time, username, amount, pcode, pcount, comment = string.match(l, - "(%d+),([%w_ -]+),(-?%d+),([%w_-]*),(-?%d*),([%w_ -]*)") - return tonumber(time), username, tonumber(amount), pcode, tonumber(pcount), comment + local time, user_src, user_dst, amount, pcode, pcount, comment = string.match(l, + format("{time},{user},{user},{amount},{barcode},{amount},{comment}", matchers)) + return tonumber(time), user_src, user_dst, tonumber(amount), pcode, tonumber(pcount), comment end end @@ -215,22 +251,23 @@ local function read_products() if l == "" or l == nil then return nil end - local barcode, price, name, owner = string.match(l, "([%w_-]+),(-?%d+),([%w_ -]*),([%w_ -]*)") - return barcode, tonumber(price), name, owner + local barcode, price, user, name = string.match(l, "{barcode},{amount},{user},{name}") + return barcode, tonumber(price), user, name end end local function balances() local users = {} - for _, username, amount, _, _, _ in read_log() do - users[username] = (users[username] or 0) + amount + for _, user_src, user_dst, amount, _, _, _ in read_log() do + users[user_src] = (users[user_src] or 0) - amount + users[user_dst] = (users[user_dst] or 0) + amount end return users end local function product_balances() local products = {} - for _, _, _, pcode, pcount, _ in read_log() do + for _, _, _, _, pcode, pcount, _ in read_log() do if pcode ~= nil and pcount ~= nil then products[pcode] = (products[pcode] or 0) + pcount end @@ -240,19 +277,25 @@ end local function last_txns() local users = {} - for time, username, _, _, _, _ in read_log() do - users[username] = time + for time, user_src, user_dst, _, _, _, _ in read_log() do + users[user_src] = time + users[user_dst] = time end return users end local function get_active_users() local user_balances = {} - for time, username, amount, _, _, _ in read_log() do - user_balances[username] = { + for time, user_src, user_dst, amount, _, _, _ in read_log() do + user_balances[user_src] = { + time = time, + user_src = user_src, + balance = (user_balances[user_src] or { balance = 0 }).balance - amount + } + user_balances[user_dst] = { time = time, - username = username, - balance = (user_balances[username] or { balance = 0 }).balance + amount + user_dst = user_dst, + balance = (user_balances[user_dst] or { balance = 0 }).balance + amount } end @@ -267,62 +310,57 @@ end local function r_user_post(username) local data = form_data() + local user_src = data.user_src or username + local user_dst = data.user_dst local amount = tonumber(data.amount) + local pcode = data.pcode + local pcount = tonumber(data.pcount) local comment = data.comment - local pcode = nil - local pcount = nil - local powner = nil - local powner_comment = nil - if data.pcode then - for p_barcode, p_amount, p_name, p_owner in read_products() do - if p_barcode == data.pcode then - powner = p_owner + if pcode then + local exists = false + for p_barcode, p_amount, p_user, p_name in read_products() do + if p_barcode == pcode then pcount = (tonumber(data.pcount) or 1) * (data.negate_pcount ~= nil and -1 or 1) - pcode = p_barcode - if amount == nil then amount = pcount * p_amount end - if comment == nil then - comment = string.format("%s %d %s", pcount < 0 and "Buy" or "Restock", - math.abs(pcount), p_name) - - powner_comment = string.format("%s %d %s %s %s", - pcount < 0 and "Sell" or "Restock", - math.abs(pcount), p_name, - pcount < 0 and "to" or "by", - username) - end + amount = amount or pcount * p_amount + user_dst = user_dst or p_user + comment = comment or + string.format("%s %d %s", pcount < 0 and "Buy" or "Restock", math.abs(pcount or 0), p_name) + exists = true end end - if amount == nil then + if not exists then return error_box("unknown product") end end + user_dst = user_dst or "@global" if amount == nil then return error_box("amount invalid") end - if comment == nil or comment:match("^[%w_ -]*$") == nil then + if comment == nil or comment:match(matchers_global.comment) == nil then return error_box("comment invalid") end + if user_src == nil or user_src:match(matchers_global.user) == nil then + return error_box("source user invalid") + end + if user_dst == nil or user_dst:match(matchers_global.user) == nil then + return error_box("destination user invalid") + end local log = io.open("log", "a+") if log == nil then return error_box("failed to open log") end local time = os.time() - -- subtract from buyer - log:write(string.format("%d,%s,%d,%s,%s,%s\n", time, username, amount, pcode or "", pcount or "", comment)) - -- add to owner - if powner then - -- count is always zero as doesn't affect stock - log:write(string.format("%d,%s,%d,%s,%s,%s\n", time, powner, -amount, pcode or "", "", powner_comment)) - end + log:write(string.format("%d,%s,%s,%d,%s,%s,%s\n", + time, user_src, user_dst, amount, pcode or "", pcount or "", comment)) log:flush() log:close() return format([[ - <div class="notif"><p>Transaction successful: {amount} ({!comment})</p></div> + <div class="notif"><p>{+user.form.transaction.success}: {amount} ({!comment})</p></div> <audio src="{sound}" autoplay></audio> ]], { sign = amount >= 0 and "pos" or "neg", amount = format_amount(amount, "strong"), - comment = escape(comment), + comment = comment, sound = config.transaction_sound or "" }) end @@ -340,12 +378,12 @@ local function r_user(username) balance = balance or 0 if notif then print(notif) end if new_user then - print([[<div class="notif"><p><i>{user.lazy_creation}</i></p></div>]]) + print(format([[<div class="notif"><p><i>{+user.lazy_creation}</i></p></div>]])) else print([[<div class="backgroundbox userinfo">]]) - 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>]], + print(format([[{+user.balance}: <br>{amount}<br>]], + { 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 @@ -353,44 +391,46 @@ local function r_user(username) print([[<div class="amount-presets backgroundbox">]]) for _, type in ipairs({ 1, -1 }) do for _, amount in ipairs({ 50, 100, 150, 200, 500, 1000 }) do - print(string.format([[ - <form action="" method="POST"> - <input type="number" name="amount" id="amount" value="%d" hidden /> - <input type="text" name="comment" id="comment" value="" hidden /> - <input type="submit" value="%s%.02f€" class="amount-%s button" /> - </form> - ]], amount * type, ({ [-1] = "-", [1] = "+" })[type], amount / 100, - ({ [-1] = "neg", [1] = "pos" })[type])) + local a = amount * type + print(format([[<form action="" method="POST"> + <input type="number" name="amount" id="amount" value="{a_raw}" hidden /> + <input type="text" name="comment" id="comment" value="" hidden /> + <input type="submit" value="{amount}" class="amount-{sign} button" /> + </form>]], { + a_raw = a, + amount = format_amount(a), + sign = a < 0 and "neg" or "pos" + })) end end print("</div>") print(format([[ <form class="transaction box backgroundbox" action="" method="POST"> - <h3>{user.form.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="{user.form.transaction.submit}" 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>{user.form.buy}</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">{field.barcode}: </label> + <label for="pcode">{+field.barcode}: </label> <input type="text" name="pcode" id="pcode" /> - <input class="amount-neg button" type="submit" value="{user.form.buy.submit}" /> + <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>{user.form.restock}</h3> - <label for="pcount">{field.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">{field.upstream_price}: </label> + <label for="amount">{+field.upstream_price}: </label> <input type="number" name="amount" id="amount" /> - <label for="pcode">{field.barcode}: </label> + <label for="pcode">{+field.barcode}: </label> <input type="text" name="pcode" id="pcode" /> - <input type="submit" value="{user.form.restock.submit}" class="button amount-pos" /> + <input type="submit" value="{+user.form.restock.submit}" class="button amount-pos" /> </form> ]])) print("</div>") @@ -399,46 +439,52 @@ end local function r_log(filter) return respond(200, "Abrechnungen", function() - print([[<table class="log" printtitle="Abrechnung"]]) - print([[<thead><tr> - <th>Time</th> - <th>Username</th> - <th>Amount</th> - <th>P.-Barcode</th> - <th>P.-Count</th> - <th>Comment</th> - <th>Actions</th> - </tr></thead>]]) + print([[<table class="log"]]) + print(format([[<thead><tr> + <th>{+field.time}</th> + <th>{+field.username}</th> + <th>{+field.amount}</th> + <th>{+field.barcode}</th> + <th>{+field.count}</th> + <th>{+field.comment}</th> + <th>{+log.actions}</th> + </tr></thead>]])) print("<tbody>") - for time, username, amount, pcode, pcount, comment in read_log() do - if filter == nil or filter == username then - print(string.format([[ + for time, user_src, user_dst, amount, pcode, pcount, comment in read_log() do + if filter == nil or filter == user_src or filter == user_dst then + print(format([[ <tr> - <td>%s (%s ago)</td> - <td>%s</td> - <td class="amount-%s">%.02f€</td> - <td>%s</td> - <td>%s %s</td> - <td>%s</td> + <td>{time} ({time_delta})</td> + <td>{user_src} → {user_dst</td> + {amount} + <td>{pcode}</td> + <td>{pcount}</td> + <td>{comment}</td> <td> - <form action="/%s" method="POST"> - <input type="number" name="amount" id="amount" value="%d" hidden /> - <input type="text" name="comment" id="comment" value="Revert %s" hidden /> + <form action="/?transaction" method="POST"> + <input type="number" name="user_src" value="{user_src}" hidden /> + <input type="number" name="user_dst" value="{user_dst}" hidden /> + <input type="number" name="amount" value="{revert_amount}" hidden /> + <input type="number" name="pcode" value="{pcode}" hidden /> + <input type="number" name="pcount" value="{revert_pcount}" hidden /> + <input type="text" name="comment" value="Revert {comment}" hidden /> <input type="submit" class="amount-ntr button" value="Revert" /> </form> </td> </tr> - ]], - os.date("!%Y-%m-%dT%H:%M:%SZ", time), format_duration(os.time() - time), - escape(username), - amount >= 0 and "pos" or "neg", amount / 100, - escape(pcode) or "", - pcount and (pcount < 0 and "buy" or "stock") or "", pcount and tostring(math.abs(pcount)) or "", - escape(comment), - escape(username), - -amount, - escape(comment) - )) + ]], { + time = os.date("!%Y-%m-%dT%H:%M:%SZ", time), + time_delta = format_duration(os.time() - time), + user_src = escape(user_src), + user_dst = escape(user_dst), + amount = format_amount(amount, "td"), + pcode = escape(pcode), + pcount = (pcount and (pcount < 0 and "buy " or "stock ") or "") .. + (pcount and tostring(math.abs(pcount)) or ""), + comment = escape(comment), + revert_amount = -amount, + revert_pcount = -pcount, + })) end end print("</tbody>") @@ -448,40 +494,35 @@ end local function r_index() return respond(200, "Abrechenbarkeit", function() - print([[ + print(format([[ <form action="/" method="GET" id="user_creation"> - <h3>User Creation</h3> - <label for="username">Username: </label> + <h3>{+index.form.create_user}</h3> + <label for="username">{+field.username}: </label> <input type="text" name="create_user" id="username" /> - <input type="submit" value="Continue" class="button amount-ntr" /> + <input type="submit" value="{+index.form.create_user.submit}" class="button amount-ntr" /> </form> - ]]) - print([[<div class="userlist" printtitle="User List, is still work in progress :/"></div>]]) -- for printing + ]])) + print([[<div class="userlist"></div>]]) -- for printing print([[<ul class="userlist">]]) for _, user in ipairs(get_active_users()) do - print(string.format([[ - <li><a href="/%s"><span class="name">%s</span> <span class="amount amount-%s">%.02f€</span></a></li> - ]], - urlencode(user.username), - escape(user.username), - user.balance >= 0 and "pos" or "neg", user.balance / 100 - )) + print(format([[<li> + <a href="/{username_url}"> + <span class="name">{!username}</span> + {balance} + </a> + </li>]], { + username_url = urlencode(user.username), + username = user.username, + balance = format_amount(user.balance, "span") + })) end print("</ul>") end) end -local function validate_username(username) - -- disallow leading or traling whitespace - return username ~= nil - and username:match("^([%w_ -]+)$") ~= nil - and username:match("^%s") == nil - and username:match("%s$") == nil -end - local function r_create_user() local username = query.create_user - if not validate_username(username) then + if username:match(matchers_global.user) == nil then return respond_error("invalid username " .. username) end return redirect(string.format("/%s", urlencode(username))) @@ -490,10 +531,7 @@ end local function r_products_post() local data = form_data() local barcode = data.barcode - if barcode == nil then - return error_box("barcode unset") - end - if barcode:match("^[%w_-]*$") == nil then + if barcode == nil or barcode:match("^[%w_-]*$") == nil then return error_box("barcode invalid") end if data.delete then @@ -501,9 +539,9 @@ local function r_products_post() if new_products == nil then return error_box("failed to open new products") end - for a_barcode, price, name, owner in read_products() do + for a_barcode, price, user, name in read_products() do if barcode ~= a_barcode then - new_products:write(string.format("%s,%d,%s,%s\n", a_barcode, price, name, owner)) + new_products:write(string.format("%s,%d,%s,%s\n", a_barcode, price, user, name)) end end new_products:flush() @@ -512,21 +550,21 @@ local function r_products_post() else local price = tonumber(data.price) local name = data.name + local user = data.user if price == nil then return error_box("price invalid") end - if name:match("^[%w_ -]*$") == nil then + if name == nil or name:match(matchers_global.name) == nil then return error_box("name invalid") end + if user == nil or user:match(matchers_global.user) == nil then + return error_box("user invalid") + end local products = io.open("products", "a+") if products == nil then return error_box("failed to open products") end - local owner = data.owner or "" - if name:match("^[%w_ -]*$") == nil then - return error_box("owner invalid") - end - products:write(string.format("%s,%d,%s,%s\n", barcode, price, name, owner)) + products:write(string.format("%s,%d,%s,%s\n", barcode, price, user, name)) products:flush() products:close() end @@ -548,8 +586,8 @@ local function r_products() <input type="text" name="name" id="name" /> <label for="price">Price (ct): </label> <input type="number" name="price" id="price" /> - <label for="owner">Owner: </label> - <input type="text" name="owner" id="owner" /> + <label for="user">User: </label> + <input type="text" name="user" id="user" /> <label for="barcode">Barcode: </label> <input type="text" name="barcode" id="barcode" /> <input type="submit" value="Add" class="amount-ntr button" /> @@ -568,19 +606,23 @@ local function r_products() <th>Price</th> <th>Barcode</th> <th>Count</th> - <th>Owner</th> + <th>User</th> </tr>]]) local pbals = product_balances() - for barcode, price, name, owner in read_products() do - print(string.format([[ - <tr><td>%s</td><td class="amount-%s">%.02f€</td><td>%s</td><td>%s</td><td>%s</td></tr> - ]], - name, - -price >= 0 and "pos" or "neg", -price / 100, - barcode, - pbals[barcode] or "0", - owner - )) + for barcode, price, user, name in read_products() do + print(string.format([[<tr> + <td>{!name}</td> + {price} + <td>{!barcode}</td> + <td>{!count}</td> + <td>{!user}</td> + </tr>]], { + name = name, + price = format_amount(-price), + barcode = barcode, + count = pbals[barcode] or "0", + user = user, + })) end print("</table>") end) @@ -588,13 +630,17 @@ end local function r_about() respond(200, "About Abrechenbarkeit", function() - print([[ - <h1>About Abrechenbarkeit</h1> - <p>Abrechenbarkeit is a simple trust-based ledger for keeping track of money spent on product.</p> - <p>Abrechenbarkeit is free software. It is licensed exclusively GNU Affero General Public License Version 3 only.<p> - <p>The source code is published on <a href="https://codeberg.org/metamuffin/strichliste">Codeberg</a>. This is also where <a href="https://codeberg.org/metamuffin/strichliste/issues">issues with this software</a> should be reported.</p> - <p>Thanks for choosing Abrechenbarkeit.</p> - ]]) + print(format([[ + <h1>{+about.title}</h1> + <p>{+about.desc}</p> + <p>{+about.license}<p> + <p>{+about.source}</p> + <p>{+about.thanks}</p> + ]], { + issues = [[<a href="https://codeberg.org/metamuffin/strichliste/issues">]], + codeberg = [[<a href="https://codeberg.org/metamuffin/strichliste">]], + ae = [[</a>]], + })) end) end @@ -603,7 +649,7 @@ local function extract_username() return respond_error("no path") end local username = urldecode(path:sub(2)) - if username == nil or username:match("^([%w_ -]+)$") == nil then + if username == nil or username:match(matchers_global.user) == nil then return nil end return username @@ -623,7 +669,7 @@ if path == "/" then end else local username = extract_username() - if username == nil or not validate_username(username) then + if username == nil then return respond_error("username invalid") elseif query.log then return r_log(username) |