NumberVault
Python-only operations platform for a legitimate phone-number inventory shop:
- aiogram 3 Telegram bot β mandatory channel membership, service/country browsing, 5 listings per page, random number, reservation, UPI QR + payment instructions, screenshot upload, account and order history.
- Gradio 6 admin dashboard β overview, inventory + bulk TXT import, orders and manual payment review, customers, pricing rules, analytics, broadcasts, settings, backup/restore, activity logs and system health.
- FastAPI
/healthβ public UptimeRobot-friendly probe. Database failure returns HTTP 503; degraded states (no bucket, stale backup, bot offline) return HTTP 200 with"status": "degraded".
Storage model (important)
The live SQLite database is local (DATA_DIR), never on the bucket. The
Hugging Face bucket is object storage mounted at /data; it does not
provide the locking/atomic-rename behaviour SQLite needs. The bucket is used
only for consistent SQLite snapshots, manifests and payment-proof files.
Write transactions use SQLite WAL, synchronous=FULL, BEGIN IMMEDIATE,
busy_timeout=30000 and bounded retries. Reservations use conditional
UPDATE ... WHERE status='AVAILABLE' plus partial unique indexes, so two
buyers cannot reserve one number.
On startup the app verifies the local DB. If it is missing/corrupt it restores the newest verified backup from the bucket. Backups are created after commits (debounced), periodically, and at shutdown. RPO is the backup interval (or the last successful post-commit backup); local-only downtime between container replacement and restore is the honest limitation of ephemeral Space storage. Use persistent hardware if zero-RPO is required.
Configuration
Set these as Space secrets:
| Secret | Required | Notes |
|---|---|---|
ADMIN_PASSWORD |
yes | β₯10 chars, not a default. Or set ADMIN_PASSWORD_HASH. |
TELEGRAM_BOT_TOKEN |
for bot | Without it the dashboard and /health still work; bot status is disabled. |
ADMIN_TELEGRAM_IDS |
for bot admin | Comma-separated Telegram user IDs. |
ADMIN_CHAT_ID |
recommended | Chat that receives payment-review notifications. |
PAYMENT_UPI |
for payments | UPI VPA, e.g. shop@bank. |
REQUIRED_CHANNELS |
no | Comma-separated channel usernames without @. |
Space variables (non-secret): ENV=production, DATA_DIR=/tmp/numbervault,
BUCKET_MOUNT=/data, CURRENCY=INR, TIMEZONE=Asia/Kolkata.
Mount the bucket as a volume:
hf spaces volumes set nezuhub/number-vault \
-v hf://buckets/nezuhub/numbervault-storage:/data
The dashboard is protected by server-side Gradio auth. Secrets are never stored in SQLite or rendered in the UI.
Local run
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
cp config.example.env .env
# edit .env (set ENV=test for a quick local password bypass)
uvicorn app:app --host 0.0.0.0 --port 7860
curl -fsS http://127.0.0.1:7860/health | python -m json.tool
The dashboard is at /. /healthz is a minimal liveness probe; /status is a
small runtime status document.
Inventory import
Format, one number per line:
+919876543210 / 50
+14155552671 / 12.50
+ and country code are mandatory. Country/ISO is detected with
phonenumbers; price is parsed as decimal and stored as integer minor units.
Preview reports line-level valid/duplicate/invalid rows and offers a CSV error
report. Commit re-validates and uses ON CONFLICT DO NOTHING, so a concurrent
import cannot create duplicates.
Tests
pytest -q
The suite covers phone validation/country detection, import preview/commit and
dedupe, 5-per-page browsing, concurrent reservations, one-active-order-per-user,
payment approval/rejection, reservation expiry, settings validation, password
hashing/authorization, backup verify/restore, cold-start recovery, dashboard
construction and FastAPI health endpoints. See TEST_RESULTS.md.
Security / scope
- Manual payment review; no account-session resale, credential distribution or authentication-code relay is implemented or supported.
- Only JPEG/PNG/WebP payment screenshots are accepted, with server-side size and magic-byte validation.
- Dashboard login is verified server-side against a PBKDF2 hash (or a production-length plaintext secret from the environment).
- Uploaded proofs are written to local disk and mirrored to the bucket; the live database is never exposed through Gradio file paths.