- 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>
255 lines
8.3 KiB
Python
255 lines
8.3 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
from datetime import datetime
|
|
from typing import List
|
|
from ..database import get_db
|
|
from ..models import User, DealerApplication, DealerInfo
|
|
from ..models.dealer import generate_dealer_code
|
|
from ..schemas import (
|
|
DealerApplicationCreate, DealerApplicationResponse,
|
|
DealerApplicationReject, DealerInfoResponse, DealerPublicInfo,
|
|
)
|
|
from .auth import get_current_user
|
|
from .notification import notify_dealer_approved, notify_dealer_rejected
|
|
|
|
router = APIRouter(prefix="/dealer", tags=["dealer"])
|
|
|
|
|
|
@router.post("/apply", response_model=DealerApplicationResponse)
|
|
def apply_dealer(
|
|
application: DealerApplicationCreate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Submit a dealer application"""
|
|
# Check if user already has a pending or approved application
|
|
existing = db.query(DealerApplication).filter(
|
|
DealerApplication.user_id == current_user.id,
|
|
DealerApplication.status.in_(["pending", "approved"])
|
|
).first()
|
|
|
|
if existing:
|
|
if existing.status == "approved":
|
|
raise HTTPException(status_code=400, detail="You are already a dealer")
|
|
raise HTTPException(status_code=400, detail="You already have a pending application")
|
|
|
|
# Check if user is already a dealer
|
|
if current_user.is_dealer:
|
|
raise HTTPException(status_code=400, detail="You are already a dealer")
|
|
|
|
# Create new application
|
|
new_application = DealerApplication(
|
|
user_id=current_user.id,
|
|
business_name=application.business_name,
|
|
business_number=application.business_number,
|
|
real_name=application.real_name,
|
|
id_number_encrypted=application.id_number, # TODO: Encrypt this properly
|
|
phone=application.phone,
|
|
bank_name=application.bank_name,
|
|
bank_account=application.bank_account,
|
|
account_holder=application.account_holder,
|
|
photo_url=application.photo_url,
|
|
status="pending"
|
|
)
|
|
|
|
db.add(new_application)
|
|
db.commit()
|
|
db.refresh(new_application)
|
|
return new_application
|
|
|
|
|
|
@router.get("/my-application", response_model=DealerApplicationResponse)
|
|
def get_my_application(
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get current user's dealer application"""
|
|
application = db.query(DealerApplication).filter(
|
|
DealerApplication.user_id == current_user.id
|
|
).order_by(DealerApplication.applied_at.desc()).first()
|
|
|
|
if not application:
|
|
raise HTTPException(status_code=404, detail="No application found")
|
|
|
|
return application
|
|
|
|
|
|
@router.get("/my-info", response_model=DealerInfoResponse)
|
|
def get_my_dealer_info(
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get current user's dealer info (if approved)"""
|
|
if not current_user.is_dealer:
|
|
raise HTTPException(status_code=403, detail="You are not a dealer")
|
|
|
|
dealer_info = db.query(DealerInfo).filter(
|
|
DealerInfo.user_id == current_user.id
|
|
).first()
|
|
|
|
if not dealer_info:
|
|
raise HTTPException(status_code=404, detail="Dealer info not found")
|
|
|
|
return dealer_info
|
|
|
|
|
|
@router.get("/list", response_model=List[DealerPublicInfo])
|
|
def list_dealers(
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get list of active dealers (public info only)"""
|
|
dealers = db.query(DealerInfo).filter(
|
|
DealerInfo.is_active == True
|
|
).all()
|
|
return dealers
|
|
|
|
|
|
# Admin endpoints
|
|
@router.get("/admin/applications", response_model=List[DealerApplicationResponse])
|
|
def get_applications(
|
|
status_filter: str = None,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""[Admin] Get all dealer applications"""
|
|
if not current_user.is_admin:
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
|
|
query = db.query(DealerApplication)
|
|
if status_filter:
|
|
query = query.filter(DealerApplication.status == status_filter)
|
|
|
|
applications = query.order_by(DealerApplication.applied_at.desc()).all()
|
|
return applications
|
|
|
|
|
|
@router.put("/admin/applications/{application_id}/approve", response_model=DealerInfoResponse)
|
|
def approve_application(
|
|
application_id: int,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""[Admin] Approve a dealer application"""
|
|
if not current_user.is_admin:
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
|
|
application = db.query(DealerApplication).filter(
|
|
DealerApplication.id == application_id
|
|
).first()
|
|
|
|
if not application:
|
|
raise HTTPException(status_code=404, detail="Application not found")
|
|
|
|
if application.status != "pending":
|
|
raise HTTPException(status_code=400, detail="Application is not pending")
|
|
|
|
# Generate unique dealer code
|
|
dealer_code = generate_dealer_code()
|
|
while db.query(DealerInfo).filter(DealerInfo.dealer_code == dealer_code).first():
|
|
dealer_code = generate_dealer_code()
|
|
|
|
# Create dealer info
|
|
dealer_info = DealerInfo(
|
|
user_id=application.user_id,
|
|
dealer_code=dealer_code,
|
|
business_name=application.business_name,
|
|
real_name=application.real_name,
|
|
phone=application.phone,
|
|
photo_url=application.photo_url,
|
|
bank_name=application.bank_name,
|
|
bank_account=application.bank_account,
|
|
account_holder=application.account_holder,
|
|
)
|
|
|
|
# Update application status
|
|
application.status = "approved"
|
|
application.approved_at = datetime.utcnow()
|
|
|
|
# Update user is_dealer flag
|
|
user = db.query(User).filter(User.id == application.user_id).first()
|
|
user.is_dealer = True
|
|
|
|
db.add(dealer_info)
|
|
db.commit()
|
|
db.refresh(dealer_info)
|
|
|
|
# TODO: Generate dealer card image here
|
|
# dealer_info.dealer_card_url = generate_dealer_card(dealer_info)
|
|
# db.commit()
|
|
|
|
# Send notification to user about dealer approval
|
|
notify_dealer_approved(db, application.user_id, dealer_code)
|
|
|
|
return dealer_info
|
|
|
|
|
|
@router.put("/admin/applications/{application_id}/reject")
|
|
def reject_application(
|
|
application_id: int,
|
|
reject_data: DealerApplicationReject,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""[Admin] Reject a dealer application"""
|
|
if not current_user.is_admin:
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
|
|
application = db.query(DealerApplication).filter(
|
|
DealerApplication.id == application_id
|
|
).first()
|
|
|
|
if not application:
|
|
raise HTTPException(status_code=404, detail="Application not found")
|
|
|
|
if application.status != "pending":
|
|
raise HTTPException(status_code=400, detail="Application is not pending")
|
|
|
|
application.status = "rejected"
|
|
application.rejected_reason = reject_data.reason
|
|
|
|
db.commit()
|
|
|
|
# Send notification to user about dealer rejection
|
|
notify_dealer_rejected(db, application.user_id, reject_data.reason)
|
|
|
|
return {"message": "Application rejected", "reason": reject_data.reason}
|
|
|
|
|
|
@router.get("/admin/dealers", response_model=List[DealerInfoResponse])
|
|
def get_all_dealers(
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""[Admin] Get all dealers with full info"""
|
|
if not current_user.is_admin:
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
|
|
dealers = db.query(DealerInfo).all()
|
|
return dealers
|
|
|
|
|
|
@router.put("/admin/dealers/{dealer_id}/toggle-active")
|
|
def toggle_dealer_active(
|
|
dealer_id: int,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""[Admin] Toggle dealer active status"""
|
|
if not current_user.is_admin:
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
|
|
dealer = db.query(DealerInfo).filter(DealerInfo.id == dealer_id).first()
|
|
if not dealer:
|
|
raise HTTPException(status_code=404, detail="Dealer not found")
|
|
|
|
dealer.is_active = not dealer.is_active
|
|
|
|
# Also update user's is_dealer status
|
|
user = db.query(User).filter(User.id == dealer.user_id).first()
|
|
if user:
|
|
user.is_dealer = dealer.is_active
|
|
|
|
db.commit()
|
|
|
|
return {"message": f"Dealer {'activated' if dealer.is_active else 'deactivated'}", "is_active": dealer.is_active}
|