feat(auth): add wechat miniapp customer phone login
Implement Wechat Mini Program phone number login flow for end customers: - add PhoneLoginIn Pydantic request schema - create wechat.py module for Wechat API interactions and phone code exchange - add customer JWT utilities and require_customer authentication dependency - add new public API endpoints: /api/public/auth/phone-login and /api/public/auth/me - add required environment config variables and update example .env - add comprehensive test cases for the new auth flow and endpoints
This commit is contained in:
@@ -2,15 +2,28 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
from .shared import site_config
|
||||
from ..auth import create_customer_token, require_customer
|
||||
from ..database import get_db
|
||||
from ..models import Destination, DestinationAlias, Lead, Product
|
||||
from ..schemas import LeadCreateIn, ProductQuery, ProductStatus
|
||||
from ..models import Customer, Destination, DestinationAlias, Lead, Product
|
||||
from ..schemas import LeadCreateIn, PhoneLoginIn, ProductQuery, ProductStatus
|
||||
from ..serializers import destination_dict, public_product_dict
|
||||
from ..wechat import WechatApiError, WechatConfigError, exchange_phone_code
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def mask_phone(phone: str) -> str:
|
||||
normalized = phone.strip()
|
||||
if len(normalized) <= 7:
|
||||
return "***"
|
||||
return f"{normalized[:3]}****{normalized[-4:]}"
|
||||
|
||||
|
||||
def public_customer_dict(customer: Customer) -> dict:
|
||||
return {"id": customer.id, "phoneMasked": mask_phone(customer.phone)}
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def health():
|
||||
return {"ok": True, "service": "miniapp-api"}
|
||||
@@ -83,6 +96,30 @@ def list_destinations(db: Session = Depends(get_db)):
|
||||
return {"items": [destination_dict(destination) for destination in destinations]}
|
||||
|
||||
|
||||
@router.post("/api/public/auth/phone-login")
|
||||
def phone_login(body: PhoneLoginIn, db: Session = Depends(get_db)):
|
||||
try:
|
||||
phone = exchange_phone_code(body.code)
|
||||
except WechatConfigError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc)) from exc
|
||||
except WechatApiError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
|
||||
|
||||
customer = db.scalar(select(Customer).where(Customer.phone == phone))
|
||||
if not customer:
|
||||
customer = Customer(phone=phone)
|
||||
db.add(customer)
|
||||
db.flush()
|
||||
db.commit()
|
||||
db.refresh(customer)
|
||||
return {"token": create_customer_token(customer), "customer": public_customer_dict(customer)}
|
||||
|
||||
|
||||
@router.get("/api/public/auth/me")
|
||||
def current_customer(customer: Customer = Depends(require_customer)):
|
||||
return public_customer_dict(customer)
|
||||
|
||||
|
||||
@router.post("/api/public/leads", status_code=status.HTTP_201_CREATED)
|
||||
def create_lead(body: LeadCreateIn, db: Session = Depends(get_db)):
|
||||
lead = Lead(**body.model_dump(exclude_none=True), status="new")
|
||||
|
||||
Reference in New Issue
Block a user