Initial commit: AutonetSellCar platform with deployment system
- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
231
backend/app/api/verification.py
Normal file
231
backend/app/api/verification.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
Verification API Endpoints
|
||||
Handles email and phone verification for users
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import User
|
||||
from ..services import verification_service
|
||||
from .auth import get_current_user, get_current_user_optional
|
||||
|
||||
router = APIRouter(prefix="/verification", tags=["verification"])
|
||||
|
||||
|
||||
# Request/Response schemas
|
||||
class SendEmailCodeRequest(BaseModel):
|
||||
email: EmailStr
|
||||
language: str = "en"
|
||||
|
||||
|
||||
class SendPhoneCodeRequest(BaseModel):
|
||||
phone: str
|
||||
language: str = "en"
|
||||
|
||||
|
||||
class VerifyCodeRequest(BaseModel):
|
||||
code: str
|
||||
email: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
|
||||
|
||||
class VerificationResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
|
||||
|
||||
class VerificationStatusResponse(BaseModel):
|
||||
email_verified: bool
|
||||
phone_verified: bool
|
||||
email: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
|
||||
|
||||
# Email Verification Endpoints
|
||||
@router.post("/email/send", response_model=VerificationResponse)
|
||||
async def send_email_code(
|
||||
request: SendEmailCodeRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user_optional)
|
||||
):
|
||||
"""Send email verification code"""
|
||||
user_id = current_user.id if current_user else None
|
||||
|
||||
# If user is logged in, only allow sending to their email
|
||||
if current_user and current_user.email != request.email:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="You can only verify your own email address"
|
||||
)
|
||||
|
||||
success, message = await verification_service.send_email_verification(
|
||||
db=db,
|
||||
email=request.email,
|
||||
user_id=user_id,
|
||||
language=request.language
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
|
||||
return VerificationResponse(success=True, message=message)
|
||||
|
||||
|
||||
@router.post("/email/verify", response_model=VerificationResponse)
|
||||
async def verify_email_code(
|
||||
request: VerifyCodeRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user_optional)
|
||||
):
|
||||
"""Verify email code"""
|
||||
email = request.email
|
||||
if current_user:
|
||||
email = current_user.email
|
||||
|
||||
if not email:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email is required"
|
||||
)
|
||||
|
||||
success, message = verification_service.verify_code(
|
||||
db=db,
|
||||
code=request.code,
|
||||
code_type="email",
|
||||
email=email
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
|
||||
# If user is logged in, mark their email as verified
|
||||
if current_user:
|
||||
verification_service.mark_email_verified(db, current_user)
|
||||
|
||||
return VerificationResponse(success=True, message=message)
|
||||
|
||||
|
||||
# Phone Verification Endpoints
|
||||
@router.post("/phone/send", response_model=VerificationResponse)
|
||||
async def send_phone_code(
|
||||
request: SendPhoneCodeRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user) # Requires login
|
||||
):
|
||||
"""Send phone verification code (requires login)"""
|
||||
success, message = await verification_service.send_sms_verification(
|
||||
db=db,
|
||||
phone=request.phone,
|
||||
user_id=current_user.id,
|
||||
language=request.language
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
|
||||
return VerificationResponse(success=True, message=message)
|
||||
|
||||
|
||||
@router.post("/phone/verify", response_model=VerificationResponse)
|
||||
async def verify_phone_code(
|
||||
request: VerifyCodeRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user) # Requires login
|
||||
):
|
||||
"""Verify phone code (requires login)"""
|
||||
if not request.phone:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Phone number is required"
|
||||
)
|
||||
|
||||
# Normalize phone number
|
||||
phone = request.phone.strip().replace(" ", "").replace("-", "")
|
||||
if not phone.startswith("+"):
|
||||
if phone.startswith("9") and len(phone) == 8:
|
||||
phone = "+976" + phone
|
||||
|
||||
success, message = verification_service.verify_code(
|
||||
db=db,
|
||||
code=request.code,
|
||||
code_type="phone",
|
||||
phone=phone
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
|
||||
# Mark phone as verified
|
||||
verification_service.mark_phone_verified(db, current_user, phone)
|
||||
|
||||
return VerificationResponse(success=True, message=message)
|
||||
|
||||
|
||||
# Status Endpoint
|
||||
@router.get("/status", response_model=VerificationStatusResponse)
|
||||
async def get_verification_status(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Get current user's verification status"""
|
||||
return VerificationStatusResponse(
|
||||
email_verified=current_user.email_verified or False,
|
||||
phone_verified=current_user.phone_verified or False,
|
||||
email=current_user.email,
|
||||
phone=current_user.phone
|
||||
)
|
||||
|
||||
|
||||
# Pre-registration email verification (for signup flow)
|
||||
@router.post("/email/send-preregister", response_model=VerificationResponse)
|
||||
async def send_preregister_email_code(
|
||||
request: SendEmailCodeRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Send email verification code for new registration (no login required)"""
|
||||
# Check if email is already registered
|
||||
existing = db.query(User).filter(User.email == request.email).first()
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="This email is already registered"
|
||||
)
|
||||
|
||||
success, message = await verification_service.send_email_verification(
|
||||
db=db,
|
||||
email=request.email,
|
||||
user_id=None,
|
||||
language=request.language
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
|
||||
return VerificationResponse(success=True, message=message)
|
||||
|
||||
|
||||
@router.post("/email/verify-preregister", response_model=VerificationResponse)
|
||||
async def verify_preregister_email_code(
|
||||
request: VerifyCodeRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Verify email code for new registration (no login required)"""
|
||||
if not request.email:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email is required"
|
||||
)
|
||||
|
||||
success, message = verification_service.verify_code(
|
||||
db=db,
|
||||
code=request.code,
|
||||
code_type="email",
|
||||
email=request.email
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
|
||||
return VerificationResponse(success=True, message=message)
|
||||
Reference in New Issue
Block a user