Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project overview
SmartUp is a web dashboard for monitoring API upstream rate groups and sending change notifications via webhooks (generic JSON / DingTalk bot). It supports multiple upstream auth types, periodic rate checking with APScheduler, snapshot diffing, and balance tracking with cost analysis.
Sibling directories (new-api/, nox-api/, sub2api/) are the Go-based AI API gateways that SmartUp monitors (upstreams) and syncs to (downstreams/websites). They are independent Go projects, not part of the SmartUp Python app. browser-extension/ is a Chrome Manifest V3 extension for capturing auth credentials from real browser sessions.
Tech stack
- Backend: FastAPI (Python 3.12) + SQLAlchemy 2.0 (ORM, SQLite) + APScheduler
- Frontend: Vue 3 + Element Plus + Vite + Pinia + ECharts
- Deploy: Docker Compose single-container (multi-stage build: Node → Python)
Commands
# Backend dev
cd backend && source ../.venv/bin/activate
uvicorn app.main:app --reload --port 8000
# Run all backend tests
.venv/bin/pytest backend/ -q
# Run a single test file
.venv/bin/pytest backend/test_upstream_token_refresh.py -q
# Run a specific test
.venv/bin/pytest backend/test_upstream_token_refresh.py::test_bearer_proactive_refresh_when_expiry_approaching -q
# Frontend dev
cd frontend && npm run dev # proxies to localhost:8000
# Docker (from repo root)
make up # start existing container
make up-build # rebuild image then start
make down log # stop / view logs
The .venv at repo root is the backend venv. Frontend uses its own node_modules.
Architecture
Backend (backend/app/)
Entry point: main.py — FastAPI app with CORS, lifespan (init DB, create admin, start scheduler), mount SPA static files at /, API docs at /api/docs.
Layers:
routers/— FastAPI route handlers (thin, call services). Prefixes:/api/upstreams,/api/websites,/api/webhooks,/api/logs,/api/auth,/api/custom-pages,/api/external-api-logs,/api/finance,/api/auth-captureservices/— Business logic. Key services:upstream_client.py(~1150 lines) — HTTP client for all upstream API calls. Auth types:none,bearer,api_key,login_password,new_api_token,nox_token,cookie. Handles rate/balance fetching, token refresh, proactive bearer refreshscheduler.py— APScheduler background checks. Calls_check_upstream()which fetches rates, diffs snapshots, triggers webhookssnapshot_service.py— Rate snapshot diffing and pruningwebhook_service.py— Send notifications (generic JSON / DingTalk with HMAC signature)auth_config.py— Auth config masking, key whitelist per auth_typefinance_service.py— Daily cost tracking (usage_stats or balance_delta mode)website_client.py/website_sync.py— Website group management and sync
models/— SQLAlchemy 2.0 ORM (mapped_columnstyle). Key models:Upstream,UpstreamRateSnapshot,WebhookConfig,NotificationLog,Website,CustomPage,UpstreamGeneratedKey,UpstreamRechargeEvent,FinanceDailySummaryschemas/— Pydantic request/response schemasutils/— JWT auth (auth.py), DingTalk HMAC signature (dingtalk.py), number formatting
Database: SQLite with WAL mode, 64MB mmap, 20MB page cache. Schema migrations are handled inline in database.py (_migrate_* functions) — no Alembic. New columns/indexes are added via ALTER TABLE IF NOT EXISTS patterns.
Auth: JWT-based (python-jose). Admin user created on first startup from ADMIN_EMAIL/ADMIN_PASSWORD env vars. Each startup syncs the admin password hash from .env — changing .env admin password takes effect on restart.
Upstream auth types (in upstream_client.py):
| Type | Config fields | Refresh behavior |
|---|---|---|
bearer |
token, refresh_token, expires_in, token_expires_at, refresh_path | Proactive 1h before expiry + 401 fallback |
login_password |
email, password | Refresh via refresh_token, fallback to re-login |
api_key |
key, header | None |
new_api_token / nox_token |
token, user_id, provider | None |
cookie |
cookie_string | None |
Scheduler: Single-threaded BackgroundScheduler. Jobs are added/removed dynamically when upstreams change. Each upstream has its own check_interval_seconds.
Frontend (frontend/src/)
- Router: Vue Router with auth guard (
requiresAuthmeta). Routes:/login,/upstreams,/websites,/webhooks,/logs,/external-api-logs,/custom-pages,/finance - State: Pinia
authStore— token + email in localStorage - API: Axios with retry (3x exponential backoff, GET/HEAD only). 401 interceptor clears auth + redirects to login. Base URL
/(same-origin in Docker) - UI: Element Plus component library, ECharts for charts, Lucide icons
- Views: Each major feature has its own view component (Upstreams, Websites, Webhooks, Finance, etc.)
Deployment
- Dockerfile: Two-stage — Node 20 builds Vue app, Python 3.12-slim runs uvicorn. Frontend dist copied to
/app/static. tini as PID 1. - docker-compose.yml: Single
smartupservice, portSERVER_PORT(default 8899), volume mounts./datafor SQLite persistence, healthcheck on/healthz
Key patterns
- Auth config persistence:
UpstreamClienttakeson_auth_config_updatecallback. The scheduler's callback persists refreshed tokens to DB vianormalize_auth_config(). - 401 retry in client:
_send_request()catches 401 and calls the appropriate refresh method, then retries once. For bearer, this is_refresh_bearer_token(); for login_password, it tries refresh then re-login. - Snapshot diffing: Rates fetched from upstream →
build_snapshot()→ compare to latest snapshot viadiff_snapshots()→ fire webhooks on changes. - Secret masking:
auth_config.pydefinesMASKsentinel. Sensitive fields are replaced with***in API responses.normalize_auth_config()strips masks before saving.
Testing
Tests live alongside backend code as backend/test_*.py. They use pytest with monkeypatch for dependency injection. Test HTTP clients use a FakeHttpClient pattern (see test_upstream_token_refresh.py) that records calls and returns canned responses. No test database — tests mock at the HTTP layer.
Environment variables
See .env.example. Required: ADMIN_PASSWORD, JWT_SECRET. Optional: SERVER_PORT (8899), UNHEALTHY_THRESHOLD (3), TZ (Asia/Shanghai), CORS_ORIGINS.