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,363 @@
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, Notification
from ..schemas.notification import (
NotificationCreate, NotificationResponse,
NotificationListResponse, NotificationMarkRead
)
from .auth import get_current_user
router = APIRouter(prefix="/notifications", tags=["notifications"])
# =====================
# Helper Functions
# =====================
def create_notification(
db: Session,
user_id: int,
notification_type: str,
title: str,
message: str,
link: Optional[str] = None,
related_id: Optional[int] = None,
related_type: Optional[str] = None
) -> Notification:
"""Create a new notification"""
notification = Notification(
user_id=user_id,
notification_type=notification_type,
title=title,
message=message,
link=link,
related_id=related_id,
related_type=related_type
)
db.add(notification)
db.commit()
db.refresh(notification)
return notification
def notify_vehicle_recommended(db: Session, user_id: int, request_id: int, vehicle_count: int):
"""Notify user when vehicles are recommended for their request"""
return create_notification(
db=db,
user_id=user_id,
notification_type="vehicle_recommended",
title="차량 추천 완료",
message=f"{vehicle_count}대의 차량이 추천되었습니다. 지금 확인해보세요!",
link=f"/my-request",
related_id=request_id,
related_type="vehicle_request"
)
def notify_shipping_update(db: Session, user_id: int, vehicle_id: int, status: int, car_name: str):
"""Notify user when shipping status changes"""
status_names = {
1: "구매완료",
2: "인천항 도착",
3: "텐진항 도착",
4: "자먼우드 도착",
5: "울란바토르 도착",
6: "통관 진행중",
7: "배송완료"
}
status_name = status_names.get(status, f"상태 {status}")
return create_notification(
db=db,
user_id=user_id,
notification_type="shipping_update",
title="배송 상태 업데이트",
message=f"{car_name}: {status_name}",
link=f"/find-my-car",
related_id=vehicle_id,
related_type="purchased_vehicle"
)
def notify_withdrawal_processed(db: Session, user_id: int, withdrawal_id: int, status: str, amount: float):
"""Notify user when withdrawal request is processed"""
status_messages = {
"approved": f"출금 신청이 승인되었습니다. {amount:,.0f}원이 곧 입금됩니다.",
"completed": f"출금 완료! {amount:,.0f}원이 입금되었습니다.",
"rejected": "출금 신청이 거부되었습니다. 관리자에게 문의해주세요."
}
return create_notification(
db=db,
user_id=user_id,
notification_type="withdrawal_processed",
title="출금 처리 알림",
message=status_messages.get(status, "출금 상태가 변경되었습니다."),
link="/withdrawal",
related_id=withdrawal_id,
related_type="withdrawal"
)
def notify_referral_reward(db: Session, user_id: int, reward_amount: float, referred_name: str):
"""Notify user when they receive referral reward"""
return create_notification(
db=db,
user_id=user_id,
notification_type="referral_reward",
title="레퍼럴 보상 적립",
message=f"{referred_name}님의 충전으로 {reward_amount:,.0f}원이 적립되었습니다!",
link="/withdrawal",
related_type="referral"
)
def notify_dealer_approved(db: Session, user_id: int, dealer_code: str):
"""Notify user when dealer application is approved"""
return create_notification(
db=db,
user_id=user_id,
notification_type="dealer_approved",
title="딜러 승인 완료",
message=f"딜러 승인이 완료되었습니다! 딜러 코드: {dealer_code}",
link="/dealer/my-card",
related_type="dealer"
)
def notify_dealer_rejected(db: Session, user_id: int, reason: str):
"""Notify user when dealer application is rejected"""
return create_notification(
db=db,
user_id=user_id,
notification_type="dealer_rejected",
title="딜러 신청 거부",
message=f"딜러 신청이 거부되었습니다. 사유: {reason}",
link="/dealer/apply",
related_type="dealer"
)
def notify_share_purchased(db: Session, user_id: int, share_id: int, reward_amount: float, car_name: str):
"""Notify user when their shared vehicle is purchased"""
return create_notification(
db=db,
user_id=user_id,
notification_type="share_purchased",
title="공유 차량 판매 완료",
message=f"{car_name} 판매 완료! 리워드 {reward_amount:,.0f}원이 적립되었습니다.",
link="/withdrawal",
related_id=share_id,
related_type="vehicle_share"
)
def notify_payment_confirmed(db: Session, user_id: int, charge_id: int, amount: float, cc_amount: int):
"""Notify user when payment is confirmed"""
return create_notification(
db=db,
user_id=user_id,
notification_type="payment_confirmed",
title="결제 확인 완료",
message=f"결제가 확인되었습니다! ${amount:.2f}{cc_amount} CC가 충전되었습니다.",
link="/charge",
related_id=charge_id,
related_type="charge"
)
def notify_inquiry_reply(db: Session, user_id: int, inquiry_id: int, subject: str = None):
"""Notify user when admin replies to their inquiry"""
return create_notification(
db=db,
user_id=user_id,
notification_type="inquiry_reply",
title="문의 답변 등록",
message=f"문의에 답변이 등록되었습니다." + (f" ({subject})" if subject else ""),
link=f"/my-inquiries/{inquiry_id}",
related_id=inquiry_id,
related_type="inquiry"
)
def notify_system(db: Session, user_id: int, title: str, message: str, link: Optional[str] = None):
"""Send a general system notification to a user"""
return create_notification(
db=db,
user_id=user_id,
notification_type="system",
title=title,
message=message,
link=link
)
# =====================
# User Endpoints
# =====================
@router.get("/", response_model=NotificationListResponse)
def get_notifications(
page: int = 1,
page_size: int = 20,
unread_only: bool = False,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get user's notifications"""
query = db.query(Notification).filter(Notification.user_id == current_user.id)
if unread_only:
query = query.filter(Notification.is_read == False)
total = query.count()
unread_count = db.query(Notification).filter(
Notification.user_id == current_user.id,
Notification.is_read == False
).count()
notifications = query.order_by(desc(Notification.created_at)) \
.offset((page - 1) * page_size) \
.limit(page_size) \
.all()
return NotificationListResponse(
notifications=[NotificationResponse.model_validate(n) for n in notifications],
unread_count=unread_count,
total=total
)
@router.get("/unread-count")
def get_unread_count(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get count of unread notifications"""
count = db.query(Notification).filter(
Notification.user_id == current_user.id,
Notification.is_read == False
).count()
return {"unread_count": count}
@router.post("/mark-read")
def mark_as_read(
data: NotificationMarkRead,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Mark notifications as read"""
notifications = db.query(Notification).filter(
Notification.id.in_(data.notification_ids),
Notification.user_id == current_user.id
).all()
for notification in notifications:
notification.is_read = True
notification.read_at = datetime.utcnow()
db.commit()
return {"message": f"Marked {len(notifications)} notifications as read"}
@router.post("/mark-all-read")
def mark_all_as_read(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Mark all notifications as read"""
count = db.query(Notification).filter(
Notification.user_id == current_user.id,
Notification.is_read == False
).update({
"is_read": True,
"read_at": datetime.utcnow()
})
db.commit()
return {"message": f"Marked {count} notifications as read"}
@router.delete("/{notification_id}")
def delete_notification(
notification_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Delete a notification"""
notification = db.query(Notification).filter(
Notification.id == notification_id,
Notification.user_id == current_user.id
).first()
if not notification:
raise HTTPException(status_code=404, detail="Notification not found")
db.delete(notification)
db.commit()
return {"message": "Notification deleted"}
# =====================
# Admin Endpoints
# =====================
@router.post("/admin/send")
def admin_send_notification(
notification_data: NotificationCreate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""[Admin] Send notification to a user"""
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
notification = create_notification(
db=db,
user_id=notification_data.user_id,
notification_type=notification_data.notification_type,
title=notification_data.title,
message=notification_data.message,
link=notification_data.link,
related_id=notification_data.related_id,
related_type=notification_data.related_type
)
return NotificationResponse.model_validate(notification)
@router.post("/admin/send-all")
def admin_send_to_all(
title: str,
message: str,
link: Optional[str] = None,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""[Admin] Send notification to all users"""
if not current_user.is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
users = db.query(User).filter(User.is_active == True).all()
for user in users:
create_notification(
db=db,
user_id=user.id,
notification_type="system",
title=title,
message=message,
link=link
)
return {"message": f"Sent notification to {len(users)} users"}