b359bbe3bf
pytest / test (push) Successful in 54s
- roadmap.md: M1 (DB consolidation) -> M2 (React SPA) -> M3 (token/mobile) - docs/design/: agent-pipeline design docs with atomic tasks for M1-M3 - CLAUDE.md: workflow, doc map, commit conventions, review-notes briefing flow - .gitignore: ignore local review-notes/
14 KiB
14 KiB
M2 — 前端 v2(React SPA)
阅读前提:先读
README.md。M2 依赖 M1 完成(单库 + 干净的数据层 + API 建立在合并后的 schema 上)。
1. 目标
用 React SPA 取代现有 Jinja 页面,由 FastAPI 同源托管(同一容器、同一 origin)。一步合并 roadmap 的"前端重写"与"前端做厚":配置界面 + 数据可视化(热力图 / 地图,接管 Grafana)+ 记录的按需展示与小幅增删改。
元目标(agentic 实验):这是用 agent 写 React 的试水,全程尽量不读代码。因此本里程碑强约束 OpenAPI → 类型化 TS client 作为契约护栏:后端 API 先稳,前端永远对着强类型契约写,便宜模型不易跑偏,reviewer 也有客观依据。
2. 现状(M1 完成后)
- 页面仍是服务端 Jinja:
app/api/routes/pages.py(GET/POST /config、/、/admin、POST /config/smtp/test)+app/templates/(base/config/home/login.html、styles.css)。 - 鉴权:
get_current_auth_session(读auth_session_cookie_namecookie),server-side session + 每会话csrf_token内嵌在表单。 app/main.py已app.mount("/static", StaticFiles(...))。- 配置读写逻辑在
app/services/config_page.py(build_config_sections/save_config_updates/build_runtime_settings)。 - 业务数据:单库中的
location、poo_records、public_ip_state、public_ip_history。
3. 目标架构
3.1 后端:JSON API + SPA 托管
- 所有数据交互走 JSON API,统一前缀
/api(SPA 是客户端渲染,必须有 API——这与"同源/同容器"无关)。 - FastAPI 既挂
/api/*,又挂 SPA 静态产物,并对非/api、非静态资源的路径回退到index.html(支持前端路由 deep-link)。 - Jinja 页面在 SPA 达到功能对齐后移除。
3.2 鉴权:复用 session cookie + SPA 版 CSRF
- 继续用现有 HttpOnly session cookie(同源自动携带),M2 不引入 token(token 属 M3)。
- CSRF:新增
GET /api/session返回当前用户 + 该会话的csrf_token;SPA 在所有写请求(POST/PUT/PATCH/DELETE)放X-CSRF-Tokenheader,后端校验其与 session 内csrf_token一致。等价于把现有表单 CSRF 平移到 header。 - 浏览器面向的所有新端点一律 session 保护;裸 ingestion 端点(设备调用的
POST /location/record、POST /poo/record)维持现状到 M3。
3.3 前端工程
frontend/:Vite + React + TypeScript。- API client:由后端
openapi/openapi.json自动生成 TS 类型与请求函数(如openapi-typescript+ 轻量 fetch 封装,或同类工具)。生成物入库或在 build 时生成(见 T06 决策)。 - 可视化:地图 + 热力图(location 轨迹 / poo 点位)。建议 MapLibre GL 或 Leaflet + heatmap 插件(最终选型见 §5 决策)。
- 状态/数据请求:轻量即可(如 TanStack Query),不引入重型框架。
3.4 构建与部署
- 多阶段
Dockerfile:node 阶段npm ci && npm run build→ 把frontend/dist拷进 python 镜像的静态目录;运行镜像不带 node。 - compose 仍是单 app 容器(同源)。
4. API 契约(M2 要落地的端点)
全部
/api前缀、session 保护、JSON 进出。具体 schema 在各任务里用 Pydantic 定义,并经export_openapi.py固化。
| 分组 | 端点 | 用途 |
|---|---|---|
| 会话 | GET /api/session |
返回当前用户 + csrf_token;未登录 401 |
| 会话 | POST /api/auth/login |
账号密码登录,下发 session cookie |
| 会话 | POST /api/auth/logout |
注销 |
| 会话 | POST /api/auth/password |
改密(沿用现有强制改密语义) |
| 配置 | GET /api/config |
返回配置 sections(secret 不回显) |
| 配置 | PUT /api/config |
保存配置(留空保留旧 secret 语义不变) |
| 配置 | POST /api/config/smtp/test |
触发测试发信 |
| 数据 | GET /api/locations |
location 记录查询(时间范围/分页,供地图/热力图) |
| 数据 | GET /api/poo |
poo 记录列表(分页) |
| 数据 | GET /api/public-ip |
当前状态 + 变化历史 |
| CRUD | PATCH /api/locations/{person}/{datetime} |
修正单条 location |
| CRUD | DELETE /api/locations/{person}/{datetime} |
删除单条 location |
| CRUD | PATCH /api/poo/{timestamp} |
修正单条 poo |
| CRUD | DELETE /api/poo/{timestamp} |
删除单条 poo |
记录 CRUD 依赖现有 PK 作行标识(location PK=
person+datetime,poo PK=timestamp)。路径参数需对datetime/timestamp做 URL 编码处理。
5. 需先拍板的决策(Orchestrator 在派 T06 前确认)
- 地图/热力图库:MapLibre GL(矢量、现代)vs Leaflet(简单、生态大)。推荐 Leaflet +
leaflet.heat(试水门槛低)。 - OpenAPI client 生成物:入库(确定性、便于 review)vs build 时生成(仓库干净)。推荐入库,并加一个
npm run codegen+ CI 校验"生成物与 openapi 同步"。 - CSRF 落地:header
X-CSRF-Token+GET /api/session下发(推荐)vs 双提交 cookie。 - 是否保留少量 Jinja:建议 SPA 对齐后全量移除
templates/,只留 SPA。
这些可用 1 个轻量"决策任务"或直接由 Orchestrator 在本节记录选择,再开 T06。
6. 任务依赖图
后端 API(可与前端 scaffold 并行)
M2-T01 config API
M2-T02 session/auth API ─┐
M2-T03 data read API ├─► 都产出 OpenAPI 契约
M2-T04 record CRUD API │
M2-T05 smtp/action API ─┘
│ (openapi 稳定后)
▼
M2-T06 前端 scaffold + codegen ──► M2-T07 auth UI
├─► M2-T08 config UI
├─► M2-T09 可视化 UI
└─► M2-T10 records 管理 UI
▼
M2-T11 FastAPI 托管 SPA + 移除 Jinja(依赖 T07–T10 达到对齐)
▼
M2-T12 多阶段 Dockerfile + CI/compose
▼
M2-T13 文档 + OpenAPI 收尾
7. 原子任务(任务卡)
后端任务沿用 M1 的校验闸门(
pytest/ruff/export_openapi)。前端任务的闸门见 §8。
M2-T01 — config JSON API
- Status:
todo· Depends: none(M1 完成后) - Context: 把
config_page的读写能力暴露成 JSON,复用现有 service,不重写业务逻辑。 - Files:
create app/api/routes/api/config.py、create app/schemas/config.py;modify app/main.py(注册路由);create tests/test_api_config.py - Steps: 用
build_config_sections/save_config_updates包出GET/PUT /api/config;session 保护;secret 不回显、留空保留旧值语义照搬。 - Acceptance:
- 未登录访问
GET /api/config返回 401。 - 登录后
GET返回 sections,secret 字段被遮罩。 PUT留空 secret 时保留旧值;非法值返回 4xx 且不写库。- 校验闸门全绿(含
openapi/重导出入库)。
- 未登录访问
- Reviewer: 复用了 service 而非复制逻辑;CSRF 校验存在;secret 不泄漏到响应或 OpenAPI 示例。
M2-T02 — session / auth JSON API
- Status:
todo· Depends: none - Context: 给 SPA 提供登录/注销/会话探测 + CSRF 下发。
- Files:
create app/api/routes/api/session.py、app/schemas/session.py;modify app/main.py;create tests/test_api_session.py - Steps:
GET /api/session(401 或 user+csrf)、POST /api/auth/login、POST /api/auth/logout、POST /api/auth/password,复用app/services/auth.py。 - Acceptance:
- 正确账号密码登录后置下 HttpOnly session cookie;
GET /api/session返回 user + csrf_token。 - 错误凭据 401,不下发 cookie。
- 写端点缺
X-CSRF-Token或不匹配 → 403。 - 强制改密语义与现有一致。
- 校验闸门全绿。
- 正确账号密码登录后置下 HttpOnly session cookie;
- Reviewer: cookie 仍 HttpOnly、
Secure跟随app_env、SameSite=Lax;密码仍 Argon2,不明文。
M2-T03 — 数据读取 API(locations / poo / public-ip)
- Status:
todo· Depends: none - Files:
create app/api/routes/api/data.py、app/schemas/data.py;modify app/main.py;create tests/test_api_data.py - Steps:
GET /api/locations(时间范围 + 分页)、GET /api/poo(分页)、GET /api/public-ip(state + history);session 保护;查询参数有上限防全表导出。 - Acceptance:
- 分页/时间范围参数生效且有上限;越权未登录 401。
- 返回 schema 经 OpenAPI 固化。
- 校验闸门全绿。
- Reviewer: 查询走索引/PK,无 N+1;时间过滤边界正确。
M2-T04 — 记录 CRUD API(修正 / 删除)
- Status:
todo· Depends: M2-T03 - Files:
modify app/api/routes/api/data.py、app/services/location.py、app/services/poo.py;create tests/test_api_record_crud.py - Steps:
PATCH/DELETElocation(PK person+datetime)与 poo(PK timestamp);session + CSRF 保护;PK 路径参数 URL 解码;删除是硬删单行(不是清表)。 - Acceptance:
- PATCH 改单行字段、DELETE 删单行,行数变化精确为 1。
- 不存在的 PK → 404。
- 缺 CSRF → 403。
- 没有任何"批量删/清表"路径。
- 校验闸门全绿。
- Reviewer: 删除限定单 PK;编辑校验输入;ingestion 裸端点未被顺手加保护或改动。
M2-T05 — SMTP 测试 / 动作类 JSON API
- Status:
todo· Depends: M2-T01 - Files:
modify app/api/routes/api/config.py;modify tests/test_api_config.py - Steps:
POST /api/config/smtp/test复用send_smtp_test_email,返回结构化结果(success / config-error / failed)。 - Acceptance:
- 三种结果都有明确 JSON 状态码/字段;session + CSRF 保护。
- 校验闸门全绿。
M2-T06 — 前端 scaffold + OpenAPI codegen [structural]
- Status:
todo· Depends: M2-T01..T05(OpenAPI 已稳定) - Context: 建
frontend/工程与类型化 client 流水线,这是后续所有前端任务的地基。 - Files:
create frontend/(Vite+React+TS 脚手架、package.json、tsconfig.json、eslint、vitest、.gitignore)、frontend/src/api/(codegen 产物 + fetch 封装,自动注入X-CSRF-Token)、frontend/README.md、npm run codegen脚本 - Steps: 初始化 Vite React-TS;接
openapi/openapi.json生成类型;写一个最小 App 壳 + 受保护路由骨架;fetch 封装统一带 cookie、写请求注入 CSRF header、401 跳登录。 - Acceptance:
npm ci && npm run build成功产出frontend/dist。npm run lint、npm run typecheck、npm run test全绿(哪怕只有 1 个 smoke 测试)。npm run codegen生成物与当前openapi/openapi.json一致(CI 可校验)。
- Reviewer: client 全部基于生成类型;CSRF/cookie/401 处理在统一封装层;无手写、与契约不符的请求类型。
M2-T07 — 鉴权 UI(登录 / 会话引导 / 改密)
- Status:
todo· Depends: M2-T06 - Acceptance: 登录成功进受保护区;未登录访问受保护路由跳登录;强制改密流程可走完;
build/lint/typecheck/test全绿。
M2-T08 — 配置 UI(取代 Jinja config 页)
- Status:
todo· Depends: M2-T06 - Acceptance: 能读/存所有现有配置 section;secret 不回显、留空保留;SMTP 测试按钮反映三态;前端闸门全绿。
M2-T09 — 数据可视化 UI(地图 + 热力图)
- Status:
todo· Depends: M2-T06(数据来自 T03) - Context: 接管 Grafana 原职责:location 轨迹/热力图、poo 点位。
- Acceptance: 地图渲染 location/poo 点;热力图层可切换;时间范围筛选生效;前端闸门全绿。
M2-T10 — 记录管理 UI(按需展示 + 增删改)
- Status:
todo· Depends: M2-T06(CRUD 来自 T04) - Acceptance: 列表分页展示 poo/location;可编辑、可删除单条并即时刷新;删除有二次确认;前端闸门全绿。
M2-T11 — FastAPI 托管 SPA + 移除 Jinja
- Status:
todo· Depends: M2-T07, T08, T09, T10 - Files:
modify app/main.py(挂载 SPA 静态目录 + 非/api路径回退index.html);delete app/templates/、app/api/routes/pages.py(功能对齐后);modify tests(移除 Jinja 页面测试,新增 SPA fallback 测试) - Acceptance:
/config等路径返回 SPA(index.html),/api/*不被 fallback 吞掉,/static/资源正常。- 旧 Jinja 模板与 pages 路由移除后
pytest全绿。 - 校验闸门全绿(含 OpenAPI 重导出)。
- Reviewer: fallback 不拦截
/api、/docs、/openapi.json、静态资源;未登录访问 API 仍 401(不是被 SPA 壳吞掉)。
M2-T12 — 多阶段 Dockerfile + CI/compose
- Status:
todo· Depends: M2-T11 - Files:
modify Dockerfile(node build 阶段 → 拷dist进 python 镜像);modify .github/workflows/*(加前端 build/lint/typecheck);modify tests/test_deployment.py(镜像断言更新) - Acceptance:
- 镜像构建成功且运行镜像不含 node 运行时。
- CI 跑前端闸门 + 后端
pytest。 - 校验闸门全绿。
M2-T13 — 文档 + OpenAPI 收尾
- Status:
todo· Depends: M2-T12 - Acceptance: README 增"前端 v2"段(开发/构建说明);architecture 退役"不前后端分离"约束;roadmap 勾选 M2;
openapi/已同步入库。
8. 前端校验闸门(前端任务每次结束都要全绿)
在 frontend/ 下:
npm ci
npm run codegen # 生成类型化 client;产物须与 openapi/openapi.json 同步
npm run lint
npm run typecheck
npm run test
npm run build # 必须产出 dist
- 后端若同任务改了路由/schema,仍需根目录
python scripts/export_openapi.py并提交openapi/。 - "codegen 产物与 OpenAPI 同步"应在 CI 校验(生成后
git diff --exit-code)。
9. 里程碑完成定义(DoD)
- 访问应用得到 React SPA;配置、可视化、记录增删改都在 SPA 内完成。
- 所有浏览器交互走
/apiJSON 端点,session + CSRF 保护;ingestion 裸端点维持现状(留给 M3)。 - Jinja
templates/与 pages 路由移除;FastAPI 同源托管 SPA。 - 多阶段镜像构建通过;CI 含前端闸门。
- 后端
pytest/ruff/export_openapi+ 前端build/lint/typecheck/test全绿。