feat(onboarding): force_onboarding 改设备维度 + admin 设备管理 + status 查询

废弃 force_onboarding(运营按用户强制引导,客户端不认——被 onboarding_completed 压过),
改用 onboarding_completion(设备+账号 维度),admin 直接增删该表记录控制重走。

- 删 force_onboarding: User 列 + auth/admin schema + admin 接口 + mutations + repo
  clear + /onboarding/complete 调用 + 3 测试;新增 alembic 迁移删列
- admin 设备管理: 列设备(按 device 聚合) / 重置单设备 / 全部重置(清 onboarding_completion)
- 新增 GET /api/v1/user/onboarding/status: 客户端已登录启动查"本设备是否要重走引导"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:51:47 +08:00
parent 57ddcd356b
commit e8bd12cc1f
15 changed files with 227 additions and 147 deletions
-28
View File
@@ -15,7 +15,6 @@ from app.admin.schemas.user import (
GrantCashRequest,
GrantCoinsRequest,
SetDebugTraceRequest,
SetForceOnboardingRequest,
SetUserStatusRequest,
)
from app.models.admin import AdminUser
@@ -102,33 +101,6 @@ def set_user_debug_trace(
return OkResponse()
@router.post("/{user_id}/force-onboarding", response_model=OkResponse, summary="一键开启/取消新手引导")
def set_user_force_onboarding(
user_id: int,
body: SetForceOnboardingRequest,
request: Request,
admin: Annotated[AdminUser, Depends(require_role("operator"))],
db: AdminDb,
) -> OkResponse:
"""运营「一键开启新手引导」:置 user.force_onboarding。开启后该用户下次启动 App(/me 带出此字段)
会被强制重走引导教程——即便此前已看完;走完引导后端自动清回 false,不无限循环。
仅对支持 force_onboarding 的 App 版本生效(老版本忽略该字段)。"""
user = user_repo.get_user_by_id(db, user_id)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
if user.status == "deleted":
raise HTTPException(status_code=400, detail="已注销账号不可操作")
before = user.force_onboarding
# 业务写 + 审计写同一事务(commit=False),最后一起 commit(同 set_user_status / debug-trace)
mutations.set_user_force_onboarding(db, user, enabled=body.enabled, commit=False)
write_audit(
db, admin, action="user.force_onboarding.set", target_type="user", target_id=user_id,
detail={"before": before, "after": body.enabled}, ip=get_client_ip(request), commit=False,
)
db.commit()
return OkResponse()
@router.post("/{user_id}/coins", response_model=OkResponse, summary="手动增减金币(带审计)")
def grant_user_coins(
user_id: int,