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,326 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy import desc
from datetime import datetime
from typing import List, Optional
from ..database import get_db
from ..models import User
from ..models.inquiry import Inquiry, InquiryMessage, InquiryStatus
from ..schemas.inquiry import (
InquiryCreate, InquiryResponse, InquiryListResponse,
InquiryMessageCreate, InquiryMessageResponse, InquiryWithMessages,
AdminInquiryRespond, AdminInquiryUpdateStatus
)
from .auth import get_current_user
from .notification import create_notification
router = APIRouter(prefix="/inquiries", tags=["inquiries"])
# =====================
# User Endpoints
# =====================
@router.get("", response_model=List[InquiryResponse])
def get_inquiries(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get current user's inquiries (legacy endpoint)"""
return db.query(Inquiry).filter(Inquiry.user_id == current_user.id).order_by(desc(Inquiry.created_at)).all()
@router.post("", response_model=InquiryResponse)
def create_inquiry(
inquiry_data: InquiryCreate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Create a new inquiry"""
inquiry = Inquiry(
user_id=current_user.id,
car_id=inquiry_data.car_id,
category=inquiry_data.category,
subject=inquiry_data.subject or f"{inquiry_data.category} 문의",
message=inquiry_data.message,
contact_email=inquiry_data.contact_email or current_user.email,
contact_phone=inquiry_data.contact_phone or current_user.phone,
status=InquiryStatus.PENDING
)
db.add(inquiry)
db.commit()
db.refresh(inquiry)
return inquiry
@router.get("/my-inquiries", response_model=InquiryListResponse)
def get_my_inquiries(
page: int = 1,
page_size: int = 10,
status: Optional[str] = None,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get current user's inquiries with pagination"""
query = db.query(Inquiry).filter(Inquiry.user_id == current_user.id)
if status:
query = query.filter(Inquiry.status == status)
total = query.count()
inquiries = query.order_by(desc(Inquiry.created_at)) \
.offset((page - 1) * page_size) \
.limit(page_size) \
.all()
return InquiryListResponse(
inquiries=[InquiryResponse.model_validate(i) for i in inquiries],
total=total
)
@router.get("/my-inquiries/{inquiry_id}", response_model=InquiryWithMessages)
def get_my_inquiry_detail(
inquiry_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get details of a specific inquiry with messages"""
inquiry = db.query(Inquiry).filter(
Inquiry.id == inquiry_id,
Inquiry.user_id == current_user.id
).first()
if not inquiry:
raise HTTPException(status_code=404, detail="Inquiry not found")
messages = db.query(InquiryMessage).filter(
InquiryMessage.inquiry_id == inquiry_id
).order_by(InquiryMessage.created_at).all()
return InquiryWithMessages(
inquiry=InquiryResponse.model_validate(inquiry),
messages=[InquiryMessageResponse.model_validate(m) for m in messages]
)
@router.post("/my-inquiries/{inquiry_id}/message", response_model=InquiryMessageResponse)
def add_message_to_inquiry(
inquiry_id: int,
message_data: InquiryMessageCreate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Add a message to an existing inquiry"""
inquiry = db.query(Inquiry).filter(
Inquiry.id == inquiry_id,
Inquiry.user_id == current_user.id
).first()
if not inquiry:
raise HTTPException(status_code=404, detail="Inquiry not found")
if inquiry.status == InquiryStatus.CLOSED:
raise HTTPException(status_code=400, detail="Cannot add message to closed inquiry")
message = InquiryMessage(
inquiry_id=inquiry_id,
user_id=current_user.id,
message=message_data.message,
is_admin=False
)
# Update inquiry status if it was resolved
if inquiry.status == InquiryStatus.RESOLVED:
inquiry.status = InquiryStatus.IN_PROGRESS
db.add(message)
db.commit()
db.refresh(message)
return message
@router.get("/{inquiry_id}", response_model=InquiryResponse)
def get_inquiry(
inquiry_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get inquiry detail (legacy endpoint)"""
inquiry = db.query(Inquiry).filter(
Inquiry.id == inquiry_id,
Inquiry.user_id == current_user.id
).first()
if not inquiry:
raise HTTPException(status_code=404, detail="Inquiry not found")
return inquiry
# =====================
# Admin Endpoints
# =====================
@router.get("/admin/list", response_model=InquiryListResponse)
def admin_get_all_inquiries(
page: int = 1,
page_size: int = 20,
status: Optional[str] = None,
category: Optional[str] = None,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""[Admin] Get all inquiries"""
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
query = db.query(Inquiry)
if status:
query = query.filter(Inquiry.status == status)
if category:
query = query.filter(Inquiry.category == category)
total = query.count()
inquiries = query.order_by(desc(Inquiry.created_at)) \
.offset((page - 1) * page_size) \
.limit(page_size) \
.all()
return InquiryListResponse(
inquiries=[InquiryResponse.model_validate(i) for i in inquiries],
total=total
)
@router.get("/admin/{inquiry_id}", response_model=InquiryWithMessages)
def admin_get_inquiry_detail(
inquiry_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""[Admin] Get inquiry details with messages"""
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
inquiry = db.query(Inquiry).filter(Inquiry.id == inquiry_id).first()
if not inquiry:
raise HTTPException(status_code=404, detail="Inquiry not found")
messages = db.query(InquiryMessage).filter(
InquiryMessage.inquiry_id == inquiry_id
).order_by(InquiryMessage.created_at).all()
return InquiryWithMessages(
inquiry=InquiryResponse.model_validate(inquiry),
messages=[InquiryMessageResponse.model_validate(m) for m in messages]
)
@router.post("/admin/{inquiry_id}/respond", response_model=InquiryMessageResponse)
def admin_respond_to_inquiry(
inquiry_id: int,
response_data: AdminInquiryRespond,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""[Admin] Respond to an inquiry"""
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
inquiry = db.query(Inquiry).filter(Inquiry.id == inquiry_id).first()
if not inquiry:
raise HTTPException(status_code=404, detail="Inquiry not found")
# Create message
message = InquiryMessage(
inquiry_id=inquiry_id,
user_id=current_user.id,
message=response_data.message,
is_admin=True
)
# Update inquiry
inquiry.admin_response = response_data.message
inquiry.responded_at = datetime.utcnow()
inquiry.responded_by = current_user.id
if response_data.status:
inquiry.status = response_data.status
elif inquiry.status == InquiryStatus.PENDING:
inquiry.status = InquiryStatus.IN_PROGRESS
db.add(message)
db.commit()
db.refresh(message)
# Send notification to user
create_notification(
db=db,
user_id=inquiry.user_id,
notification_type="system",
title="문의 답변 도착",
message=f"'{inquiry.subject}' 문의에 답변이 등록되었습니다.",
link="/contact"
)
return message
@router.put("/admin/{inquiry_id}/status", response_model=InquiryResponse)
def admin_update_inquiry_status(
inquiry_id: int,
status_data: AdminInquiryUpdateStatus,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""[Admin] Update inquiry status"""
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
inquiry = db.query(Inquiry).filter(Inquiry.id == inquiry_id).first()
if not inquiry:
raise HTTPException(status_code=404, detail="Inquiry not found")
valid_statuses = [InquiryStatus.PENDING, InquiryStatus.IN_PROGRESS, InquiryStatus.RESOLVED, InquiryStatus.CLOSED]
if status_data.status not in valid_statuses:
raise HTTPException(
status_code=400,
detail=f"Invalid status. Must be one of: {valid_statuses}"
)
inquiry.status = status_data.status
db.commit()
db.refresh(inquiry)
return inquiry
@router.get("/admin/stats")
def admin_get_inquiry_stats(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""[Admin] Get inquiry statistics"""
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
total = db.query(Inquiry).count()
pending = db.query(Inquiry).filter(Inquiry.status == InquiryStatus.PENDING).count()
in_progress = db.query(Inquiry).filter(Inquiry.status == InquiryStatus.IN_PROGRESS).count()
resolved = db.query(Inquiry).filter(Inquiry.status == InquiryStatus.RESOLVED).count()
closed = db.query(Inquiry).filter(Inquiry.status == InquiryStatus.CLOSED).count()
return {
"total": total,
"pending": pending,
"in_progress": in_progress,
"resolved": resolved,
"closed": closed
}