# Roadmap 本文档记录 `home-automation` 在 `v1.0.3` 之后的下一阶段规划。这一阶段不是小修补,而是几次较大的结构性改动:单库化、前端重写、以及远期的移动端试水。 > 每个里程碑的**可执行原子任务**展开在 [`docs/design/`](./design/README.md):M1 [`m1-db-consolidation.md`](./design/m1-db-consolidation.md)、M2 [`m2-frontend-v2.md`](./design/m2-frontend-v2.md)、M3 [`m3-token-mobile.md`](./design/m3-token-mobile.md)。这些文档为 Orchestrator→Implementer→Reviewer 的多模型流水线设计。 ## 当前基线(v1.0.3) - FastAPI + 服务端 Jinja 模板页面(目前只有 `/login`、`/config`) - 三个独立 SQLite 库: - App DB:`sqlite:///./data/app.db` - Location DB:`sqlite:///./data/locationRecorder.db` - Poo DB:`sqlite:///./data/pooRecorder.db` - 三条独立 Alembic 链:`alembic_app/`、`alembic_location/`、`alembic_poo/` - 单 admin 鉴权(Argon2 + server-side session cookie) - Public IPv4 monitor、SMTP 通知、Location / Poo recorder、Home Assistant in/out、TickTick OAuth - 数据可视化目前由 Grafana provisioning 承担(仅 location / poo dashboard) - 已有 OpenAPI 导出脚本:`scripts/export_openapi.py` ## 本阶段正式退役的架构约束 `docs/architecture-overview.md` 里有几条当时刻意写死的约束,这一阶段明确退役: - **“不引入前后端分离”** → 退役。本阶段改为 React SPA(仍由 FastAPI 同源托管,但渲染移到客户端)。 - **“三个独立 DB 不合并”** → 退役。本阶段把 location / poo 合并进 `app.db`。 - **Grafana 作为可视化方案** → 退役。可视化由 React 前端自己承担(热力图、地图等)。 保持不变的约束: - 继续使用 **SQLite**,本阶段不上 Postgres。 - 不引入 Notion。 ## 里程碑总览 | 里程碑 | 主题 | 一句话 | | --- | --- | --- | | **M1** ✅ | 单库化地基 | 把三库合并成单一 `app.db`,清理散落数据层,删掉 Grafana | | **M2** ✅ | 前端 v2 | React SPA 取代 Jinja,承载 config + 可视化 + 记录增删改 | | **M3** | 开放与移动端(远期试水) | token 鉴权 + React Native 移动端 | 排序原则:**先清地基,再在干净结构上盖楼。** M2 的新 API 和 React 必须建立在合并后的单库之上,否则就是在准备推倒的旧数据层上盖新楼、之后回头返工。 --- ## M1 — 单库化地基(✅ 已完成) ### 目标 把 location / poo 两个独立库合并进 `app.db`,借机清理项目早期散落各处的数据访问代码,并移除 Grafana。 ### 范围 - **Alembic 收敛为单链(app 链)**:location / poo 的表此后纳入 app 链管理;`alembic_location/`、`alembic_poo/` 退出活跃使用(保留在 git 历史)。 - **新建表(schema only)**:在 app 链上加一条 upgrade revision,把原来两个旧库里的表**原样**建到 `app.db` 中。Alembic **不需要知道任何旧数据**——它只负责把 app DB 往上升一个版本、建出这两张新表。 - **数据搬迁交给独立脚本**:`scripts/migrate_legacy_data.py`(见下方“迁移策略”),手动跑一次。 - **配置层收敛**:去掉 `LOCATION_DATABASE_URL` / `POO_DATABASE_URL`,统一到 `APP_DATABASE_URL`。 - **开启 SQLite WAL**:单文件 + Web + APScheduler 并发写入,开 WAL 更稳。 - **删除 Grafana**:移除 compose 中的 grafana service、`grafana/provisioning/`、`grafana/dashboards/`。直接删除,不再 re-point datasource。 - **更新文档**:README、architecture-overview 同步反映单库现实。 ### 注意 - **可视化空窗可接受**:M1 删掉 Grafana 后、到 M2 React 可视化落地之前会有一段没有可视化面板的时间。已确认可以接受。 - **历史数据是第一优先级,绝不能丢**(见“数据安全原则”)。 --- ## 迁移策略(M1 核心) 职责拆分得很清楚:**Alembic 管 schema,脚本管数据。** ### Alembic revision(只建结构) - 一条 app 链上的 upgrade revision,建出与旧库**完全相同**的表结构。 - 确定性、与环境无关:在生产机、CI、全新部署上都一样地建空表,不依赖任何旧文件是否存在。 - 本步**只原样挪表,不顺手改 schema**。任何表结构清理留到之后一条单独的 migration 去做——不可替代的历史数据,一次只承担一种风险。 ### 数据搬迁脚本(`scripts/migrate_legacy_data.py`) - 把旧 `locationRecorder.db` / `pooRecorder.db` 里的行,拷进 `app.db` 的新表(SQLite `ATTACH DATABASE` 或单独连接均可)。 - **幂等**:重复运行不会重复插入。 - **搬完对账**:逐表核对源 / 目标行数,对不上就报错中止。 - 只在生产机上**手动跑一次**,不进 Alembic 永久链路(避免把一次性历史搬迁焊死进每次全新建库都要跑的链路里)。 ### 旧库的“撤掉” - “撤掉旧库” = ① 配置不再指向它们 + ② 文件**归档保留**。 - **绝不**在任何脚本 / migration 里 `os.remove` 旧文件——那不可逆,且踩数据安全红线。 - 真正的删除是**人工、最后、确认无误之后**单独的一步。 --- ## 数据安全原则 历史数据(location / poo 记录)是这个项目里最不可替代的东西,迁移期间一律按以下原则: 1. **迁移前先归档**旧 `.db` 文件一份。 2. **先在副本上演练**:把每日备份恢复到一个 scratch 目录,在副本上跑完整迁移、核对行数无误,再对真实库动手。 3. **脚本幂等 + 行数对账**,对不上立即中止。 4. **旧文件只读归档、绝不自动删除**,删除是事后人工动作。 --- ## M2 — 前端 v2(React SPA)✅ 已完成 ### 目标 用 React SPA 取代现有 Jinja 页面,由 FastAPI 同源托管(同一容器、同一 origin)。这一步合并了“前端重写为 React”和“前端做厚”两件原本分开的事——它们本质是同一坨活。 > 备注:React 是一次 agentic programming 试水。之前只手写过 Vue、没手写过 React,这一轮想全程靠 agent、尽量不读代码地把它做出来。OpenAPI 导出 → 生成类型化 TS client 作为 agent 的契约护栏,正好服务这个目标。 ### 范围 - **React SPA**,FastAPI 挂载打包后的静态产物(同源,省掉 CORS)。 - **Config 界面**:取代现有 Jinja config 页。 - **数据可视化**:热力图、地图等,接管原先 Grafana 干的事。 - **按需展示 DB 数据**(例如 poo 记录)。 - **记录的小幅增删改**:用于修正不准确的记录。 ### 后端配套 - **补一套 JSON API**:SPA 是客户端渲染,需要后端提供 config 读写、数据查询、记录 CRUD 等 JSON 端点。(同源不等于不需要 API——API 是“客户端怎么拿数据”,与文件托管在哪无关。) - **鉴权**:浏览器面向的新端点(含记录 CRUD)复用现有 session cookie 保护。 - **类型化 client**:用 `scripts/export_openapi.py` 的输出生成 TS client。 ### 鉴权边界(与 M3 衔接) - 现在那个”裸 API 记小狗日志”的 ingestion 端点(设备 / 脚本调用,非浏览器)**维持现状到 M3**。 - M2 新增的、浏览器调用的 CRUD 端点,用 session 保护即可,本步不引入 token。 > **M2 已完成**(M2-T01 至 M2-T13 全部 done)。Jinja 模板已移除,React SPA 同源托管,多阶段 Docker 构建通过,所有校验闸门绿。 --- ## M3 — 开放与移动端(远期试水) ### 目标 引入 token 鉴权并做一个 React Native 移动端。**明确是很远期、低投入的试水**——先把 React 前端做出来,之后才会碰移动端,且主要是想试试没做过的 React / React Native。 ### 范围 - **OAuth-lite token 签发**:移动端在内置浏览器里用账号密码登录,走一遍类 OAuth 流程,服务端签发一个 bearer token 给 app 存起来使用。(本质是没有第三方的 Authorization Code 简化版。) - **React Native 移动端**:试水性质。 - **给 ingestion 端点上 token**:把 M2 暂时维持裸奔的设备端点收口到 token 鉴权下。 ### 为什么放最后 - 移动端是这一阶段最远期、最不确定的部分。 - token 主要是移动端的前置条件;Web 端 React 用现有 session cookie 即可,不需要为它提前引入 token。 ## 下一阶段:已确定要做(尚未拆解为任务卡) > 这些是 M2 之后**已经定下来要做**的方向——区别于下面的 Future Ideas(仅备忘、未必做)。这里只记到 roadmap 粒度:确定**做什么、为什么**;具体排期、依赖与原子任务,等动手时再展开成 `docs/design/` 的任务卡。**先后顺序未定**,部分项(如 MQTT)时间点灵活,可提前也可靠后。 ### 1. TOTP 二次验证(Dashboard 加固) **动机**:M2 之后多了一个 Web Dashboard。它虽有单 admin 密码保护,但**大概率会暴露在公网**上,只靠密码这一层不够。给登录再叠一层 **TOTP(基于时间的一次性密码,RFC 6238)** 作为第二因子,做纵深防御。 **范围(粗略,待细化)**: - 在现有单 admin(Argon2 + server-side session)登录之上,叠加 TOTP 第二步:密码校验通过后再验 6 位动态码,通过才发 session cookie。 - 首次启用时生成 TOTP secret,给出可导入 Authenticator 的二维码 / 可手输密钥;同时生成一组一次性**恢复码(recovery codes)**。 **运维 / 命令行要求(关键,实现时必须满足)**: 1. **忘记密码**:不需要任何 Web 端“找回密码”流程——直接在命令行里重置 admin 密码即可(沿用现有 CLI 思路)。 2. **TOTP 重置 / 恢复**:必须提供**命令行重置入口**。要覆盖最坏情况——**连恢复码(restore key)都丢了**,也能纯靠 CLI 把 TOTP 关掉 / 重新发放新的 secret,从而恢复登录。即:**CLI 是不依赖任何已存恢复凭据的最终逃生通道**,不能出现“密钥丢了就彻底锁死”的死角。 ### 2. 前端优化 **动机**:M2 的 React SPA 先把功能跑通,性能 / 体验层面的打磨还没做。这一项**确定要做,但具体优化什么还没定**。 **范围(待定)**:方向先留空,想清楚再细化。可能的候选(仅占位、非承诺):打包体积与代码分割(M2 构建已提示存在 > 500 kB 的单 chunk)、首屏加载、热力图 / 地图的渲染性能、移动端适配、可访问性等。等确定具体目标后再拆任务卡。 ### 3. MQTT 与 IoT 集成 **动机**:把这个后端接入家里的 IoT 设备生态,用 **MQTT** 作为设备 ↔ 后端的消息通道。属于**确定的实现方向**,时间点灵活——可以放到后面,也可以提前先做一部分。 **范围(粗略,待细化)**: - 引入 MQTT(接入既有 broker 或自带一个),后端作为订阅 / 发布方与设备互通。 - 与现有模块(Home Assistant in/out、location / poo recorder 等)如何衔接、哪些数据走 MQTT,待细化。 - 设备侧鉴权 / 安全边界另议(可能与下面第 4 条的 token 共用一套凭据)。 ### 4. 设置页生成 Long-lived Token(供 API 调用) **动机**:浏览器端走 session cookie 即可,但**脚本 / 设备 / 外部程序调用 API** 需要一种长期有效、可随身携带的凭据。在设置页加一组功能,由 admin **手动签发 long-lived token**,之后用它来调 API。 **范围(粗略,待细化)**: - 设置页新增「API Token」区:生成 / 命名 / 吊销 long-lived token;明文只在**生成时展示一次**,此后只存哈希。 - 后端支持用该 token 鉴权访问 API(与现有 session cookie 并存,互不影响)。 - 与 [M3](#m3--开放与移动端远期试水) 的 token 主题相关,但**这条是 Web 设置页手动签发的 PAT 风格**,不依赖移动端 OAuth 流程;两者实现时可复用同一套 token 存储 / 校验。 ## Future Ideas(暂不排期,想到先记下) > 这里收集**还没排进里程碑、也还没决定要不要做**的想法。不是承诺、也没有先后顺序;想做时再从这里捞出来——先升进上面的「下一阶段」,再细化成 `docs/design/` 的任务卡。 _(暂无条目:原 TOTP 已确定要做,已上移到「下一阶段」。)_