feat: 实现密码重置功能与用户搜索API,优化注册登录流程

- 新增忘记密码页面与重置密码确认流程(前端+后端)
- 修复注册验证码页登录跳转路由
- 新增用户搜索API(按邮箱查询)
- 简化infra脚本,统一为app.sh
- 补充密码重置与用户API测试覆盖
- 更新runtime文档与AGENTS配置
This commit is contained in:
qzl
2026-02-27 15:22:42 +08:00
parent 0d4811fee5
commit e4e995854d
37 changed files with 2101 additions and 222 deletions
@@ -0,0 +1,309 @@
# Invite Code Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 在现有 OTP 注册链路中引入邀请码能力,支持用户自动生成专属邀请码、注册时可选填邀请码并记录邀请关系与使用次数。
**Architecture:** 采用数据库中心实现:通过 Alembic 新增 `invite_codes` 表、扩展 `profiles` 字段,并在 `auth.users` 的现有 trigger 函数中完成邀请码校验与记账,保证注册与邀请关系写入尽量原子。应用层只负责透传 `invite_code` 到 Supabase `raw_user_meta_data`
**Tech Stack:** FastAPI, SQLAlchemy, Alembic, Supabase Auth, PostgreSQL PL/pgSQL, Pytest
---
### Task 1: 更新注册请求 SchemaTDD
**Files:**
- Modify: `backend/src/v1/auth/schemas.py`
- Modify: `backend/tests/integration/test_auth_routes.py`
**Step 1: Write the failing test**
`test_signup_start_returns_pending_response` 基础上新增断言路径:请求体带 `invite_code` 时返回仍为 202,且未触发 422。
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start_returns_pending_response -v`
Expected: FAIL`invite_code` 为额外字段或校验不通过)
**Step 3: Write minimal implementation**
`VerificationCreateRequest` 增加可选字段:
```python
invite_code: str | None = Field(default=None, min_length=8, max_length=8)
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start_returns_pending_response -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/v1/auth/schemas.py backend/tests/integration/test_auth_routes.py
git commit -m "feat: accept invite code in signup request"
```
### Task 2: 透传 invite_code 到 Supabase metadataTDD
**Files:**
- Modify: `backend/src/v1/auth/gateway.py`
- Modify: `backend/tests/unit/v1/auth/test_auth_service.py`
**Step 1: Write the failing test**
`test_supabase_signup_passes_username_in_metadata` 增加 `invite_code` 并断言:
```python
assert captured_payload["data"] == {
"username": "demo",
"invite_code": "A1B2C3D4",
}
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/unit/v1/auth/test_auth_service.py -k metadata -v`
Expected: FAILmetadata 未包含 `invite_code`
**Step 3: Write minimal implementation**
`create_verification` 中构建 metadata
```python
metadata = {"username": request.username}
if request.invite_code:
metadata["invite_code"] = request.invite_code
payload = {
"email": request.email,
"password": request.password,
"data": metadata,
}
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/unit/v1/auth/test_auth_service.py -k metadata -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/v1/auth/gateway.py backend/tests/unit/v1/auth/test_auth_service.py
git commit -m "feat: pass invite code through signup metadata"
```
### Task 3: 新增 invite_codes 表与 profiles.referred_by(迁移先行)
**Files:**
- Create: `backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py`
- Modify: `backend/src/models/profile.py`
- Create: `backend/src/models/invite_code.py`
- Modify: `backend/src/models/__init__.py`
**Step 1: Write the failing test**
`backend/tests/unit/database/test_profile_models.py` 新增 `referred_by` 读写测试;新增 `backend/tests/unit/database/test_invite_code_models.py` 验证 `InviteCode` 基本创建与约束字段。
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/unit/database/test_profile_models.py tests/unit/database/test_invite_code_models.py -v`
Expected: FAIL(字段/模型不存在)
**Step 3: Write minimal implementation**
- Alembic 创建 `invite_codes`
- `code` 唯一索引
- `owner_id` 外键到 `profiles.id`(可空)
- `status``used_count``max_uses` check 约束
- `max_uses` 默认 `NULL`(无限制)
- `expires_at` 默认 `NULL`(无限制)
- `reward_config` JSONB 默认 `{}`
- 启用 RLS(按项目默认 deny-all
- **注意**:本期不开放 invite_codes 表直接读取,用户邀请码通过 profile 聚合接口返回(后续实现)
- Alembic 给 `profiles` 增加 `referred_by` + 索引 + 外键
- ORM 同步 `Profile.referred_by``InviteCode` 模型
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/unit/database/test_profile_models.py tests/unit/database/test_invite_code_models.py -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py backend/src/models/profile.py backend/src/models/invite_code.py backend/src/models/__init__.py backend/tests/unit/database/test_profile_models.py backend/tests/unit/database/test_invite_code_models.py
git commit -m "feat: add invite code schema and profile referral fields"
```
### Task 4: 扩展注册 trigger 生成邀请码并消费邀请(TDD)
**Files:**
- Modify: `backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py`
- Modify: `backend/tests/integration/test_auth_routes.py`
**Step 1: Write the failing test**
新增集成测试(建议通过测试替身/fixture 验证行为):
- 注册不带邀请码时,profile 创建后存在 owner 邀请码
- 注册带有效邀请码时,`referred_by` 生效且 `used_count + 1`
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k invite -v`
Expected: FAIL(触发器逻辑尚未实现)
**Step 3: Write minimal implementation**
在迁移 SQL 中:
- 新增 helper function:生成 8 位随机码(排除易混淆字符 0/O/1/I/L,冲突重试)
- 重建 `public.create_profile_for_new_user()`
1. 插入 `profiles`
2. 创建该用户专属 `invite_codes``owner_id = NEW.id`
3. 读取 `NEW.raw_user_meta_data ->> 'invite_code'`
4. 校验邀请码状态/过期/次数
5. 若有效:更新 `profiles.referred_by`,并 `used_count = used_count + 1`
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k invite -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py backend/tests/integration/test_auth_routes.py
git commit -m "feat: extend signup trigger for invite code generation and usage"
```
### Task 5: 覆盖邀请码边界场景(TDD)
**Files:**
- Modify: `backend/tests/integration/test_auth_routes.py`
- Optional Modify: `backend/tests/e2e/test_auth_flow.py`
**Step 1: Write the failing test**
新增场景测试:
- 邀请码不存在
- 邀请码 disabled
- 邀请码 expires_at 已过期
- 邀请码达到 `max_uses`
断言:注册仍成功(202/200 链路正常),仅邀请关系不建立。
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k "invite and (expired or disabled or max_uses or invalid)" -v`
Expected: FAIL
**Step 3: Write minimal implementation**
修正 trigger 判断顺序和条件,确保“邀请无效不影响注册”原则。
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k invite -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/tests/integration/test_auth_routes.py backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py
git commit -m "test: cover invite code edge cases in signup flow"
```
### Task 6: 文档同步与运行手册更新
**Files:**
- Modify: `docs/runtime/runtime-route.md`
- Modify: `docs/runtime/runtime-runbook.md`
**Step 1: Write the failing test**
无自动化测试;改为文档一致性检查清单(手工):
- 注册接口 request 字段包含 `invite_code`
- 说明邀请码消费时机与“无效码不阻断注册”
**Step 2: Run check to verify missing docs**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start -v`
Expected: PASS(作为行为基线),文档尚未同步
**Step 3: Write minimal implementation**
- 更新 `POST /auth/verifications` 请求字段
- 新增邀请码行为说明
- 在 runbook 变更日志添加本次改动记录
**Step 4: Run check after docs update**
Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start -v`
Expected: PASS(行为与文档一致)
**Step 5: Commit**
```bash
git add docs/runtime/runtime-route.md docs/runtime/runtime-runbook.md
git commit -m "docs: document invite code behavior in signup flow"
```
### Task 7: 全量验证与风险审查(L2)
**Files:**
- Verify only
**Step 1: Run lint/type checks**
Run:
- `cd backend && uv run ruff check src tests`
- `cd backend && uv run basedpyright src`
Expected: 全部通过
**Step 2: Run test suites**
Run:
- `cd backend && uv run pytest tests/unit -v`
- `cd backend && uv run pytest tests/integration -v`
- `cd backend && uv run pytest tests/e2e/test_auth_flow.py -v`
Expected: 通过
**Step 3: Run mandatory review gates for L2**
- `refactor-cleaner` agent:确认无死代码/重复代码
- `code-reviewer` agent:检查 DB trigger、安全边界、可维护性
Expected: CRITICAL/HIGH 为 0
**Step 4: Security-specific sanity checks**
检查项:
- 未硬编码密钥
- SQL 逻辑无注入风险(trigger 中仅参数/列操作)
- 邀请码校验失败不泄露内部细节
**Step 5: Commit verification evidence (if needed in docs/PR notes)**
```bash
git add <updated verification notes if any>
git commit -m "chore: record invite code verification results"
```
---
## 交付验收标准
1. 新用户注册后必有 1 条专属邀请码。
2. 注册时传入有效邀请码会建立 `profiles.referred_by` 并增加 `used_count`
3. 无效邀请码不会阻断注册成功。
4. 支持运营码(`owner_id IS NULL`)与后续奖励扩展(`reward_config`)。
5. 文档已同步,测试与检查通过。
## 备注
- 本需求触发 L2(数据库迁移 + trigger + 多文件大改),必须走双审查 gate。
- 不在本期实现运营后台批量发码 API;仅完成数据层与注册链路支撑。