feat: Add banner toggle and soldout tracking to Cars page
- Add is_banner, soldout fields to Car model
- Add banner toggle API (POST /hero-banners/admin/toggle/{car_id})
- Add soldout APIs (POST/DELETE /cars/{car_id}/soldout)
- Add nightly soldout checker in agent (runs at 3:00 AM)
- Update Local Cars UI with banner checkbox and status column
- Remove hero-banners admin page (functionality moved to Cars page)
- Banner cars sorted to top with purple background
- Soldout cars displayed with gray overlay
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import aiofiles
|
||||
|
||||
from ..database import get_db
|
||||
from ..models.hero_banner import HeroBanner, HeroBannerSettings
|
||||
from ..models.car import Car
|
||||
from ..schemas.hero_banner import (
|
||||
HeroBannerCreate, HeroBannerUpdate, HeroBannerResponse,
|
||||
HeroBannerListResponse, HeroBannerLocalizedResponse,
|
||||
@@ -264,3 +265,97 @@ async def upload_banner_image(
|
||||
"image_url": image_url,
|
||||
"filename": filename,
|
||||
}
|
||||
|
||||
|
||||
# ==================== Banner Toggle & Ordering ====================
|
||||
|
||||
@router.post("/admin/toggle/{car_id}")
|
||||
def toggle_banner(
|
||||
car_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user)
|
||||
):
|
||||
"""차량의 배너 상태 토글 (Admin)
|
||||
|
||||
- is_banner=False → True: HeroBanner 생성
|
||||
- is_banner=True → False: HeroBanner 삭제
|
||||
"""
|
||||
car = db.query(Car).filter(Car.id == car_id).first()
|
||||
if not car:
|
||||
raise HTTPException(status_code=404, detail="Car not found")
|
||||
|
||||
if car.is_banner:
|
||||
# 배너에서 제거
|
||||
banner = db.query(HeroBanner).filter(HeroBanner.car_id == car_id).first()
|
||||
if banner:
|
||||
db.delete(banner)
|
||||
car.is_banner = False
|
||||
db.commit()
|
||||
return {"car_id": car_id, "is_banner": False, "message": "Removed from banner"}
|
||||
else:
|
||||
# 배너에 추가
|
||||
# 현재 최대 display_order 찾기
|
||||
max_order = db.query(HeroBanner).count()
|
||||
|
||||
# 차량 이미지 URL
|
||||
image_url = f"/uploads/cars/{car_id}/image_0.jpg"
|
||||
|
||||
# 배너 생성
|
||||
banner = HeroBanner(
|
||||
title_ko=car.car_name or "",
|
||||
title_en=car.car_name or "", # 프론트엔드에서 번역
|
||||
title_mn=car.car_name or "",
|
||||
title_ru=car.car_name or "",
|
||||
subtitle_ko=f"{car.year or ''}년식 | {car.mileage:,}km" if car.mileage else f"{car.year or ''}년식",
|
||||
subtitle_en=f"{car.year or ''} | {car.mileage:,}km" if car.mileage else f"{car.year or ''}",
|
||||
subtitle_mn=f"{car.year or ''} | {car.mileage:,}km" if car.mileage else f"{car.year or ''}",
|
||||
subtitle_ru=f"{car.year or ''} | {car.mileage:,}km" if car.mileage else f"{car.year or ''}",
|
||||
image_url=image_url,
|
||||
link_url=f"/cars/{car_id}",
|
||||
car_id=car_id,
|
||||
display_order=max_order,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(banner)
|
||||
car.is_banner = True
|
||||
db.commit()
|
||||
db.refresh(banner)
|
||||
return {"car_id": car_id, "is_banner": True, "banner_id": banner.id, "message": "Added to banner"}
|
||||
|
||||
|
||||
@router.put("/admin/reorder")
|
||||
def reorder_banners(
|
||||
car_ids: List[int],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user)
|
||||
):
|
||||
"""배너 순서 재정렬 (Admin)
|
||||
|
||||
car_ids: 배너 차량 ID 목록 (원하는 순서대로)
|
||||
"""
|
||||
for order, car_id in enumerate(car_ids):
|
||||
banner = db.query(HeroBanner).filter(HeroBanner.car_id == car_id).first()
|
||||
if banner:
|
||||
banner.display_order = order
|
||||
|
||||
db.commit()
|
||||
return {"message": "Banner order updated", "count": len(car_ids)}
|
||||
|
||||
|
||||
@router.get("/admin/banner-cars")
|
||||
def get_banner_cars(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user)
|
||||
):
|
||||
"""배너 등록된 차량 목록 조회 (Admin)
|
||||
|
||||
display_order 순으로 정렬된 차량 ID 목록 반환
|
||||
"""
|
||||
banners = db.query(HeroBanner).filter(
|
||||
HeroBanner.car_id.isnot(None)
|
||||
).order_by(HeroBanner.display_order.asc()).all()
|
||||
|
||||
return {
|
||||
"car_ids": [b.car_id for b in banners],
|
||||
"count": len(banners)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user