read.markets/admin
Giorgio Gilestro 411094d7b8 admin: internal-only superadmin console (users, payments, DB stats)
New independent `admin` service (admin.main:app) on the same image, reusing
app.db/app.models read-only. Never runs migrations or the scheduler; issues
SELECTs only.

- Password-gated (ADMIN_CONSOLE_PASSWORD) with a 12h signed cookie; closed by
  default when the password is empty.
- Bound to 127.0.0.1:8091 (SSH-tunnel access); off the intranet/NPM network.
- Pages: overview stats, user list + search, per-user history/payment detail,
  DB usage (information_schema size + row estimates).
- Compose: base `admin` service (+prod DB-host override, test mount); Dockerfile
  bakes admin/ into runtime + test stages.
- Tests: tests/test_admin_console.py (auth, queries, page wiring) — 12 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:08:20 +02:00
..
templates admin: internal-only superadmin console (users, payments, DB stats) 2026-07-01 16:08:20 +02:00
__init__.py admin: internal-only superadmin console (users, payments, DB stats) 2026-07-01 16:08:20 +02:00
auth.py admin: internal-only superadmin console (users, payments, DB stats) 2026-07-01 16:08:20 +02:00
main.py admin: internal-only superadmin console (users, payments, DB stats) 2026-07-01 16:08:20 +02:00
queries.py admin: internal-only superadmin console (users, payments, DB stats) 2026-07-01 16:08:20 +02:00
README.md admin: internal-only superadmin console (users, payments, DB stats) 2026-07-01 16:08:20 +02:00

Superadmin console

An independent, internal-only web service for the operator: user list with per-user history + payment status, and database usage stats. Runs in its own container (admin service) off the same image as the main app, reusing app.db + app.models, but it is a separate FastAPI app (admin.main:app) that never runs migrations or the scheduler and only ever issues SELECTs.

Access model

  • Bound to 127.0.0.1:8091 on the host — never exposed publicly, not on the intranet/NPM network. Reach it over an SSH tunnel:

    ssh -L 8091:localhost:8091 <vps>
    # then open http://localhost:8091
    
  • Gated by a single shared password, ADMIN_CONSOLE_PASSWORD (in .env). Empty password ⇒ every login is refused (closed by default). The login sets a 12-hour signed cookie (admin_console_session).

Pages

  • / — overview: totals, tier split, paid-active, signups 7/30d, sync count, referral conversions, newest users.
  • /users — full user table with email search + paging.
  • /users/{id} — one user: account, computed paid status, Stripe/Polar linkage, cloud-sync state, referrals sent, feedback votes, digest-email log, legal acknowledgements.
  • /db — per-table row estimates and on-disk size (from information_schema.tables), with each table's share of total.

Deploy (this host runs prod)

Adding a new service means up -d (a plain restart won't create it). Do NOT run a bare docker compose up on this host — always pass the prod overlay:

# 1. set ADMIN_CONSOLE_PASSWORD in .env
# 2. build + create just the admin container (leaves app/scheduler/db running)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build admin

Tests

docker compose -f docker-compose.test.yml run --rm test pytest tests/test_admin_console.py -v