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:
443
backend/app/api/dashboard.py
Normal file
443
backend/app/api/dashboard.py
Normal file
@@ -0,0 +1,443 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, and_, desc
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import (
|
||||
User, Car, Inquiry, InquiryStatus,
|
||||
VehicleRequest, RequestVehicle, PurchasedVehicle,
|
||||
DealerApplication, DealerInfo,
|
||||
VehicleShare, ShareReward,
|
||||
WithdrawalRequest,
|
||||
ReferralReward,
|
||||
HeroBanner,
|
||||
ChargeHistory,
|
||||
)
|
||||
from .auth import get_current_admin_user
|
||||
|
||||
router = APIRouter(prefix="/dashboard", tags=["Dashboard"])
|
||||
|
||||
|
||||
class DashboardStats(BaseModel):
|
||||
total_users: int
|
||||
new_users_today: int
|
||||
new_users_this_week: int
|
||||
total_dealers: int
|
||||
pending_dealer_applications: int
|
||||
total_cars: int
|
||||
total_vehicle_requests: int
|
||||
pending_requests: int
|
||||
total_purchased_vehicles: int
|
||||
total_inquiries: int
|
||||
pending_inquiries: int
|
||||
total_shares: int
|
||||
purchased_shares: int
|
||||
total_withdrawals: int
|
||||
pending_withdrawals: int
|
||||
total_cc_charged: float
|
||||
total_withdrawal_amount: float
|
||||
|
||||
|
||||
class RevenueStats(BaseModel):
|
||||
total_revenue: float
|
||||
revenue_this_month: float
|
||||
revenue_last_month: float
|
||||
platform_commission: float
|
||||
dealer_commission: float
|
||||
|
||||
|
||||
class ChartData(BaseModel):
|
||||
labels: List[str]
|
||||
values: List[int]
|
||||
|
||||
|
||||
class DailyStats(BaseModel):
|
||||
date: str
|
||||
users: int
|
||||
requests: int
|
||||
purchases: int
|
||||
revenue: float
|
||||
|
||||
|
||||
class RecentActivity(BaseModel):
|
||||
type: str
|
||||
title: str
|
||||
description: str
|
||||
time: str
|
||||
icon: str
|
||||
|
||||
|
||||
class TopDealer(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
dealer_code: str
|
||||
total_sales: int
|
||||
total_commission: float
|
||||
|
||||
|
||||
@router.get("/stats", response_model=DashboardStats)
|
||||
def get_dashboard_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get comprehensive dashboard statistics"""
|
||||
today = datetime.utcnow().date()
|
||||
week_ago = today - timedelta(days=7)
|
||||
|
||||
# User stats
|
||||
total_users = db.query(func.count(User.id)).filter(User.is_admin == False).scalar() or 0
|
||||
new_users_today = db.query(func.count(User.id)).filter(
|
||||
and_(
|
||||
User.is_admin == False,
|
||||
func.date(User.created_at) == today
|
||||
)
|
||||
).scalar() or 0
|
||||
new_users_this_week = db.query(func.count(User.id)).filter(
|
||||
and_(
|
||||
User.is_admin == False,
|
||||
func.date(User.created_at) >= week_ago
|
||||
)
|
||||
).scalar() or 0
|
||||
|
||||
# Dealer stats
|
||||
total_dealers = db.query(func.count(DealerInfo.id)).filter(DealerInfo.is_active == True).scalar() or 0
|
||||
pending_dealer_applications = db.query(func.count(DealerApplication.id)).filter(
|
||||
DealerApplication.status == "pending"
|
||||
).scalar() or 0
|
||||
|
||||
# Car stats
|
||||
total_cars = db.query(func.count(Car.id)).scalar() or 0
|
||||
|
||||
# Vehicle request stats
|
||||
total_vehicle_requests = db.query(func.count(VehicleRequest.id)).scalar() or 0
|
||||
pending_requests = db.query(func.count(VehicleRequest.id)).filter(
|
||||
VehicleRequest.status == "pending"
|
||||
).scalar() or 0
|
||||
|
||||
# Purchased vehicles
|
||||
total_purchased_vehicles = db.query(func.count(PurchasedVehicle.id)).scalar() or 0
|
||||
|
||||
# Inquiry stats
|
||||
total_inquiries = db.query(func.count(Inquiry.id)).scalar() or 0
|
||||
pending_inquiries = db.query(func.count(Inquiry.id)).filter(
|
||||
Inquiry.status == InquiryStatus.PENDING
|
||||
).scalar() or 0
|
||||
|
||||
# Share stats
|
||||
total_shares = db.query(func.count(VehicleShare.id)).scalar() or 0
|
||||
purchased_shares = db.query(func.count(VehicleShare.id)).filter(
|
||||
VehicleShare.is_purchased == True
|
||||
).scalar() or 0
|
||||
|
||||
# Withdrawal stats
|
||||
total_withdrawals = db.query(func.count(WithdrawalRequest.id)).scalar() or 0
|
||||
pending_withdrawals = db.query(func.count(WithdrawalRequest.id)).filter(
|
||||
WithdrawalRequest.status == "pending"
|
||||
).scalar() or 0
|
||||
|
||||
# CC stats
|
||||
total_cc_charged = db.query(func.coalesce(func.sum(ChargeHistory.amount), 0)).filter(
|
||||
ChargeHistory.status == "completed"
|
||||
).scalar() or 0
|
||||
|
||||
total_withdrawal_amount = db.query(func.coalesce(func.sum(WithdrawalRequest.amount), 0)).filter(
|
||||
WithdrawalRequest.status == "completed"
|
||||
).scalar() or 0
|
||||
|
||||
return DashboardStats(
|
||||
total_users=total_users,
|
||||
new_users_today=new_users_today,
|
||||
new_users_this_week=new_users_this_week,
|
||||
total_dealers=total_dealers,
|
||||
pending_dealer_applications=pending_dealer_applications,
|
||||
total_cars=total_cars,
|
||||
total_vehicle_requests=total_vehicle_requests,
|
||||
pending_requests=pending_requests,
|
||||
total_purchased_vehicles=total_purchased_vehicles,
|
||||
total_inquiries=total_inquiries,
|
||||
pending_inquiries=pending_inquiries,
|
||||
total_shares=total_shares,
|
||||
purchased_shares=purchased_shares,
|
||||
total_withdrawals=total_withdrawals,
|
||||
pending_withdrawals=pending_withdrawals,
|
||||
total_cc_charged=float(total_cc_charged),
|
||||
total_withdrawal_amount=float(total_withdrawal_amount),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/revenue", response_model=RevenueStats)
|
||||
def get_revenue_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get revenue statistics"""
|
||||
today = datetime.utcnow().date()
|
||||
this_month_start = today.replace(day=1)
|
||||
last_month_end = this_month_start - timedelta(days=1)
|
||||
last_month_start = last_month_end.replace(day=1)
|
||||
|
||||
# Total CC charged as revenue
|
||||
total_revenue = db.query(func.coalesce(func.sum(ChargeHistory.amount), 0)).filter(
|
||||
ChargeHistory.status == "completed"
|
||||
).scalar() or 0
|
||||
|
||||
revenue_this_month = db.query(func.coalesce(func.sum(ChargeHistory.amount), 0)).filter(
|
||||
and_(
|
||||
ChargeHistory.status == "completed",
|
||||
func.date(ChargeHistory.created_at) >= this_month_start
|
||||
)
|
||||
).scalar() or 0
|
||||
|
||||
revenue_last_month = db.query(func.coalesce(func.sum(ChargeHistory.amount), 0)).filter(
|
||||
and_(
|
||||
ChargeHistory.status == "completed",
|
||||
func.date(ChargeHistory.created_at) >= last_month_start,
|
||||
func.date(ChargeHistory.created_at) <= last_month_end
|
||||
)
|
||||
).scalar() or 0
|
||||
|
||||
# Commission stats from purchased vehicles
|
||||
platform_commission = db.query(func.coalesce(func.sum(PurchasedVehicle.platform_commission), 0)).scalar() or 0
|
||||
dealer_commission = db.query(func.coalesce(func.sum(PurchasedVehicle.dealer_commission), 0)).scalar() or 0
|
||||
|
||||
return RevenueStats(
|
||||
total_revenue=float(total_revenue),
|
||||
revenue_this_month=float(revenue_this_month),
|
||||
revenue_last_month=float(revenue_last_month),
|
||||
platform_commission=float(platform_commission),
|
||||
dealer_commission=float(dealer_commission),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/chart/users", response_model=ChartData)
|
||||
def get_user_chart_data(
|
||||
days: int = 30,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get user registration chart data for last N days"""
|
||||
today = datetime.utcnow().date()
|
||||
|
||||
labels = []
|
||||
values = []
|
||||
|
||||
for i in range(days - 1, -1, -1):
|
||||
date = today - timedelta(days=i)
|
||||
count = db.query(func.count(User.id)).filter(
|
||||
and_(
|
||||
User.is_admin == False,
|
||||
func.date(User.created_at) == date
|
||||
)
|
||||
).scalar() or 0
|
||||
|
||||
labels.append(date.strftime("%m/%d"))
|
||||
values.append(count)
|
||||
|
||||
return ChartData(labels=labels, values=values)
|
||||
|
||||
|
||||
@router.get("/chart/requests", response_model=ChartData)
|
||||
def get_request_chart_data(
|
||||
days: int = 30,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get vehicle request chart data for last N days"""
|
||||
today = datetime.utcnow().date()
|
||||
|
||||
labels = []
|
||||
values = []
|
||||
|
||||
for i in range(days - 1, -1, -1):
|
||||
date = today - timedelta(days=i)
|
||||
count = db.query(func.count(VehicleRequest.id)).filter(
|
||||
func.date(VehicleRequest.created_at) == date
|
||||
).scalar() or 0
|
||||
|
||||
labels.append(date.strftime("%m/%d"))
|
||||
values.append(count)
|
||||
|
||||
return ChartData(labels=labels, values=values)
|
||||
|
||||
|
||||
@router.get("/chart/revenue", response_model=ChartData)
|
||||
def get_revenue_chart_data(
|
||||
days: int = 30,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get daily revenue chart data for last N days"""
|
||||
today = datetime.utcnow().date()
|
||||
|
||||
labels = []
|
||||
values = []
|
||||
|
||||
for i in range(days - 1, -1, -1):
|
||||
date = today - timedelta(days=i)
|
||||
amount = db.query(func.coalesce(func.sum(ChargeHistory.amount), 0)).filter(
|
||||
and_(
|
||||
ChargeHistory.status == "completed",
|
||||
func.date(ChargeHistory.created_at) == date
|
||||
)
|
||||
).scalar() or 0
|
||||
|
||||
labels.append(date.strftime("%m/%d"))
|
||||
values.append(int(amount))
|
||||
|
||||
return ChartData(labels=labels, values=values)
|
||||
|
||||
|
||||
@router.get("/recent-activities", response_model=List[RecentActivity])
|
||||
def get_recent_activities(
|
||||
limit: int = 10,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get recent activities across the platform"""
|
||||
activities = []
|
||||
|
||||
# Recent user registrations
|
||||
recent_users = db.query(User).filter(User.is_admin == False).order_by(
|
||||
desc(User.created_at)
|
||||
).limit(3).all()
|
||||
|
||||
for user in recent_users:
|
||||
activities.append({
|
||||
"type": "user",
|
||||
"title": "New User Registration",
|
||||
"description": f"{user.name or user.email} joined the platform",
|
||||
"time": user.created_at.isoformat() if user.created_at else "",
|
||||
"icon": "user"
|
||||
})
|
||||
|
||||
# Recent vehicle requests
|
||||
recent_requests = db.query(VehicleRequest).order_by(
|
||||
desc(VehicleRequest.created_at)
|
||||
).limit(3).all()
|
||||
|
||||
for req in recent_requests:
|
||||
activities.append({
|
||||
"type": "request",
|
||||
"title": "Vehicle Request",
|
||||
"description": f"Request #{req.id} - {req.status}",
|
||||
"time": req.created_at.isoformat() if req.created_at else "",
|
||||
"icon": "car"
|
||||
})
|
||||
|
||||
# Recent inquiries
|
||||
recent_inquiries = db.query(Inquiry).order_by(
|
||||
desc(Inquiry.created_at)
|
||||
).limit(3).all()
|
||||
|
||||
for inq in recent_inquiries:
|
||||
activities.append({
|
||||
"type": "inquiry",
|
||||
"title": "New Inquiry",
|
||||
"description": f"{inq.subject or 'General inquiry'} - {inq.status}",
|
||||
"time": inq.created_at.isoformat() if inq.created_at else "",
|
||||
"icon": "message"
|
||||
})
|
||||
|
||||
# Recent dealer applications
|
||||
recent_applications = db.query(DealerApplication).filter(
|
||||
DealerApplication.status == "pending"
|
||||
).order_by(desc(DealerApplication.applied_at)).limit(2).all()
|
||||
|
||||
for app in recent_applications:
|
||||
activities.append({
|
||||
"type": "dealer",
|
||||
"title": "Dealer Application",
|
||||
"description": f"{app.real_name} ({app.business_name}) applied",
|
||||
"time": app.applied_at.isoformat() if app.applied_at else "",
|
||||
"icon": "badge"
|
||||
})
|
||||
|
||||
# Recent withdrawals
|
||||
recent_withdrawals = db.query(WithdrawalRequest).filter(
|
||||
WithdrawalRequest.status == "pending"
|
||||
).order_by(desc(WithdrawalRequest.requested_at)).limit(2).all()
|
||||
|
||||
for wd in recent_withdrawals:
|
||||
activities.append({
|
||||
"type": "withdrawal",
|
||||
"title": "Withdrawal Request",
|
||||
"description": f"₩{wd.amount:,.0f} withdrawal requested",
|
||||
"time": wd.requested_at.isoformat() if wd.requested_at else "",
|
||||
"icon": "wallet"
|
||||
})
|
||||
|
||||
# Sort by time
|
||||
activities.sort(key=lambda x: x["time"], reverse=True)
|
||||
|
||||
return [RecentActivity(**a) for a in activities[:limit]]
|
||||
|
||||
|
||||
@router.get("/top-dealers", response_model=List[TopDealer])
|
||||
def get_top_dealers(
|
||||
limit: int = 5,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get top performing dealers"""
|
||||
# Get dealers with their stats
|
||||
dealers = db.query(
|
||||
DealerInfo,
|
||||
User.name,
|
||||
func.count(PurchasedVehicle.id).label("sales_count"),
|
||||
func.coalesce(func.sum(PurchasedVehicle.dealer_commission), 0).label("total_commission")
|
||||
).join(
|
||||
User, DealerInfo.user_id == User.id
|
||||
).outerjoin(
|
||||
PurchasedVehicle, DealerInfo.user_id == PurchasedVehicle.selected_dealer_id
|
||||
).filter(
|
||||
DealerInfo.is_active == True
|
||||
).group_by(
|
||||
DealerInfo.id, User.name
|
||||
).order_by(
|
||||
desc("sales_count")
|
||||
).limit(limit).all()
|
||||
|
||||
return [
|
||||
TopDealer(
|
||||
id=dealer.DealerInfo.id,
|
||||
name=dealer.name or "Unknown",
|
||||
dealer_code=dealer.DealerInfo.dealer_code,
|
||||
total_sales=dealer.sales_count,
|
||||
total_commission=float(dealer.total_commission)
|
||||
)
|
||||
for dealer in dealers
|
||||
]
|
||||
|
||||
|
||||
@router.get("/pending-actions")
|
||||
def get_pending_actions(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_user)
|
||||
):
|
||||
"""Get counts of pending items requiring admin action"""
|
||||
pending_requests = db.query(func.count(VehicleRequest.id)).filter(
|
||||
VehicleRequest.status == "pending"
|
||||
).scalar() or 0
|
||||
|
||||
pending_inquiries = db.query(func.count(Inquiry.id)).filter(
|
||||
Inquiry.status == InquiryStatus.PENDING
|
||||
).scalar() or 0
|
||||
|
||||
pending_dealer_apps = db.query(func.count(DealerApplication.id)).filter(
|
||||
DealerApplication.status == "pending"
|
||||
).scalar() or 0
|
||||
|
||||
pending_withdrawals = db.query(func.count(WithdrawalRequest.id)).filter(
|
||||
WithdrawalRequest.status == "pending"
|
||||
).scalar() or 0
|
||||
|
||||
return {
|
||||
"pending_requests": pending_requests,
|
||||
"pending_inquiries": pending_inquiries,
|
||||
"pending_dealer_applications": pending_dealer_apps,
|
||||
"pending_withdrawals": pending_withdrawals,
|
||||
"total_pending": pending_requests + pending_inquiries + pending_dealer_apps + pending_withdrawals
|
||||
}
|
||||
Reference in New Issue
Block a user