Commit Graph

3 Commits

Author SHA1 Message Date
ericwyuan
af0604bce4 fix(garmin): 两步验证账号同步报 EOFError,改用令牌登录
现象:网页触发同步报 "EOF when reading a line"。

原因:garth 的默认 MFA 提示是 input(),向 stdin 索取验证码。
gunicorn worker 没有 stdin,于是抛出 EOFError——错误信息本身
完全没提到 MFA,看不出该做什么。

方案:把"输验证码"和"日常同步"拆开。
- 新增 garmin_tokens 表存 garth 令牌(Client.dumps/loads 序列化)
- garmin_login.py:在终端里跑一次,可正常输入验证码,
  成功后令牌存库
- _connect() 优先加载令牌并 refresh_oauth2(),命中则完全跳过登录,
  既不需要密码也不需要验证码(令牌有效期约一年)
- 无令牌且密码登录撞上 MFA 时,抛 MFARequired 并给出具体该执行
  哪条命令,而不是把 EOFError 原样抛给用户

接口:
- GET /api/garmin/auth-status 返回是否已有令牌
- /api/garmin/sync 在已有令牌时不再强制要求密码

前端:
- 有令牌时隐藏密码输入框,提示无需密码
- 同步返回 mfaRequired 时,展示需要在 NAS 上执行的具体命令
- 同步请求超时放宽到 180s(一周的天数 + 运动是多次上游调用)
- 成功消息补上运动记录条数

tests (test_garmin_sync.py 新增 12 条,共 35):
- 令牌存取、覆盖不累积、按用户隔离
- 有令牌时绝不调用 login()
- MFA 的 EOFError 转成带操作指引的 MFARequired
- 普通 401 不会被误标成 mfaRequired
- 无令牌且无密码时给出明确拒绝

NAS 真机: 252 passed

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 19:57:40 +08:00
ericwyuan
6de7562cd8 [阶段5.1] 开机自启 + 接入真实 garminconnect,修复同步的三处 API 误用
fix(garmin): 同步代码调用的 garminconnect API 全是错的

装上库核对签名后发现(garminconnect 0.2.8),原代码从未被真正
执行过,三处调用都不成立:

1. get_activities(date) —— 该方法签名是 (start, limit),收的是
   分页下标和条数,不是日期。传日期等于在问"第 2026-08-23 条运动",
   而且少传一个参数必然 TypeError。
   改为 get_activities_by_date(start, end),整个窗口一次取回,
   顺带把原来"每天一次调用"减为一次。

2. 睡眠数据不在 get_user_summary 里,是独立的 get_sleep_data(cdate);
   原代码从 summary 里读 sleep 字段,结果会把每一晚都记成无睡眠数据。
   HRV 同理,走 get_hrv_data(cdate)。

3. 字段名不符:实际是 totalSteps / totalKilocalories /
   averageStressLevel,原代码用的是 steps / calories.total /
   stress.average。

其他改进:
- 运动记录改用 Garmin 自己的 activityId 作主键,重复同步同一窗口
  不再产生重复行(原来每次同步都会把同一条运动再插一遍)
- 某天无数据时返回全 None,不再写入空行让读端点再过滤掉
- 原来 bare except 吞掉每天的异常,全部失败也报 success;
  现在全窗口失败会如实返回 error 并记录原因
- 同步天数、是否走 Garmin 中国区改为环境变量可配

tests/test_garmin_sync.py (23 通过):
- 用 StubClient 模拟真实 0.2.8 的接口形状,无需库、凭证或网络
- 回归用例覆盖上述三处误用:活动必须按日期区间一次取回、
  睡眠与 HRV 必须走各自端点
- 重复同步不产生重复的天和重复的运动记录
- 单天失败跳过、全窗口失败报错、登录失败如实上报

部署:
- NAS 安装 garminconnect(pydantic-core 有 cp38 x86_64 轮子,
  无需 gcc)
- 新增 deploy/S99garmin.sh 开机自启脚本,与 NAS 上既有的
  S99frpc.sh 同一套惯例;以 root 启动但降权到 ericwyuan 运行,
  因为服务不需要特权而 .env 里有数据库和 API 凭证
- PID 文件放应用目录而非 /var/run(非特权用户写不了)

NAS 真机全量测试: 240 passed

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 19:05:19 +08:00
ericwyuan
3b2d0697f0 [阶段1.1-1.7] 实现完整的认证系统
后端实现:
- 创建 AuthService 包含密码加密、JWT 生成和验证
- 创建 authMiddleware 用于 API 路由保护
- 实现 auth 路由 (register, login, logout, /me)

前端实现:
- 创建 Login 页面 (登录/注册标签页)
- 创建 ProtectedRoute 组件用于路由保护
- 更新 App.tsx 集成路由保护
- 前端 API 客户端已包含认证方法和拦截器

验收标准已满足:
- 用户可以注册和登录
- JWT Token 正确生成和验证
- 受保护的路由需要有效 Token
- 未认证用户重定向到登录页面

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:25:33 +08:00