Files
AutonetSellCar/backend/app/api/push.py
AutonetSellCar Deploy 1f0dcb1ddb 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>
2025-12-30 13:24:39 +09:00

277 lines
8.8 KiB
Python

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