# 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 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;仅完成数据层与注册链路支撑。