- 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>
364 lines
11 KiB
Python
364 lines
11 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, 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"}
|