Files
Genarrative/.hermes/plans/2026-05-04_032321-invite-code-validity-and-admin-confirmation.md
kdletters 9f3e34e81a feat: add invite code validity controls
- Add invite code starts/expires fields across contracts, API, Spacetime bindings, and admin UI
- Enforce pending/expired invite code redemption behavior and expose admin status
- Add admin write-operation confirmation guard and documentation
- Add invite code contract/runtime tests
2026-05-04 13:54:40 +08:00

9.4 KiB
Raw Blame History

邀请码有效期与后台二次确认实施计划

For Hermes: 按 plan 模式,仅输出并保存实施计划,不直接改业务代码。

Goal: 为邀请码新增开始日期与截止日期,并让后台所有会修改数据的操作在提交前增加二次确认,降低误操作风险。

Architecture: 邀请码仍作为“用户稳定邀请身份码”保留,不做停用删除;在数据层增加 starts_at / expires_at,前台填写邀请码时按时间窗校验,后台列表与编辑页展示状态。后台所有写操作统一先弹二次确认,再真正调用 API避免对兑换码、邀请码、任务配置等管理动作误触发。

Tech Stack: Rust / SpacetimeDB / Axum / shared-contracts / TS + React 的 admin-web。


当前上下文

  • 邀请码当前只有 user_idinvite_codemetadata_jsoncreated_atupdated_at,没有状态字段。
  • 目前后台存在邀请码管理入口,但没有停用能力,也没有有效期概念。
  • 邀请码用于 redeem_profile_referral_invite_code 时的实时校验,适合增加“时间窗”而不是“禁用删除”。
  • 后台已存在兑换码、任务配置等可写操作;本次要求把所有后台操作统一加二次确认,包括新增、编辑、禁用、删除等写入口。

设计原则

  1. 邀请码不做软删除:保留历史记录和邀请链路。
  2. 有效期由时间窗推导
    • starts_at 为空表示立即生效。
    • expires_at 为空表示长期有效。
  3. 前台只拒绝新绑定:已绑定关系不回溯修改。
  4. 后台写操作统一确认:所有会触发 POST / PATCH / DELETE 的管理动作,在真正提交前必须弹出二次确认。
  5. 尽量少改接口语义:优先在现有 admin upsert/list 体系内扩展字段,而不是新增一套并行 API。

方案概要

邀请码时间窗

新增字段:

  • starts_at: Option<Timestamp>
  • expires_at: Option<Timestamp>

校验规则:

  • 当前时间 < starts_at:返回“邀请码未生效”
  • 当前时间 >= expires_at:返回“邀请码已过期”
  • 其他情况允许填写

建议把状态展示为:

  • 未生效
  • 有效
  • 已过期
  • 长期有效(两个字段都为空或仅无截止)

后台二次确认

对 admin-web 所有管理动作统一加确认弹窗/对话框,至少覆盖:

  • 兑换码新增/更新
  • 兑换码停用
  • 邀请码新增/更新
  • 任务配置新增/更新
  • 任务配置停用
  • 其他后续新增的后台写操作

