feat: 实现密码重置功能与用户搜索API,优化注册登录流程
- 新增忘记密码页面与重置密码确认流程(前端+后端) - 修复注册验证码页登录跳转路由 - 新增用户搜索API(按邮箱查询) - 简化infra脚本,统一为app.sh - 补充密码重置与用户API测试覆盖 - 更新runtime文档与AGENTS配置
This commit is contained in:
@@ -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: 更新注册请求 Schema(TDD)
|
||||
|
||||
**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 metadata(TDD)
|
||||
|
||||
**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: FAIL(metadata 未包含 `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;仅完成数据层与注册链路支撑。
|
||||
Reference in New Issue
Block a user