- 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>
327 lines
9.9 KiB
Python
327 lines
9.9 KiB
Python
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
|
|
}
|