确认文案要求:

  • 显示对象标识(如 code / inviteCode / taskId
  • 显示操作类型(新增 / 更新 / 停用)
  • 明确提醒“该操作会立即影响线上数据”
  • 允许取消返回,不调用 API

预期修改文件

1. 服务端领域与契约

  • server-rs/crates/spacetime-module/src/runtime/profile.rs
    • ProfileInviteCode 表结构新增开始/截止字段
    • 邀请码 upsert 逻辑写入时间窗
    • 邀请码 redeem 逻辑增加时间窗校验
    • 邀请中心快照补充时间窗/状态
  • server-rs/crates/spacetime-module/src/migration.rs
    • 兼容旧表数据,给旧邀请码补默认空值
  • server-rs/crates/shared-contracts/src/runtime*.rs 或对应生成/手写契约文件
    • AdminUpsertProfileInviteCodeRequest 扩展字段
    • ProfileInviteCodeAdminResponse 扩展字段
    • 如需要,增加时间窗相关状态枚举或派生字段
  • server-rs/crates/spacetime-client/src/module_bindings/*
    • 重新生成 bindings
    • mapper 补齐新字段

2. API Server

  • server-rs/crates/api-server/src/runtime_profile.rs
    • 接收/转发邀请码时间窗参数
    • 返回新增字段给后台
    • 如需要,调整校验错误文案
  • server-rs/crates/api-server/src/app.rs
    • 若有新路由或错误码需挂接,在此统一登记

3. Admin Web

  • apps/admin-web/src/api/adminApiTypes.ts
    • 增加邀请码时间窗字段
    • 如有需要,增加后台操作请求结构字段
  • apps/admin-web/src/api/adminApiClient.ts
    • 透传新的请求/响应字段
  • apps/admin-web/src/app/adminRoutes.ts
    • 不一定需要改,但如果新增独立页面/子面板,需要在此登记
  • apps/admin-web/src/styles/admin.css
    • 确认弹窗与时间窗展示样式
  • apps/admin-web/src/** 实际管理页面组件
    • 邀请码编辑表单
    • 邀请码列表状态展示
    • 所有写操作前的二次确认弹窗

4. 文档

  • docs/technical/docs/design/
    • 补一份邀请码时间窗与后台确认交互说明
  • 如现有文档已经覆盖后台管理规范,则优先补充现有文档,不重复造新说明页

分步实施计划

Task 1: 明确数据模型与契约扩展

Objective: 定义邀请码开始/截止日期字段及其在响应中的展示方式。

要点:

  • 确认字段名采用 starts_at / expires_at,避免与现有字段语义冲突。
  • 确认时间类型统一用 Timestamp / 毫秒微秒整数转换策略。
  • 明确返回给后台的字段是否需要附带派生状态(如 status)。

产出:

  • 契约字段定义
  • 状态枚举/派生规则

Task 2: 更新 SpacetimeDB 表与迁移

Objective: 让邀请码表可保存有效期,并兼容旧数据。

要点:

  • 修改 ProfileInviteCode 表结构。
  • 更新迁移逻辑,旧记录默认无开始/截止。
  • 检查是否需要补充索引或查询辅助字段。

验证:

  • 旧数据能正常读取。
  • 新数据能写入开始/截止。

Task 3: 实现邀请填写时的时间窗校验

Objective: 在邀请码被填写时正确拒绝未生效或已过期的邀请码。

要点:

  • redeem_profile_referral_invite_code_record 内增加开始/截止校验。
  • 保持“自己的邀请码不能填”“邀请码不存在”等原有错误优先级清晰。
  • 保留历史绑定关系不受影响。

验证:

  • 未到开始时间时返回明确错误。
  • 超过截止时间时返回明确错误。
  • 正常区间可绑定成功。

Task 4: 扩展后台邀请码管理接口

Objective: 让后台可以创建/修改邀请码时间窗,并在列表中查看状态。

要点:

  • 扩展 AdminUpsertProfileInviteCodeRequest
  • 扩展 ProfileInviteCodeAdminResponse
  • api-server 接口负责接收新字段并转发。
  • 列表接口返回可读时间字段与状态。

验证:

  • 后台表单提交后,返回结果包含时间窗信息。
  • 列表页能看到状态与时间。

Task 5: 给后台所有写操作加二次确认

Objective: 统一拦截所有后台写动作,避免误点直接生效。

覆盖范围建议:

  • 邀请码新增/更新
  • 兑换码新增/更新/停用
  • 任务配置新增/更新/停用
  • 后续新增的管理写操作

实现要求:

  • 在真正调用 API 之前弹出确认框。
  • 确认框需要展示对象名、操作类型、影响范围。
  • 取消后不发送请求。
  • 尽量抽象出通用确认组件/通用 action 包装函数,避免每个页面重复写。

验证:

  • 点击“保存”不会直接提交,需先确认。
  • 点击“取消”不会发请求。
  • 所有后台写入口行为一致。

Task 6: 补充文档与交互说明

Objective: 把新规则写进项目文档,避免后续实现偏差。

要点:

  • 记录邀请码时间窗语义。
  • 记录后台二次确认规范。
  • 说明哪些动作属于“必须确认”的写操作。

测试与验证

服务端

  • 邀请码时间窗单测 / 集成测试
  • 邀请码 redeem 流程回归测试
  • 旧数据兼容测试

API / 前端

  • 管理后台列表展示正确
  • 表单提交能回传新字段
  • 二次确认取消后不请求接口
  • 二次确认确认后正常提交

推荐验证命令

  • 视项目现有脚本执行对应后端测试
  • 前端按 admin-web 构建/测试脚本验证
  • 如涉及生成绑定,先确认生成产物无漏字段

风险与权衡

  1. 时间字段格式不统一

    • 风险:前后端对时间单位理解不一致。
    • 处理:在契约层明确是 ISO 字符串还是微秒整数,并全链路统一。
  2. 后台“所有操作”范围过大

    • 风险:遗漏某些写入口。
    • 处理:先枚举现有写 API再做统一确认封装。
  3. 邀请码过期后历史链接解释成本

    • 风险:用户误以为历史邀请码失效影响已绑定关系。
    • 处理:文案明确“仅影响新填写,不影响已绑定记录”。
  4. 契约与生成绑定联动较多

    • 风险:字段变更后生成文件数量较多。
    • 处理:先改源契约与服务端,再统一重生成 bindings。

待确认问题

  1. starts_at / expires_at 在接口里要返回 ISO 字符串 还是 微秒整数
  2. 后台二次确认是否统一用一个全局弹窗组件,还是页面级本地实现?
  3. 邀请码列表是否需要直接展示“状态标签”还是只展示时间字段由前端推导?
  4. 现有后台所有写操作里,是否还要覆盖调试类接口,还是仅覆盖业务管理接口?

建议执行顺序

  1. 先确认时间字段格式与确认弹窗范围。
  2. 再改服务端契约与迁移。
  3. 再改 redeem 校验与后台接口。
  4. 最后统一改 admin-web 的二次确认与表单展示。

结论: 这是一个适合分阶段落地的改动,建议先做“邀请码时间窗 + 后台统一二次确认”的基础能力,再补交互细节。