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"}