from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import Optional from pydantic import BaseModel from datetime import datetime from ..database import get_db from ..models import User, PushSubscription, UserNotificationPreference from .auth import get_current_user, get_current_admin_user router = APIRouter(prefix="/push", tags=["Push Notifications"]) # VAPID keys for web push (in production, store these securely) # Generate these using: npx web-push generate-vapid-keys VAPID_PUBLIC_KEY = "BMjR7pDj6PUjFo8VkA4f1BYhOAzGhJPcVnT7mJ6Bq8jG9yYKvN8dZ5jT3pQ2sL9wR0xF4bM1nK3vH5uC7yX2aE0" class PushSubscriptionCreate(BaseModel): endpoint: str p256dh_key: str auth_key: str device_info: Optional[str] = None class NotificationPreferenceUpdate(BaseModel): vehicle_recommended: Optional[bool] = None shipping_update: Optional[bool] = None payment_confirmed: Optional[bool] = None withdrawal_processed: Optional[bool] = None dealer_status: Optional[bool] = None share_purchased: Optional[bool] = None referral_reward: Optional[bool] = None inquiry_reply: Optional[bool] = None system_announcements: Optional[bool] = None push_enabled: Optional[bool] = None email_enabled: Optional[bool] = None @router.get("/vapid-key") def get_vapid_public_key(): """Get VAPID public key for push subscription""" return {"public_key": VAPID_PUBLIC_KEY} @router.post("/subscribe") def subscribe_push( subscription: PushSubscriptionCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Subscribe to push notifications""" # Check if subscription already exists existing = db.query(PushSubscription).filter( PushSubscription.user_id == current_user.id, PushSubscription.endpoint == subscription.endpoint ).first() if existing: # Update existing subscription existing.p256dh_key = subscription.p256dh_key existing.auth_key = subscription.auth_key existing.device_info = subscription.device_info existing.is_active = True existing.last_used_at = datetime.utcnow() else: # Create new subscription new_sub = PushSubscription( user_id=current_user.id, endpoint=subscription.endpoint, p256dh_key=subscription.p256dh_key, auth_key=subscription.auth_key, device_info=subscription.device_info, is_active=True ) db.add(new_sub) db.commit() return {"message": "Push subscription saved successfully"} @router.delete("/unsubscribe") def unsubscribe_push( endpoint: str, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Unsubscribe from push notifications""" subscription = db.query(PushSubscription).filter( PushSubscription.user_id == current_user.id, PushSubscription.endpoint == endpoint ).first() if subscription: subscription.is_active = False db.commit() return {"message": "Push subscription removed"} @router.get("/subscriptions") def get_my_subscriptions( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get user's active push subscriptions""" subscriptions = db.query(PushSubscription).filter( PushSubscription.user_id == current_user.id, PushSubscription.is_active == True ).all() return [ { "id": sub.id, "endpoint": sub.endpoint[:50] + "..." if len(sub.endpoint) > 50 else sub.endpoint, "device_info": sub.device_info, "created_at": sub.created_at.isoformat() if sub.created_at else None, "last_used_at": sub.last_used_at.isoformat() if sub.last_used_at else None } for sub in subscriptions ] @router.get("/preferences") def get_notification_preferences( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get user's notification preferences""" prefs = db.query(UserNotificationPreference).filter( UserNotificationPreference.user_id == current_user.id ).first() if not prefs: # Create default preferences prefs = UserNotificationPreference(user_id=current_user.id) db.add(prefs) db.commit() db.refresh(prefs) return { "vehicle_recommended": prefs.vehicle_recommended, "shipping_update": prefs.shipping_update, "payment_confirmed": prefs.payment_confirmed, "withdrawal_processed": prefs.withdrawal_processed, "dealer_status": prefs.dealer_status, "share_purchased": prefs.share_purchased, "referral_reward": prefs.referral_reward, "inquiry_reply": prefs.inquiry_reply, "system_announcements": prefs.system_announcements, "push_enabled": prefs.push_enabled, "email_enabled": prefs.email_enabled, } @router.put("/preferences") def update_notification_preferences( preferences: NotificationPreferenceUpdate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Update user's notification preferences""" prefs = db.query(UserNotificationPreference).filter( UserNotificationPreference.user_id == current_user.id ).first() if not prefs: prefs = UserNotificationPreference(user_id=current_user.id) db.add(prefs) # Update preferences for field, value in preferences.dict(exclude_none=True).items(): setattr(prefs, field, value) db.commit() db.refresh(prefs) return {"message": "Preferences updated successfully"} # Admin endpoints @router.get("/admin/stats") def admin_get_push_stats( db: Session = Depends(get_db), current_user: User = Depends(get_current_admin_user) ): """Get push notification statistics (Admin only)""" total_subscriptions = db.query(PushSubscription).filter( PushSubscription.is_active == True ).count() users_with_push = db.query(PushSubscription.user_id).filter( PushSubscription.is_active == True ).distinct().count() return { "total_subscriptions": total_subscriptions, "users_with_push": users_with_push } # Helper function to send push notification (called from other modules) def send_push_notification( db: Session, user_id: int, title: str, body: str, url: str = None, notification_type: str = "system" ): """ Send push notification to a user. In production, this would use pywebpush to actually send the notification. """ # Check user preferences prefs = db.query(UserNotificationPreference).filter( UserNotificationPreference.user_id == user_id ).first() if prefs and not prefs.push_enabled: return False # Check specific notification type preference if prefs: type_pref_map = { "vehicle_recommended": prefs.vehicle_recommended, "shipping_update": prefs.shipping_update, "payment_confirmed": prefs.payment_confirmed, "withdrawal_processed": prefs.withdrawal_processed, "dealer_approved": prefs.dealer_status, "dealer_rejected": prefs.dealer_status, "share_purchased": prefs.share_purchased, "referral_reward": prefs.referral_reward, "inquiry_reply": prefs.inquiry_reply, "system": prefs.system_announcements, } if notification_type in type_pref_map and not type_pref_map[notification_type]: return False # Get user's active subscriptions subscriptions = db.query(PushSubscription).filter( PushSubscription.user_id == user_id, PushSubscription.is_active == True ).all() if not subscriptions: return False # In production, use pywebpush to send notifications # For now, we just log and return success # Example with pywebpush: # from pywebpush import webpush, WebPushException # for sub in subscriptions: # try: # webpush( # subscription_info={ # "endpoint": sub.endpoint, # "keys": { # "p256dh": sub.p256dh_key, # "auth": sub.auth_key # } # }, # data=json.dumps({ # "title": title, # "body": body, # "url": url # }), # vapid_private_key=VAPID_PRIVATE_KEY, # vapid_claims={"sub": "mailto:admin@autosellcar.com"} # ) # sub.last_used_at = datetime.utcnow() # except WebPushException as ex: # if ex.response and ex.response.status_code == 410: # sub.is_active = False # db.commit() return True