Add accounts, presets, live sheet panel and equipment tiles

- Login with registration code, sessions, rate limiting, per-user characters
- Share characters with other users (read-only), admin CLI
- Rules presets with a Presets tab, applied/pending changes with diff in a side sheet
- Equipment as tiles with amounts, starting kit removed
- Rename nav tab to "Character", add "New character" button
- Stop tracking _to_delete/

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
Claude
2026-10-20 20:37:09 +00:00
parent afbdc4b139
commit 5f3b2b2874
27 changed files with 857 additions and 244 deletions
+45
View File
@@ -0,0 +1,45 @@
"""Small admin CLI: python -m app.admin list | passwd <user> | delete <user>"""
import getpass
import sys
from . import auth
from .main import db
def main(argv):
if not argv:
print(__doc__)
return 1
cmd = argv[0]
with db() as con:
if cmd == "list":
for r in con.execute("SELECT username, created FROM users ORDER BY id"):
print(r["username"])
elif cmd == "passwd" and len(argv) == 2:
pw = getpass.getpass("New password: ")
if len(pw) < auth.MIN_PASSWORD:
print(f"Password must have at least {auth.MIN_PASSWORD} characters.")
return 1
cur = con.execute("UPDATE users SET pw_hash=? WHERE username=?", (auth.hash_password(pw), argv[1]))
if not cur.rowcount:
print("No such user.")
return 1
con.execute("DELETE FROM sessions WHERE user_id=(SELECT id FROM users WHERE username=?)", (argv[1],))
print("Password changed, user logged out everywhere.")
elif cmd == "delete" and len(argv) == 2:
row = con.execute("SELECT id FROM users WHERE username=?", (argv[1],)).fetchone()
if not row:
print("No such user.")
return 1
con.execute("DELETE FROM sessions WHERE user_id=?", (row["id"],))
con.execute("DELETE FROM shares WHERE user_id=?", (row["id"],))
con.execute("DELETE FROM users WHERE id=?", (row["id"],))
print("User deleted. Their characters and presets stay on the server without an owner.")
else:
print(__doc__)
return 1
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))