Files
AutonetSellCar/backend/app/main.py
AutonetSellCar Deploy 1f0dcb1ddb 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>
2025-12-30 13:24:39 +09:00

169 lines
5.6 KiB
Python

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager
import os
import asyncio
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from .database import engine, Base, SessionLocal
from .api import cars, auth, inquiries, hero_banners, carmodoo, translations, cc, settings, vehicle_requests, dealer, vehicle_share, withdrawal, referral, notification, dashboard, push, exchange_rate, verification, visitor
from .config import get_settings
from .services.exchange_rate_service import update_exchange_rates
from .services.visitor_service import aggregate_daily_stats, cleanup_old_visitor_logs
from datetime import datetime, timedelta
app_settings = get_settings()
# Create tables
Base.metadata.create_all(bind=engine)
# APScheduler 설정
scheduler = AsyncIOScheduler()
async def scheduled_update_exchange_rates():
"""스케줄된 환율 업데이트 작업"""
print("[Scheduler] Starting daily exchange rate update...")
db = SessionLocal()
try:
result = await update_exchange_rates(db, force=True)
print(f"[Scheduler] Exchange rate update completed: {result}")
except Exception as e:
print(f"[Scheduler] Exchange rate update failed: {e}")
finally:
db.close()
async def scheduled_aggregate_visitor_stats():
"""Aggregate yesterday's visitor stats"""
print("[Scheduler] Aggregating visitor stats...")
db = SessionLocal()
try:
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
result = aggregate_daily_stats(db, yesterday)
if result:
print(f"[Scheduler] Visitor stats aggregated for {yesterday}")
else:
print(f"[Scheduler] No visitor data for {yesterday}")
except Exception as e:
print(f"[Scheduler] Visitor stats aggregation failed: {e}")
finally:
db.close()
async def scheduled_cleanup_old_visitor_logs():
"""Delete visitor logs older than 90 days"""
print("[Scheduler] Cleaning up old visitor logs...")
db = SessionLocal()
try:
deleted = cleanup_old_visitor_logs(db, days=90)
print(f"[Scheduler] Deleted {deleted} old visitor logs")
except Exception as e:
print(f"[Scheduler] Visitor log cleanup failed: {e}")
finally:
db.close()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""앱 시작/종료 시 실행되는 lifespan 이벤트"""
# 시작 시
print("[Startup] Initializing scheduler...")
# 환율 업데이트 스케줄 등록 (매일 오전 11시 30분 - 수출입은행 11시경 업데이트)
scheduler.add_job(
scheduled_update_exchange_rates,
CronTrigger(hour=11, minute=30),
id="daily_exchange_rate_update",
name="Daily Exchange Rate Update",
replace_existing=True
)
# 방문자 통계 집계 (매일 새벽 2시)
scheduler.add_job(
scheduled_aggregate_visitor_stats,
CronTrigger(hour=2, minute=0),
id="daily_visitor_stats_aggregation",
name="Daily Visitor Stats Aggregation",
replace_existing=True
)
# 오래된 방문자 로그 정리 (매주 일요일 새벽 3시)
scheduler.add_job(
scheduled_cleanup_old_visitor_logs,
CronTrigger(day_of_week='sun', hour=3, minute=0),
id="weekly_visitor_log_cleanup",
name="Weekly Visitor Log Cleanup",
replace_existing=True
)
scheduler.start()
print("[Startup] Scheduler started - Exchange rates: 11:30 AM, Visitor stats: 2:00 AM, Log cleanup: Sunday 3:00 AM")
# 서버 시작 시 환율 데이터 초기화 (백그라운드에서)
asyncio.create_task(scheduled_update_exchange_rates())
yield
# 종료 시
print("[Shutdown] Stopping scheduler...")
scheduler.shutdown()
app = FastAPI(
title="AutonetSellCar API",
description="AutonetSellCar - Used Car Export Platform API",
version="1.0.0",
lifespan=lifespan
)
# CORS - credentials=True requires explicit origins (not "*")
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:8000",
"http://192.168.0.202:3000", # Local network
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Static files for uploads
os.makedirs("./uploads/hero-banners", exist_ok=True)
app.mount("/uploads", StaticFiles(directory="./uploads"), name="uploads")
# Routes
app.include_router(cars.router, prefix="/api")
app.include_router(auth.router, prefix="/api")
app.include_router(inquiries.router, prefix="/api")
app.include_router(hero_banners.router, prefix="/api")
app.include_router(carmodoo.router, prefix="/api")
app.include_router(translations.router, prefix="/api")
app.include_router(cc.router, prefix="/api")
app.include_router(settings.router, prefix="/api")
app.include_router(vehicle_requests.router, prefix="/api")
app.include_router(dealer.router, prefix="/api")
app.include_router(vehicle_share.router, prefix="/api")
app.include_router(withdrawal.router, prefix="/api")
app.include_router(referral.router, prefix="/api")
app.include_router(notification.router, prefix="/api")
app.include_router(dashboard.router, prefix="/api")
app.include_router(push.router, prefix="/api")
app.include_router(exchange_rate.router)
app.include_router(verification.router, prefix="/api")
app.include_router(visitor.router, prefix="/api")
@app.get("/")
def root():
return {"message": "AutonetSellCar API", "version": "1.0.0"}
@app.get("/health")
def health():
return {"status": "healthy"}