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:
326
backend/app/api/inquiries.py
Normal file
326
backend/app/api/inquiries.py
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user