feat: add JWT authentication and AGENTS.md

This commit is contained in:
祀梦
2026-05-17 11:21:41 +08:00
parent 40eb2dadb0
commit 3c03866021
19 changed files with 554 additions and 1632 deletions

81
AGENTS.md Normal file
View File

@@ -0,0 +1,81 @@
# AGENTS.md
## Project overview
- Full-stack todo app: **Vue 3 + Element Plus** (WebUI/) + **FastAPI + SQLAlchemy** (api/)
- **Python 3.10+, Node 18+**; SQLite database
- Single `master` branch, no CI/CD
## Quick commands
```powershell
# Install Python deps (required before first run)
pip install -r requirements.txt
# Full-stack one-shot (builds frontend, then starts backend on :23994)
python main.py
# Frontend dev only (hot-reload on :5173, proxies /api → :23994)
cd WebUI; npm run dev
# Backend only
cd api; uvicorn app.main:app --host 0.0.0.0 --port 23994
# Type-check frontend (noEmit; uses project references tsconfig)
cd WebUI; npm run typecheck
# Run the one hand-written test (backend must be running on :23994)
python tests/test_accounts.py
```
**Build order matters:** `python main.py` automatically compiles WebUI (`npm install` + `npm run build`), copies `WebUI/dist/``api/webui/`, then starts uvicorn. It checks timestamps to skip rebuilds when frontend is unchanged.
**Import path:** `main.py` injects `api/` into `sys.path` (no `os.chdir`). Backend imports resolve relative to `api/` — e.g. `from app.config import ...`, not `from api.app.config import ...`.
**Port conflict:** `main.py` will auto-kill any process already listening on port 23994 before starting. If you have another instance running, it will be terminated without warning.
## Architecture
### Backend (`api/app/`)
- **Config is hardcoded** in `api/app/config.py` — no `.env`, no `os.getenv()`. Port `23994`, CORS origins `["http://localhost:5173", "http://localhost:23994"]`. Note: `pydantic-settings` is in `requirements.txt` but unused.
- **SQLite path** is computed relative to `api/` via `__file__` — safe regardless of cwd. `connect_args={"check_same_thread": False}` for FastAPI async compatibility.
- **`database.py:init_db()`** auto-creates tables on startup (`create_all`) and auto-adds missing columns via `ALTER TABLE ADD COLUMN` (no Alembic). Columns that are non-nullable with no default are skipped.
- **UserSettings is a singleton**: always id=1, auto-created on first `GET`.
- **Account balance changes** auto-create `AccountHistory` records in `update_balance()`.
- **Habit checkins for the same day** accumulate count (not new rows), enforced by a `(habit_id, checkin_date)` unique constraint.
- **Anniversaries / DebtInstallments** have computed fields (`next_date`, `days_until`, `year_count` / `remaining_periods`) calculated at request time, not stored in DB.
- **`task_tags` M2M table** is defined in `models/tag.py` (not `models/task.py`).
- **Update schemas** use `clearable_fields` + `exclude_unset=True` to distinguish "field not sent" from "field sent as null".
- **JWT authentication** — mandatory for all `/api/*` routes except `/api/auth/*` and `/health`. Default password: `elysia`. Login via `POST /api/auth/login`. Token key in localStorage: `elysia_auth_token`. Frontend axios interceptor auto-attaches `Authorization: Bearer <token>`.
### Frontend (`WebUI/`)
- Vue Router uses `createWebHistory()` (HTML5 history mode) — **requires the backend SPA fallback** (`/{full_path:path}``index.html`).
- Vite dev proxy forwards `/api``http://localhost:23994`.
- `@` alias maps to `src/`.
- Global styles in SCSS (`src/styles/`).
- 8 Pinia stores; Element Plus icons registered globally in `main.ts`.
- Element Plus uses Chinese locale (`zh-cn`).
### Route registration order matters
The `/health` endpoint must be registered **before** the `/{full_path:path}` SPA fallback catch-all, otherwise `/health` requests return `index.html`. This is enforced in `api/app/main.py:114` — do not reorder these registrations.
### Docker
- `Dockerfile` copies pre-built `api/webui/` — you must build frontend before `docker build`.
- Docker CMD is an inline `python -c` one-liner that injects `api/` into `sys.path` and starts uvicorn. No separate entrypoint script.
- `docker-compose.yml` mounts `api/data/` and `api/logs/` for persistence; `api/webui/` is read-only.
## Testing quirks
- Only one test file: `tests/test_accounts.py`**hand-written, no framework** (not pytest). It counts pass/fail manually and uses `requests` directly against `localhost:23994`.
- Backend must be running before executing tests.
- **The test permanently mutates the database** — Section 15 deliberately leaves test data in place for UI display. Run it on a disposable database copy or reset manually if you need a clean state.
- **The test sends no auth headers** and will fail with 401 if JWT auth is enforced. You must first `POST /api/auth/login` with `{"password": "elysia"}` to get a token, then include `Authorization: Bearer <token>` in requests, or temporarily comment out the auth middleware for testing.
- No test coverage for tasks, habits, anniversaries, or tags.
## What's missing (agents should not assume)
- No linter, formatter, pre-commit hooks, or CI/CD
- No `.env` or environment variable loading
- No database migrations framework (Alembic)
## Additional notes
- Swagger UI at `/docs` when backend is running — the live, auto-generated API reference.
- `python-multipart` in `requirements.txt` is required for FastAPI to parse form data (not optional).
- `sass` is a runtime `dependency` (not `devDependency`) in `package.json` — unusual, but intentional.