diff --git a/.gitignore b/.gitignore index 8dbea86..ee0fbf7 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,7 @@ check_*.py # Environment variables .env .env.* + +# Claude Code +CLAUDE.md +AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index d2e4220..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,86 +0,0 @@ -# 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 (4 commits total), 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 -``` - -**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`. `set_default_password()` auto-initializes password to `"elysia"` on first access if `password_hash` is empty. -- **Habit checkins for the same day** accumulate count (not new rows), enforced by a `(habit_id, checkin_date)` unique constraint. Cancelling reduces count; deleting when count ≤ 0 removes the row. -- **Anniversaries** have computed fields (`next_date`, `days_until`, `year_count`) calculated at request time, not stored in DB. The calculation functions live in the router layer (not models), because they depend on `date.today()`. -- **`task_tags` M2M table** is defined in `models/tag.py` (not `models/task.py`). Tags only support create/delete (no update). -- **Update schemas** use `clearable_fields` + `exclude_unset=True` to distinguish "field not sent" from "field sent as null". For non-clearable fields, `None` means "don't change"; for clearable fields, `None` means "clear it". See `schemas/task.py:TaskUpdate`, `schemas/habit.py:HabitUpdate`, `schemas/anniversary.py:AnniversaryUpdate`. -- **JWT authentication** — `utils/auth.py` handles JWT (HS256, key: `"elysia-todo-secret-key-change-in-production"`, 24h expiry). Middleware in `main.py` validates tokens 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`. **Middleware only validates the token; it does NOT inject user info into `request.state`.** Routes needing user data must call `get_current_user(request)` manually. -- **Different cascade delete strategies:** - - `Category`: refuses deletion if tasks are linked (400) - - `HabitGroup`: sets linked habits' `group_id` to NULL - - `AnniversaryCategory`: sets linked anniversaries' `category_id` to NULL - -### Router registration quirks -- **`/health` MUST be registered before `/{full_path:path}`** in `main.py:114` — otherwise SPA fallback intercepts health checks and returns `index.html`. -- **habits router** (`routers/habits.py`) is an empty-shell router that combines 3 sub-routers via `include_router`: habit-groups (`/api/habit-groups`), habits (`/api/habits`), and checkins (`/api/habits/{habit_id}/checkins`). -- **anniversaries router** uses `prefix="/api"` (not `/api/anniversaries`). Its internal paths are `/anniversaries`, `/anniversary-categories`, etc. - -### 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/`. -- 8 Pinia stores: `auth`, `task`, `category`, `tag`, `habit`, `anniversary`, `userSettings`, `ui`. The `ui` store manages dialog visibility, editing state, sidebar collapse, and global loading (no API calls). -- Element Plus icons registered globally in `main.ts` — use ``, `` etc. in templates without imports. -- Element Plus uses Chinese locale (`zh-cn`). -- **Vite 7.x + TypeScript 5.9** with `erasableSyntaxOnly: true` and project references. -- **Frontend task filtering is entirely client-side.** `fetchTasks()` loads all tasks once; filtering, sorting, and pinyin search run in computed getters. No server-side filtering for tasks. -- **Pinyin search** via `pinyin-pro` (`utils/pinyin.ts`) — supports Chinese character search by pinyin initials or full pinyin. -- **Token key `elysia_auth_token` is hardcoded in 3 separate files** (`router/index.ts`, `api/request.ts`, `stores/useAuthStore.ts`). If you rename it, update all 3. -- **401 response triggers a hard page redirect** (`window.location.href = '/login'`) in the axios interceptor, which reloads the SPA entirely. This differs from the router guard's in-app redirect (`return { path: '/login' }`). -- `sass` is a runtime `dependency` (not `devDependency`) in `package.json` — intentional. - -### 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 -- No test framework or test files currently in the repo. -- 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. -- `python-multipart` in `requirements.txt` is required for FastAPI to parse form data (not optional). -- `pydantic-settings` is installed but unused — config is hardcoded.