# App Push｜每日提醒推送（客户端 + 后端）｜Tasks

> 阶段：任务清单（tasks）
>
> 依赖：`spec_kit/App Push/spec.md`、`spec_kit/App Push/plan.md`

---

## 0. 约束与共识（本期已确认）

- 每日次数：**0～5 条/天**
- 发送窗口：**9:00～24:00（按客户端上报时区）**
- 时间点：窗口内 **随机抖动**
- 勿扰：**不做**
- 文案：使用后端推荐模块的 **Push 场景个性化模板（降风险）**

---

## 1. 客户端（Expo RN）

### 1.1 UUID（client_user_id）

- [ ] 在 `client/src/storage/appStorage.ts`（或新模块）实现：
  - `getOrCreateClientUserId(): Promise<string>`
  - 首次生成 UUID 并持久化，后续复用

### 1.2 Push 权限与 token

- [ ] 接入获取 Expo Push Token 的封装（例如 `src/features/push/`）：
  - 获取权限状态
  - 请求权限
  - 获取 Expo Push Token
- [ ] 在 Onboarding / push 引导完成后：
  - granted 时：获取 token + 调 `register`
  - 未 granted：仅保存本地设置，不阻塞进入主功能

### 1.3 “每日提醒次数”入口

- [ ] Onboarding：新增/复用一个步骤页面
  - 选择 0～5 次（0 表示关闭）
  - 支持跳过
  - 完成后写本地存储，并调用后端 `preferences`
- [ ] 个人主页弹窗：
  - 修复“测试模式强制无权限”的逻辑
  - 次数限制 0～5；关闭等价 0
  - denied 时提示去系统设置
  - 修改后写本地存储，并调用后端 `preferences`

### 1.4 接口封装

- [ ] 新增 `client/src/services/pushApi.ts`
  - `registerPushToken(...)`
  - `setPushPreferences(...)`
  - `getPushPreferences(...)`（可选）
- [ ] 统一使用 `client/src/utils/http.ts` 的请求封装

### 1.5 联调开关与日志

- [ ] 开发环境输出必要日志（不打印敏感信息）：
  - client_user_id 生成结果
  - 权限状态与 token 是否获取成功
  - register/preferences 的请求是否成功与错误原因

---

## 2. 后端（FastAPI）

### 2.1 数据模型与迁移

- [ ] 新增表（或等价模型）：
  - `push_tokens`
  - `push_preferences`
  - `push_send_log`（幂等防重复）
- [ ] 增加迁移脚本（Alembic 或项目现有迁移机制）

> 注意：不执行任何破坏性数据库操作；运行迁移前若涉及真实库，需要你明确回复“允许操作数据库”。

### 2.2 API

- [ ] `POST /v1/push/register`
  - 幂等：`env + app_id + push_token` 唯一
  - 更新 `client_user_id` 归属与 `last_seen_at`
- [ ] `PUT /v1/push/preferences`
  - 校验 `times_per_day` ∈ [0,5]
  - `enabled=false` 或 `times_per_day=0` 时停止后续推送
- [ ] `GET /v1/push/preferences`
  - 返回当前偏好与更新时间
- [ ] `POST /v1/push/test`（仅 dev）
  - 立即向该用户发送一条测试推送（用于真机联调）

### 2.3 Expo 推送发送器

- [ ] 实现 `send_expo_push(token, title, body, data?)`
  - 处理 Expo 返回错误并对不可恢复错误停用 token
  - 记录发送结果到 `push_send_log`

### 2.4 推送文案（推荐模板）

- [ ] 在推送任务中调用后端推荐模块的 Push 场景模板：
  - 输入：`client_user_id`、语言/时区（可选）
  - 输出：`title/body`
  - 默认“降个性化/降风险”

---

## 3. 定时任务（每日计划 + ETA 发送）

- [ ] 每日“计划生成任务”
  - 扫描 `enabled && times_per_day>0` 用户
  - 按用户时区在 9:00～24:00 生成 N 个随机抖动时间点
  - 写入 `push_send_log`（唯一键保证幂等）
  - 投递 ETA 发送任务（或按项目现有任务系统实现）
- [ ] ETA “发送任务”
  - 拉取当次发送所需 token/偏好
  - 生成文案（推荐模板）
  - 调用 Expo push 发送
  - 更新 `push_send_log` 状态

---

## 4. 验收与回归

- [ ] iOS 真机：权限申请、token 获取、test 推送可达
- [ ] Android 真机：权限申请、token 获取、test 推送可达
- [ ] 修改次数：后端计划生成正确（一天内不超发）
- [ ] 关闭：后端停止后续推送（不再生成计划/不再发送）

