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:
AutonetSellCar Deploy
2025-12-30 13:24:39 +09:00
commit 1f0dcb1ddb
224 changed files with 55119 additions and 0 deletions

View 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
}