6.6 KiB
6.6 KiB
AGENTS.md
Project overview
- Full-stack todo app: Vue 3 + Element Plus (WebUI/) + FastAPI + SQLAlchemy (api/)
- Python 3.10+, Node 18+; SQLite database
- Single
masterbranch (4 commits total), no CI/CD
Quick commands
# 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, noos.getenv(). Port23994, CORS origins["http://localhost:5173", "http://localhost:23994"]. Note:pydantic-settingsis inrequirements.txtbut 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 viaALTER 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 ifpassword_hashis 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 ondate.today(). task_tagsM2M table is defined inmodels/tag.py(notmodels/task.py). Tags only support create/delete (no update).- Update schemas use
clearable_fields+exclude_unset=Trueto distinguish "field not sent" from "field sent as null". For non-clearable fields,Nonemeans "don't change"; for clearable fields,Nonemeans "clear it". Seeschemas/task.py:TaskUpdate,schemas/habit.py:HabitUpdate,schemas/anniversary.py:AnniversaryUpdate. - JWT authentication —
utils/auth.pyhandles JWT (HS256, key:"elysia-todo-secret-key-change-in-production", 24h expiry). Middleware inmain.pyvalidates tokens for all/api/*routes except/api/auth/*and/health. Default password:elysia. Login viaPOST /api/auth/login. Token key in localStorage:elysia_auth_token. Middleware only validates the token; it does NOT inject user info intorequest.state. Routes needing user data must callget_current_user(request)manually. - Different cascade delete strategies:
Category: refuses deletion if tasks are linked (400)HabitGroup: sets linked habits'group_idto NULLAnniversaryCategory: sets linked anniversaries'category_idto NULL
Router registration quirks
/healthMUST be registered before/{full_path:path}inmain.py:114— otherwise SPA fallback intercepts health checks and returnsindex.html.- habits router (
routers/habits.py) is an empty-shell router that combines 3 sub-routers viainclude_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 tosrc/.- 8 Pinia stores:
auth,task,category,tag,habit,anniversary,userSettings,ui. Theuistore manages dialog visibility, editing state, sidebar collapse, and global loading (no API calls). - Element Plus icons registered globally in
main.ts— use<Edit />,<Delete />etc. in templates without imports. - Element Plus uses Chinese locale (
zh-cn). - Vite 7.x + TypeScript 5.9 with
erasableSyntaxOnly: trueand 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_tokenis 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' }). sassis a runtimedependency(notdevDependency) inpackage.json— intentional.
Docker
Dockerfilecopies pre-builtapi/webui/— you must build frontend beforedocker build.- Docker CMD is an inline
python -cone-liner that injectsapi/intosys.pathand starts uvicorn. No separate entrypoint script. docker-compose.ymlmountsapi/data/andapi/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
.envor environment variable loading - No database migrations framework (Alembic)
Additional notes
- Swagger UI at
/docswhen backend is running. python-multipartinrequirements.txtis required for FastAPI to parse form data (not optional).pydantic-settingsis installed but unused — config is hardcoded.