Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
108 lines
6.8 KiB
Markdown
108 lines
6.8 KiB
Markdown
# 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`.
|