e8bd12cc1f
废弃 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>
87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
"""admin 设备维度新手引导管理:列设备 / 重置单设备 / 全部重置。
|
|
|
|
背景:原 force_onboarding(运营「按用户」强制引导)客户端不认(被 onboarding_completed 压过),
|
|
已废弃。改为直接操作 onboarding_completion(设备+账号 维度):删记录 → 该(设备,账号)的
|
|
onboarding_completed 变 false → 客户端任何版本下次登录都重走引导。可靠、不依赖客户端实现。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
|
|
from app.admin.audit import write_audit
|
|
from app.admin.deps import AdminDb, get_client_ip, get_current_admin, require_role
|
|
from app.admin.repositories import mutations, queries
|
|
from app.admin.schemas.common import CursorPage, OkResponse
|
|
from app.admin.schemas.onboarding import DeviceOnboardingItem, ResetAllRequest
|
|
from app.models.admin import AdminUser
|
|
|
|
router = APIRouter(
|
|
prefix="/admin/api/onboarding",
|
|
tags=["admin-onboarding"],
|
|
dependencies=[Depends(get_current_admin)],
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/devices",
|
|
response_model=CursorPage[DeviceOnboardingItem],
|
|
summary="设备列表(按设备聚合走过引导的账号)",
|
|
)
|
|
def list_devices(db: AdminDb) -> CursorPage[DeviceOnboardingItem]:
|
|
"""列出走过新手引导的设备(ANDROID_ID),按最近完成时间倒序。没走过引导的设备不在表里、
|
|
本就会引导,无需管理,故不列。当前全量返回(上限 500;设备量大再加分页/搜索),
|
|
next_cursor 恒 None。"""
|
|
rows = queries.list_onboarding_devices(db)
|
|
return CursorPage(
|
|
items=[DeviceOnboardingItem(**r) for r in rows],
|
|
next_cursor=None,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/devices/{device_id}/reset",
|
|
response_model=OkResponse,
|
|
summary="重置单个设备(该设备所有账号下次重走引导)",
|
|
)
|
|
def reset_device(
|
|
device_id: str,
|
|
request: Request,
|
|
admin: Annotated[AdminUser, Depends(require_role("operator"))],
|
|
db: AdminDb,
|
|
) -> OkResponse:
|
|
"""删该设备的全部 onboarding 完成记录 → 该设备上所有账号下次登录重走引导。"""
|
|
# 业务写 + 审计写同一事务(commit=False),最后一起 commit(同 users 各写接口)
|
|
deleted = mutations.delete_device_onboarding(db, device_id, commit=False)
|
|
write_audit(
|
|
db, admin, action="device_onboarding.reset", target_type="device", target_id=device_id,
|
|
detail={"deleted_rows": deleted}, ip=get_client_ip(request), commit=False,
|
|
)
|
|
db.commit()
|
|
return OkResponse()
|
|
|
|
|
|
@router.post(
|
|
"/reset-all",
|
|
response_model=OkResponse,
|
|
summary="全部重设(清空所有设备引导记录)",
|
|
)
|
|
def reset_all(
|
|
body: ResetAllRequest,
|
|
request: Request,
|
|
admin: Annotated[AdminUser, Depends(require_role("operator"))],
|
|
db: AdminDb,
|
|
) -> OkResponse:
|
|
"""清空 onboarding_completion 表 → 所有用户在所有设备下次登录都重走引导。
|
|
需 confirm=true 防误触(前端另有二次确认弹窗)。"""
|
|
if not body.confirm:
|
|
raise HTTPException(status_code=400, detail="需 confirm=true 确认")
|
|
deleted = mutations.delete_all_onboarding(db, commit=False)
|
|
write_audit(
|
|
db, admin, action="device_onboarding.reset_all", target_type="onboarding", target_id=None,
|
|
detail={"deleted_rows": deleted}, ip=get_client_ip(request), commit=False,
|
|
)
|
|
db.commit()
|
|
return OkResponse()
|