# 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. When the Python app's interaction with upstream/downstream APIs is unclear — e.g. auth flows, response shapes, endpoint behavior — read the relevant Go source in `new-api/`, `nox-api/`, or `sub2api/` to understand what the upstream/downstream expects. ## 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 ```bash # 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-capture` - `services/` — 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 refresh - `scheduler.py` — APScheduler background checks. Calls `_check_upstream()` which fetches rates, diffs snapshots, triggers webhooks - `snapshot_service.py` — Rate snapshot diffing and pruning - `webhook_service.py` — Send notifications (generic JSON / DingTalk with HMAC signature) - `auth_config.py` — Auth config masking, key whitelist per auth_type - `finance_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_column` style). Key models: `Upstream`, `UpstreamRateSnapshot`, `WebhookConfig`, `NotificationLog`, `Website`, `CustomPage`, `UpstreamGeneratedKey`, `UpstreamRechargeEvent`, `FinanceDailySummary` - `schemas/` — Pydantic request/response schemas - `utils/` — 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 (`requiresAuth` meta). 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 `smartup` service, port `SERVER_PORT` (default 8899), volume mounts `./data` for SQLite persistence, healthcheck on `/healthz` ## Key patterns - **Auth config persistence**: `UpstreamClient` takes `on_auth_config_update` callback. The scheduler's callback persists refreshed tokens to DB via `normalize_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 via `diff_snapshots()` → fire webhooks on changes. - **Secret masking**: `auth_config.py` defines `MASK` sentinel. 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`.