feat(onboarding): 新增重置新手引导端点 /api/v1/user/onboarding/reset
- user.py:新增 reset_onboarding 端点(需登录),删当前账号+该设备的引导完成标记, 与 /onboarding/complete 互逆;幂等(无记录也返回 ok),只删自己不影响他人 - repositories/onboarding.py:新增 delete_completion(user_id, device_id), device_id 为空忽略,返回删除行数 - tests:补 test_onboarding_reset_makes_relogin_reonboard,覆盖 完成 → 重置 → 再登录变未完成 + 重复重置幂等 配合客户端「开发设置 → 重置新手引导」:先删后端行再退登录重开,复现全新用户。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,18 @@ def complete_onboarding(
|
||||
return OkResponse()
|
||||
|
||||
|
||||
@router.post("/onboarding/reset", response_model=OkResponse, summary="重置新手引导(删该 设备+账号 完成标记,下次登录重走)")
|
||||
def reset_onboarding(
|
||||
req: OnboardingCompleteRequest, user: CurrentUser, db: DbSession
|
||||
) -> OkResponse:
|
||||
"""删该 (当前账号, device_id) 的引导完成标记 → 下次登录 onboarding_completed=false,客户端重走。
|
||||
与 /onboarding/complete 互逆。device_id 取客户端硬件级 ANDROID_ID(与登录/complete 一致)。
|
||||
幂等:无记录也返回 ok。仅删自己(当前 JWT 用户)这台设备的记录,不影响别的账号/设备。"""
|
||||
deleted = onboarding_repo.delete_completion(db, user_id=user.id, device_id=req.device_id)
|
||||
logger.info("onboarding reset user_id=%d device_len=%d deleted=%d", user.id, len(req.device_id), deleted)
|
||||
return OkResponse()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/onboarding/status",
|
||||
response_model=OnboardingStatusResponse,
|
||||
|
||||
@@ -5,7 +5,7 @@ device_id 为空(老客户端 / 取不到 ANDROID_ID)一律按"未完成"处理,
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -33,3 +33,18 @@ def mark_completed(db: Session, *, user_id: int, device_id: str) -> None:
|
||||
except IntegrityError:
|
||||
# 并发 / 重复提交撞唯一约束:已有行即视为成功。
|
||||
db.rollback()
|
||||
|
||||
|
||||
def delete_completion(db: Session, *, user_id: int, device_id: str) -> int:
|
||||
"""删该 (账号, 设备) 的引导完成标记 → 下次登录 is_completed=False → 客户端重走。
|
||||
与 [mark_completed] 互逆。device_id 为空忽略(返 0);无记录也幂等(返 0)。返回删除行数。"""
|
||||
if not device_id:
|
||||
return 0
|
||||
result = db.execute(
|
||||
delete(OnboardingCompletion).where(
|
||||
OnboardingCompletion.user_id == user_id,
|
||||
OnboardingCompletion.device_id == device_id,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return result.rowcount
|
||||
|
||||
@@ -65,3 +65,28 @@ def test_onboarding_missing_device_id_treated_as_incomplete(client) -> None:
|
||||
phone = "13800138003"
|
||||
data = _login(client, phone, device_id=None)
|
||||
assert data["onboarding_completed"] is False
|
||||
|
||||
|
||||
def _reset(client, access: str, device_id: str):
|
||||
return client.post(
|
||||
"/api/v1/user/onboarding/reset",
|
||||
json={"device_id": device_id},
|
||||
headers={"Authorization": f"Bearer {access}"},
|
||||
)
|
||||
|
||||
|
||||
def test_onboarding_reset_makes_relogin_reonboard(client) -> None:
|
||||
"""重置(删完成标记)后,同 (账号, 设备) 再登录 → onboarding_completed=False,重走引导。
|
||||
对应客户端「开发设置 → 重置新手引导」:先删后端行,再退登录重开。"""
|
||||
phone = "13800138004"
|
||||
dev = "android-id-reset"
|
||||
|
||||
# ① 走完引导 → 标记完成 → 再登录已完成
|
||||
access = _login(client, phone, dev)["access_token"]
|
||||
assert _mark_complete(client, access, dev).status_code == 200
|
||||
assert _login(client, phone, dev)["onboarding_completed"] is True
|
||||
|
||||
# ② 重置(幂等:重复重置也 200)→ 再登录变未完成 → 重走引导
|
||||
assert _reset(client, access, dev).status_code == 200
|
||||
assert _reset(client, access, dev).json()["ok"] is True
|
||||
assert _login(client, phone, dev)["onboarding_completed"] is False
|
||||
|
||||
Reference in New Issue
Block a user