read.markets/admin/README.md
Giorgio Gilestro 83ffa7dbf8 admin: front console via NPM + add purge-test-users CLI
Networking: the superadmin console now mirrors `app` instead of a
loopback-only host port. Base compose drops the host port; the dev
override binds 127.0.0.1:8091; the prod overlay joins the `intranet`
network and listens on :80 with --proxy-headers so NPM can proxy it.

CLI: add `purge-test-users` (dry-run by default, --commit to delete,
--keep allow-list defaulting to the real accounts). Deletes child rows
explicitly (DB-agnostic) plus email-keyed OTPs, so smoke-test signups
that were pointed at prod can be cleaned repeatably instead of via
ad-hoc SQL. Covered by 6 new tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:54:26 +02:00

2.4 KiB

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

  • Dev: bound to 127.0.0.1:8091 on the host (loopback only, from docker-compose.override.yml). Open http://localhost:8091.
  • Prod: no host port. The container joins the intranet network and listens on port 80, so Nginx Proxy Manager fronts it like the main app (upstream readmarkets-admin-1:80). Point an NPM proxy host at it and, ideally, add an NPM access list / basic-auth as a second layer.
  • 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).

The console is now reachable on whatever public hostname NPM maps to it — it is no longer air-gapped behind an SSH tunnel. Keep ADMIN_CONSOLE_PASSWORD strong and prefer adding an NPM access rule in front of it.

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
# 3. in Nginx Proxy Manager: add a proxy host → forward to readmarkets-admin-1
#    port 80 (both containers are on the `intranet` network).

Tests

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