some basic statistics and invite tree, plus watch new data
This commit is contained in:
@@ -154,6 +154,33 @@ def delete_user(db: sqlite3.Connection, handle: str) -> None:
|
||||
db.commit()
|
||||
|
||||
|
||||
def get_member_tree(db: sqlite3.Connection) -> list[dict]:
|
||||
"""Return users with their inviter handle and join timestamp.
|
||||
|
||||
Each entry: {handle, display_name, created_at, invited_by (handle or None)}.
|
||||
Ordered oldest-first so callers can build the tree top-down.
|
||||
"""
|
||||
users = {r["handle"]: r for r in db.execute(
|
||||
"SELECT handle, display_name, created_at FROM users ORDER BY created_at"
|
||||
).fetchall()}
|
||||
# Map invitee → inviter from the used invites
|
||||
invited_by: dict[str, str] = {}
|
||||
for row in db.execute(
|
||||
"SELECT created_by, used_by FROM invites WHERE used_by IS NOT NULL"
|
||||
).fetchall():
|
||||
invited_by[row["used_by"]] = row["created_by"]
|
||||
|
||||
return [
|
||||
{
|
||||
"handle": r["handle"],
|
||||
"display_name": r["display_name"],
|
||||
"created_at": r["created_at"],
|
||||
"invited_by": invited_by.get(r["handle"]),
|
||||
}
|
||||
for r in users.values()
|
||||
]
|
||||
|
||||
|
||||
def count_users(db: sqlite3.Connection) -> int:
|
||||
"""Return the total number of registered users."""
|
||||
row = db.execute("SELECT COUNT(*) FROM users").fetchone()
|
||||
|
||||
@@ -29,6 +29,7 @@ from bincio.serve.db import (
|
||||
create_user,
|
||||
delete_session,
|
||||
get_invite,
|
||||
get_member_tree,
|
||||
get_session,
|
||||
get_setting,
|
||||
get_user,
|
||||
@@ -173,6 +174,27 @@ async def me(bincio_session: Optional[str] = Cookie(default=None)) -> JSONRespon
|
||||
})
|
||||
|
||||
|
||||
@app.get("/api/stats")
|
||||
async def stats() -> JSONResponse:
|
||||
"""Public endpoint: member count, join dates, and invitation tree."""
|
||||
import time as _time
|
||||
now = int(_time.time())
|
||||
members = get_member_tree(_get_db())
|
||||
return JSONResponse({
|
||||
"user_count": len(members),
|
||||
"members": [
|
||||
{
|
||||
"handle": m["handle"],
|
||||
"display_name": m["display_name"],
|
||||
"member_since": m["created_at"],
|
||||
"member_for_days": (now - m["created_at"]) // 86400,
|
||||
"invited_by": m["invited_by"],
|
||||
}
|
||||
for m in members
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
async def login(request: Request) -> JSONResponse:
|
||||
ip = request.client.host if request.client else "unknown"
|
||||
|
||||
Reference in New Issue
Block a user