dacb37e3e1
- onboarding_completion 表(user_id+device_id 唯一约束)+ model + repository - POST /api/v1/user/onboarding/complete 标记完成;登录响应 TokenWithUser.onboarding_completed (按登录请求带的 device_id 判定是否已走过引导,跨卸载重装稳定) - alembic 迁移 onboarding_completion(rebase 到最新 main 后已链在 coupon_daily_completion 之后,单 head) - docs/database 文档 + tests/test_onboarding.py + run.bat(本地启动脚本) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
36 lines
1.4 KiB
Python
36 lines
1.4 KiB
Python
"""新手引导完成标记的读写(按 user_id + device_id 去重)。
|
|
|
|
登录时 [is_completed] 判断该 (账号, 设备) 是否走过引导;走完引导时 [mark_completed] 写一条。
|
|
device_id 为空(老客户端 / 取不到 ANDROID_ID)一律按"未完成"处理,即照常走引导,不误跳过。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.onboarding import OnboardingCompletion
|
|
|
|
|
|
def is_completed(db: Session, *, user_id: int, device_id: str) -> bool:
|
|
"""该 (账号, 设备) 是否已完成新手引导。device_id 为空 → 一律 False。"""
|
|
if not device_id:
|
|
return False
|
|
stmt = select(OnboardingCompletion.id).where(
|
|
OnboardingCompletion.user_id == user_id,
|
|
OnboardingCompletion.device_id == device_id,
|
|
)
|
|
return db.execute(stmt).first() is not None
|
|
|
|
|
|
def mark_completed(db: Session, *, user_id: int, device_id: str) -> None:
|
|
"""走完引导时写入。device_id 为空忽略;同 (账号, 设备) 已存在则幂等(靠唯一约束兜并发)。"""
|
|
if not device_id:
|
|
return
|
|
db.add(OnboardingCompletion(user_id=user_id, device_id=device_id))
|
|
try:
|
|
db.commit()
|
|
except IntegrityError:
|
|
# 并发 / 重复提交撞唯一约束:已有行即视为成功。
|
|
db.rollback()
|