Implement student roster login gating
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -9,7 +10,7 @@ from app.core.dependencies import get_current_admin
|
||||
from app.core.responses import api_success
|
||||
from app.models.admin import Admin
|
||||
from app.models.user import User
|
||||
from app.schemas.admin import AdminUserUpdateRequest
|
||||
from app.schemas.admin import AdminUserCreateRequest, AdminUserImportRequest, AdminUserUpdateRequest
|
||||
from app.services.admin_service import OperationLogService
|
||||
|
||||
router = APIRouter()
|
||||
@@ -29,6 +30,86 @@ def list_users(
|
||||
return api_success([_user_dict(user) for user in users])
|
||||
|
||||
|
||||
@router.post("/user")
|
||||
def create_user(
|
||||
payload: AdminUserCreateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
phone = _normalize_phone(payload.phone)
|
||||
_validate_phone(phone)
|
||||
existing = db.scalar(select(User).where(User.phone == phone))
|
||||
if existing is not None and not existing.is_deleted:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="手机号已存在")
|
||||
|
||||
if existing is not None:
|
||||
user = existing
|
||||
user.is_deleted = 0
|
||||
user.daily_chat_used = 0
|
||||
else:
|
||||
user = User(phone=phone, daily_chat_used=0)
|
||||
|
||||
_apply_user_payload(user, payload)
|
||||
db.add(user)
|
||||
db.flush()
|
||||
OperationLogService.write(db, admin_id=current_admin.id, module="user", action="create", target_id=user.id)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return api_success(_user_dict(user))
|
||||
|
||||
|
||||
@router.post("/user/import")
|
||||
def import_users(
|
||||
payload: AdminUserImportRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
if not payload.students:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请提供学员数据")
|
||||
|
||||
created = 0
|
||||
updated = 0
|
||||
failed: list[dict] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
for index, item in enumerate(payload.students, start=1):
|
||||
phone = _normalize_phone(item.phone)
|
||||
name = item.name.strip()
|
||||
try:
|
||||
_validate_phone(phone)
|
||||
if not name:
|
||||
raise ValueError("姓名不能为空")
|
||||
if phone in seen:
|
||||
raise ValueError("手机号在本次导入中重复")
|
||||
seen.add(phone)
|
||||
|
||||
user = db.scalar(select(User).where(User.phone == phone))
|
||||
if user is None:
|
||||
user = User(phone=phone, daily_chat_used=0)
|
||||
created += 1
|
||||
else:
|
||||
user.is_deleted = 0
|
||||
updated += 1
|
||||
_apply_user_payload(user, item)
|
||||
db.add(user)
|
||||
except ValueError as exc:
|
||||
failed.append({"row": index, "phone": item.phone, "reason": str(exc)})
|
||||
|
||||
if created or updated:
|
||||
db.flush()
|
||||
OperationLogService.write(
|
||||
db,
|
||||
admin_id=current_admin.id,
|
||||
module="user",
|
||||
action="import",
|
||||
target_id=None,
|
||||
result="SUCCESS",
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return api_success({"created": created, "updated": updated, "failed": len(failed), "failures": failed})
|
||||
|
||||
|
||||
@router.get("/user/{user_id}")
|
||||
def user_detail(
|
||||
user_id: int,
|
||||
@@ -96,3 +177,27 @@ def _user_dict(user: User) -> dict:
|
||||
"lastLoginAt": user.last_login_at,
|
||||
"createdAt": user.created_at,
|
||||
}
|
||||
|
||||
|
||||
def _apply_user_payload(user: User, payload: AdminUserCreateRequest) -> None:
|
||||
settings_limit = 100
|
||||
try:
|
||||
from app.core.config import get_settings
|
||||
|
||||
settings_limit = get_settings().default_daily_chat_limit
|
||||
except Exception:
|
||||
pass
|
||||
user.name = payload.name.strip()
|
||||
user.nickname = payload.nickname.strip() if payload.nickname else None
|
||||
user.status = payload.status
|
||||
user.daily_chat_limit = payload.dailyChatLimit if payload.dailyChatLimit is not None else settings_limit
|
||||
user.expired_at = payload.expiredAt.replace(tzinfo=None) if payload.expiredAt is not None else None
|
||||
|
||||
|
||||
def _normalize_phone(phone: str) -> str:
|
||||
return re.sub(r"\D", "", phone or "")
|
||||
|
||||
|
||||
def _validate_phone(phone: str) -> None:
|
||||
if not re.fullmatch(r"1[3-9]\d{9}", phone):
|
||||
raise ValueError("手机号格式不正确")
|
||||
|
||||
@@ -13,8 +13,8 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/sms/send")
|
||||
def send_sms(payload: SendSmsRequest) -> dict:
|
||||
AuthService.send_sms_code(payload.phone)
|
||||
def send_sms(payload: SendSmsRequest, db: Session = Depends(get_db)) -> dict:
|
||||
AuthService.send_sms_code(db, payload.phone)
|
||||
return api_success(message="发送成功")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user