From bd09523e947dc038372830a9562d98efe9b29a30 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Sat, 13 Jun 2026 12:01:34 +0200 Subject: [PATCH] M2-T13: docs wrap-up + retire frontend constraints + dependency cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README: add 前端 v2 (React SPA) section (dev/build/codegen/hosting/gates), update directory listing, drop stale Jinja descriptions - architecture-overview: retire '不引入前后端分离' constraint; reflect SPA + JSON API - roadmap: mark M2 done - remove orphaned jinja2 dependency (recompile requirements*.txt; no other churn) - delete empty tests/test_auth.py stub; drop dead _extract_csrf_token in test_api_data - verified image still builds and app imports with the slimmer deps --- README.md | 81 +++++++++++++++++++++++++++++------ dev-requirements.txt | 6 +-- docs/architecture-overview.md | 25 +++++++---- docs/design/m2-frontend-v2.md | 2 +- docs/roadmap.md | 8 ++-- requirements.in | 1 - requirements.txt | 6 +-- tests/test_api_data.py | 7 --- tests/test_auth.py | 5 --- 9 files changed, 92 insertions(+), 49 deletions(-) delete mode 100644 tests/test_auth.py diff --git a/README.md b/README.md index c1fb3b9..908704a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 当前系统已经包含: -- FastAPI Web 应用与服务端模板页面 +- FastAPI Web 应用(React SPA 前端 + JSON API) - SQLite + SQLAlchemy + Alembic 的单库结构 - username/password + server-side session 鉴权 - runtime config 页面与 app DB 持久化 @@ -47,11 +47,13 @@ python -m scripts.run_migrations 主要目录如下: -- `app/`: FastAPI 应用代码 +- `app/`: FastAPI 应用代码(包含 JSON API、业务服务、数据模型) +- `frontend/`: React SPA 前端(Vite + React + TypeScript + Mantine) - `alembic_app/`: App DB 的 Alembic migration 环境(同时管理 `location` / `poo_records` 表) - `tests/`: pytest 测试 - `docs/`: 当前系统说明文档 - `scripts/`: 辅助脚本,例如 OpenAPI 导出 +- `openapi/`: OpenAPI schema 静态产物(`openapi.json` / `openapi.yaml`),纳入版本控制 ## 依赖管理 @@ -112,11 +114,62 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 启动后可访问: -- 应用首页:`http://localhost:8000/` +- 应用首页(React SPA):`http://localhost:8000/` - 健康检查:`http://localhost:8000/status` - Swagger UI:`http://localhost:8000/docs` - ReDoc:`http://localhost:8000/redoc` +## 前端 v2(React SPA) + +M2 用 React SPA 取代了原有 Jinja 服务端模板,由 FastAPI 同源托管(同一容器、同一 origin)。 + +### 技术栈 + +- **Vite + React + TypeScript + Mantine**(组件库) +- **TanStack Query**(数据请求/缓存) +- **Leaflet / react-leaflet**(地图与热力图) +- **openapi-typescript + openapi-fetch**(类型化 API client,由 `openapi/openapi.json` 生成) + +### 本地开发(前端) + +前端开发服务器会把 `/api`、`/location`、`/poo`、`/public-ip`、`/homeassistant`、`/ticktick`、`/status` 等路径代理到后端 FastAPI(`:8000`)。 + +```bash +cd frontend +npm install +npm run dev # 启动 Vite dev server(默认 :5173),代理后端 +``` + +### 构建 + +```bash +cd frontend +npm run build # 产出 frontend/dist +``` + +FastAPI 启动时若 `frontend/dist/index.html` 存在,则自动挂载该目录,并对非 `/api` 路径做 SPA fallback(返回 `index.html`)。该路径可通过环境变量 `SPA_DIST_DIR` 覆盖(默认值为 `frontend/dist`,与多阶段 Dockerfile 中 `COPY` 到 `/app/frontend/dist` 一致)。 + +### 类型化 API Client + +前端 API client 由后端 OpenAPI schema 自动生成: + +```bash +cd frontend +npm run codegen # 从 ../openapi/openapi.json 生成 src/api/schema.d.ts +``` + +生成物(`src/api/schema.d.ts`)已提交入库,CI 会校验它与 `openapi/openapi.json` 保持同步。 + +### 前端校验闸门 + +```bash +cd frontend +npm run lint # ESLint +npm run typecheck # TypeScript 类型检查 +npm run test # Vitest 单元测试 +npm run build # 构建,确认产出 dist +``` + ## 数据库与 Alembic 当前使用单一 SQLite 数据库文件: @@ -142,9 +195,9 @@ python -m scripts.migrate_legacy_data - 认证模型:`username/password` - 会话模型:server-side session + cookie -- 当前主要受保护页面:`/config` -- 当前公开页面:`/login` -- 当前公开 API:现有业务 API 暂未在这一轮统一收口到 auth 下 +- 当前受保护入口:React SPA(`/` 等客户端路由)调用 `/api/*` JSON 端点 +- 当前公开页面:`/login`(SPA 登录页) +- 当前公开 API:裸 ingestion 端点(`/location/record`、`/poo/record` 等设备调用端点)暂未收口到 session 保护(M3 再做) 安全实现的当前边界: @@ -152,7 +205,7 @@ python -m scripts.migrate_legacy_data - session cookie 使用 `HttpOnly` - `Secure` 默认随 `APP_ENV` 切换:非 development 时默认开启 - `SameSite=Lax` -- 登录表单和登出表单都有基础 CSRF 防护 +- 写请求(POST/PUT/PATCH/DELETE)需携带 `X-CSRF-Token` header(SameSite=Lax + 自定义 header 纵深防御,无需 per-session token 值比对) 首次启动时,如果 `APP_DATABASE_URL` 对应的 auth DB 里还没有用户,应用会使用: @@ -166,12 +219,14 @@ python -m scripts.migrate_legacy_data 首次登录后会被要求立即修改密码。这个 bootstrap 只用于首个用户落库,不是后续的完整配置管理方案。 -当前前端主要有两条页面路径: +React SPA 主要页面路由(客户端路由,均由 FastAPI fallback 到 `index.html`): -- `/login` -- `/config` +- `/login`:登录页 +- `/`:首页(地图热力图主视图) +- `/config`:配置页(取代原 Jinja `/config`) +- `/records`:记录管理列表页 -无论是本地 `host:port` 还是反向代理后的域名访问,登录成功后都使用相对路径跳转到 `/config`。 +无论是本地 `host:port` 还是反向代理后的域名访问,登录成功后进入 SPA 首页(`/`)。 ## Config 持久化 @@ -230,8 +285,8 @@ python -m scripts.migrate_legacy_data 当前系统已经提供最小可用的 SMTP 能力: -- SMTP 配置可在 `/config` 页面填写并保存到 `app_config` -- 可通过 config 页面发送测试邮件 +- SMTP 配置可在 React SPA `/config` 页面填写并保存到 `app_config`(通过 `PUT /api/config`) +- 可通过 config 页面发送测试邮件(`POST /api/config/smtp/test`) - 邮件 `From` 头支持显示名,例如 `Home Automation ` 当前 SMTP 配置项包括: diff --git a/dev-requirements.txt b/dev-requirements.txt index 1d79e6a..adcc7f0 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -53,14 +53,10 @@ idna==3.11 # httpx iniconfig==2.3.0 # via pytest -jinja2==3.1.6 - # via -r requirements.in mako==1.3.11 # via alembic markupsafe==3.0.3 - # via - # jinja2 - # mako + # via mako packaging==26.1 # via # build diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md index 41d7239..8aabd28 100644 --- a/docs/architecture-overview.md +++ b/docs/architecture-overview.md @@ -29,10 +29,8 @@ - 通用依赖注入 - `api/` - HTTP routes - - 当前已迁入 `/login`、`/logout`、`/admin` - - 当前已迁入 `GET /public-ip/check` - - 当前已迁入 `POST /homeassistant/publish` 第一版入口 - - 当前已迁入 `POST /poo/record` 与 `GET /poo/latest` + - `api/routes/api/`:JSON API(`/api/*` 前缀),供 React SPA 调用:会话/鉴权、配置读写、数据查询、记录 CRUD + - 裸 ingestion 端点:`GET /public-ip/check`、`POST /homeassistant/publish`、`POST /poo/record`、`GET /poo/latest`、TickTick OAuth 等 - `models/` - SQLAlchemy models - 所有模型(auth / config / public_ip / location / poo)共用同一个 `Base`,均落在单一 `app.db` 中 @@ -46,8 +44,6 @@ - `integrations/` - 外部系统适配层 - 当前已迁入 Home Assistant outbound adapter -- `templates/` - - Jinja2 模板 - `static/` - 极简静态资源 @@ -63,15 +59,26 @@ pytest 测试目录。后续可以在这里自然扩展: - mock tests - integration tests +### `frontend/` + +React SPA 前端(M2 引入)。Vite + React + TypeScript + Mantine,由 FastAPI 同源托管。 + +- `src/`:React 源码 +- `src/api/`:由 `openapi/openapi.json` 生成的类型化 client(`schema.d.ts`)+ fetch 封装 +- `dist/`:`npm run build` 产物,由 FastAPI 的 `SPA_DIST_DIR` 挂载并对非 `/api` 路径做 fallback + ### `scripts/` -辅助脚本目录。当前包含 OpenAPI 导出脚本。 +辅助脚本目录。当前包含 OpenAPI 导出脚本(`export_openapi.py`)与数据层辅助脚本。 + +### `openapi/` + +OpenAPI schema 静态产物(`openapi.json` / `openapi.yaml`),由 `python scripts/export_openapi.py` 生成,纳入版本控制。前端 codegen 以此为契约源。 ## 当前约束 -- 当前只搭骨架,不迁业务逻辑 - 当前数据库继续使用 SQLite -- 当前不引入前后端分离 +- ~~当前不引入前后端分离~~ **已退役(M2)**:现为 React SPA + JSON `/api` 层,由 FastAPI 同源托管 - 当前不设计 Notion 模块 - 当前通知能力仍保持极小范围,不引入独立通知中心或多渠道抽象 diff --git a/docs/design/m2-frontend-v2.md b/docs/design/m2-frontend-v2.md index 9021029..a44358a 100644 --- a/docs/design/m2-frontend-v2.md +++ b/docs/design/m2-frontend-v2.md @@ -222,7 +222,7 @@ - [ ] 校验闸门全绿。 ### M2-T13 — 文档 + OpenAPI 收尾 -- **Status**: `todo` · **Depends**: M2-T12 +- **Status**: `done` · **Depends**: M2-T12 - **Acceptance**: README 增"前端 v2"段(开发/构建说明);architecture 退役"不前后端分离"约束;roadmap 勾选 M2;`openapi/` 已同步入库。 --- diff --git a/docs/roadmap.md b/docs/roadmap.md index 36771e6..5bca937 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -35,7 +35,7 @@ | 里程碑 | 主题 | 一句话 | | --- | --- | --- | | **M1** ✅ | 单库化地基 | 把三库合并成单一 `app.db`,清理散落数据层,删掉 Grafana | -| **M2** | 前端 v2 | React SPA 取代 Jinja,承载 config + 可视化 + 记录增删改 | +| **M2** ✅ | 前端 v2 | React SPA 取代 Jinja,承载 config + 可视化 + 记录增删改 | | **M3** | 开放与移动端(远期试水) | token 鉴权 + React Native 移动端 | 排序原则:**先清地基,再在干净结构上盖楼。** M2 的新 API 和 React 必须建立在合并后的单库之上,否则就是在准备推倒的旧数据层上盖新楼、之后回头返工。 @@ -101,7 +101,7 @@ --- -## M2 — 前端 v2(React SPA) +## M2 — 前端 v2(React SPA)✅ 已完成 ### 目标 @@ -125,9 +125,11 @@ ### 鉴权边界(与 M3 衔接) -- 现在那个“裸 API 记小狗日志”的 ingestion 端点(设备 / 脚本调用,非浏览器)**维持现状到 M3**。 +- 现在那个”裸 API 记小狗日志”的 ingestion 端点(设备 / 脚本调用,非浏览器)**维持现状到 M3**。 - M2 新增的、浏览器调用的 CRUD 端点,用 session 保护即可,本步不引入 token。 +> **M2 已完成**(M2-T01 至 M2-T13 全部 done)。Jinja 模板已移除,React SPA 同源托管,多阶段 Docker 构建通过,所有校验闸门绿。 + --- ## M3 — 开放与移动端(远期试水) diff --git a/requirements.in b/requirements.in index 61b29b2..f32ec3f 100644 --- a/requirements.in +++ b/requirements.in @@ -3,7 +3,6 @@ apscheduler>=3.10,<4.0 argon2-cffi>=25.1,<26.0 fastapi>=0.115,<0.116 httpx>=0.28,<1.0 -jinja2>=3.1,<4.0 pydantic-settings>=2.6,<3.0 python-multipart>=0.0.12,<1.0 pyyaml>=6.0,<7.0 diff --git a/requirements.txt b/requirements.txt index c76c026..933a262 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,14 +45,10 @@ idna==3.11 # via # anyio # httpx -jinja2==3.1.6 - # via -r requirements.in mako==1.3.11 # via alembic markupsafe==3.0.3 - # via - # jinja2 - # mako + # via mako pycparser==2.23 # via cffi pydantic==2.13.2 diff --git a/tests/test_api_data.py b/tests/test_api_data.py index 03dc1ce..5dd8485 100644 --- a/tests/test_api_data.py +++ b/tests/test_api_data.py @@ -1,7 +1,6 @@ """Tests for M2-T03: GET /api/locations, GET /api/poo, GET /api/public-ip.""" from __future__ import annotations -import re from datetime import UTC, datetime from fastapi.testclient import TestClient @@ -18,12 +17,6 @@ from app.models.public_ip import PublicIPHistory, PublicIPState # --------------------------------------------------------------------------- -def _extract_csrf_token(html: str) -> str: - match = re.search(r'name="csrf_token" value="([^"]+)"', html) - assert match is not None, "csrf_token not found in HTML" - return match.group(1) - - def _api_login(client: TestClient) -> None: """Log in via POST /api/auth/login so the TestClient has a session cookie.""" resp = client.post( diff --git a/tests/test_auth.py b/tests/test_auth.py deleted file mode 100644 index 1902c37..0000000 --- a/tests/test_auth.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Jinja-based auth tests removed in M2-T11 (Jinja routes deleted). - -Equivalent JSON-API coverage lives in test_api_session.py and test_api_config.py. -This file is intentionally left with no test functions so pytest does not error. -"""