Improve user delete flow
This commit is contained in:
@@ -5,10 +5,14 @@
|
||||
Complete notification API routes with full CRUD operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, Path, BackgroundTasks
|
||||
from typing import List, Optional, Dict, Any
|
||||
import structlog
|
||||
from datetime import datetime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
import uuid
|
||||
from sqlalchemy import select, delete, func
|
||||
|
||||
from app.schemas.notifications import (
|
||||
NotificationCreate,
|
||||
@@ -39,6 +43,8 @@ from shared.auth.decorators import (
|
||||
require_role
|
||||
)
|
||||
|
||||
from app.core.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@@ -515,4 +521,355 @@ async def test_send_whatsapp(
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to send test WhatsApp", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/users/{user_id}/notifications/cancel-pending")
|
||||
async def cancel_pending_user_notifications(
|
||||
user_id: str,
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_role(["admin"])),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Cancel all pending notifications for a user (admin only)"""
|
||||
try:
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid user ID format"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.notifications import NotificationQueue, NotificationLog
|
||||
|
||||
# Find pending notifications
|
||||
pending_notifications_query = select(NotificationQueue).where(
|
||||
NotificationQueue.user_id == user_uuid,
|
||||
NotificationQueue.status.in_(["pending", "queued", "scheduled"])
|
||||
)
|
||||
pending_notifications_result = await db.execute(pending_notifications_query)
|
||||
pending_notifications = pending_notifications_result.scalars().all()
|
||||
|
||||
notifications_cancelled = 0
|
||||
cancelled_notification_ids = []
|
||||
errors = []
|
||||
|
||||
for notification in pending_notifications:
|
||||
try:
|
||||
notification.status = "cancelled"
|
||||
notification.updated_at = datetime.utcnow()
|
||||
notification.cancelled_by = current_user.get("user_id")
|
||||
notifications_cancelled += 1
|
||||
cancelled_notification_ids.append(str(notification.id))
|
||||
|
||||
logger.info("Cancelled pending notification",
|
||||
notification_id=str(notification.id),
|
||||
user_id=user_id)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to cancel notification {notification.id}: {str(e)}"
|
||||
errors.append(error_msg)
|
||||
logger.error(error_msg)
|
||||
|
||||
if notifications_cancelled > 0:
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"user_id": user_id,
|
||||
"notifications_cancelled": notifications_cancelled,
|
||||
"cancelled_notification_ids": cancelled_notification_ids,
|
||||
"errors": errors,
|
||||
"cancelled_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error("Failed to cancel pending user notifications",
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to cancel pending notifications"
|
||||
)
|
||||
|
||||
@router.delete("/users/{user_id}/notification-data")
|
||||
async def delete_user_notification_data(
|
||||
user_id: str,
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_role(["admin"])),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Delete all notification data for a user (admin only)"""
|
||||
try:
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid user ID format"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.notifications import (
|
||||
NotificationPreference,
|
||||
NotificationQueue,
|
||||
NotificationLog,
|
||||
DeliveryAttempt
|
||||
)
|
||||
|
||||
deletion_stats = {
|
||||
"user_id": user_id,
|
||||
"deleted_at": datetime.utcnow().isoformat(),
|
||||
"preferences_deleted": 0,
|
||||
"notifications_deleted": 0,
|
||||
"logs_deleted": 0,
|
||||
"delivery_attempts_deleted": 0,
|
||||
"errors": []
|
||||
}
|
||||
|
||||
# Delete delivery attempts first (they reference notifications)
|
||||
try:
|
||||
delivery_attempts_query = select(DeliveryAttempt).join(
|
||||
NotificationQueue, DeliveryAttempt.notification_id == NotificationQueue.id
|
||||
).where(NotificationQueue.user_id == user_uuid)
|
||||
delivery_attempts_result = await db.execute(delivery_attempts_query)
|
||||
delivery_attempts = delivery_attempts_result.scalars().all()
|
||||
|
||||
for attempt in delivery_attempts:
|
||||
await db.delete(attempt)
|
||||
|
||||
deletion_stats["delivery_attempts_deleted"] = len(delivery_attempts)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error deleting delivery attempts: {str(e)}"
|
||||
deletion_stats["errors"].append(error_msg)
|
||||
logger.error(error_msg)
|
||||
|
||||
# Delete notification queue entries
|
||||
try:
|
||||
notifications_delete_query = delete(NotificationQueue).where(
|
||||
NotificationQueue.user_id == user_uuid
|
||||
)
|
||||
notifications_delete_result = await db.execute(notifications_delete_query)
|
||||
deletion_stats["notifications_deleted"] = notifications_delete_result.rowcount
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error deleting notifications: {str(e)}"
|
||||
deletion_stats["errors"].append(error_msg)
|
||||
logger.error(error_msg)
|
||||
|
||||
# Delete notification logs
|
||||
try:
|
||||
logs_delete_query = delete(NotificationLog).where(
|
||||
NotificationLog.user_id == user_uuid
|
||||
)
|
||||
logs_delete_result = await db.execute(logs_delete_query)
|
||||
deletion_stats["logs_deleted"] = logs_delete_result.rowcount
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error deleting notification logs: {str(e)}"
|
||||
deletion_stats["errors"].append(error_msg)
|
||||
logger.error(error_msg)
|
||||
|
||||
# Delete notification preferences
|
||||
try:
|
||||
preferences_delete_query = delete(NotificationPreference).where(
|
||||
NotificationPreference.user_id == user_uuid
|
||||
)
|
||||
preferences_delete_result = await db.execute(preferences_delete_query)
|
||||
deletion_stats["preferences_deleted"] = preferences_delete_result.rowcount
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error deleting notification preferences: {str(e)}"
|
||||
deletion_stats["errors"].append(error_msg)
|
||||
logger.error(error_msg)
|
||||
|
||||
await db.commit()
|
||||
|
||||
logger.info("Deleted user notification data",
|
||||
user_id=user_id,
|
||||
preferences=deletion_stats["preferences_deleted"],
|
||||
notifications=deletion_stats["notifications_deleted"],
|
||||
logs=deletion_stats["logs_deleted"])
|
||||
|
||||
deletion_stats["success"] = len(deletion_stats["errors"]) == 0
|
||||
|
||||
return deletion_stats
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error("Failed to delete user notification data",
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to delete user notification data"
|
||||
)
|
||||
|
||||
@router.post("/notifications/user-deletion")
|
||||
async def send_user_deletion_notification(
|
||||
notification_data: dict, # {"admin_email": str, "deleted_user_email": str, "deletion_summary": dict}
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_role(["admin"])),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Send notification about user deletion to admins (admin only)"""
|
||||
try:
|
||||
admin_email = notification_data.get("admin_email")
|
||||
deleted_user_email = notification_data.get("deleted_user_email")
|
||||
deletion_summary = notification_data.get("deletion_summary", {})
|
||||
|
||||
if not admin_email or not deleted_user_email:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="admin_email and deleted_user_email are required"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid request data: {str(e)}"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.notifications import NotificationQueue
|
||||
from app.services.notification_service import NotificationService
|
||||
|
||||
# Create notification for the admin about the user deletion
|
||||
notification_content = {
|
||||
"subject": f"Admin User Deletion Completed - {deleted_user_email}",
|
||||
"message": f"""
|
||||
Admin User Deletion Summary
|
||||
|
||||
Deleted User: {deleted_user_email}
|
||||
Deletion Performed By: {admin_email}
|
||||
Deletion Date: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')}
|
||||
|
||||
Summary:
|
||||
- Tenants Affected: {deletion_summary.get('total_tenants_affected', 0)}
|
||||
- Models Deleted: {deletion_summary.get('total_models_deleted', 0)}
|
||||
- Forecasts Deleted: {deletion_summary.get('total_forecasts_deleted', 0)}
|
||||
- Notifications Deleted: {deletion_summary.get('total_notifications_deleted', 0)}
|
||||
- Tenants Transferred: {deletion_summary.get('tenants_transferred', 0)}
|
||||
- Tenants Deleted: {deletion_summary.get('tenants_deleted', 0)}
|
||||
|
||||
Status: {'Success' if deletion_summary.get('deletion_successful', False) else 'Completed with errors'}
|
||||
Total Errors: {deletion_summary.get('total_errors', 0)}
|
||||
|
||||
This action was performed through the admin user deletion system and all associated data has been permanently removed.
|
||||
""".strip(),
|
||||
"notification_type": "user_deletion_admin",
|
||||
"priority": "high"
|
||||
}
|
||||
|
||||
# Create notification queue entry
|
||||
notification = NotificationQueue(
|
||||
user_email=admin_email,
|
||||
notification_type="user_deletion_admin",
|
||||
subject=notification_content["subject"],
|
||||
message=notification_content["message"],
|
||||
priority="high",
|
||||
status="pending",
|
||||
created_at=datetime.utcnow(),
|
||||
metadata={
|
||||
"deleted_user_email": deleted_user_email,
|
||||
"deletion_summary": deletion_summary,
|
||||
"performed_by": current_user.get("user_id")
|
||||
}
|
||||
)
|
||||
|
||||
db.add(notification)
|
||||
await db.commit()
|
||||
|
||||
# Trigger immediate sending (assuming NotificationService exists)
|
||||
try:
|
||||
notification_service = NotificationService(db)
|
||||
await notification_service.process_pending_notification(notification.id)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to immediately send notification, will be processed by background worker",
|
||||
error=str(e))
|
||||
|
||||
logger.info("Created user deletion notification",
|
||||
admin_email=admin_email,
|
||||
deleted_user=deleted_user_email,
|
||||
notification_id=str(notification.id))
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "User deletion notification created successfully",
|
||||
"notification_id": str(notification.id),
|
||||
"recipient": admin_email,
|
||||
"created_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error("Failed to send user deletion notification",
|
||||
admin_email=admin_email,
|
||||
deleted_user=deleted_user_email,
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to send user deletion notification"
|
||||
)
|
||||
|
||||
@router.get("/users/{user_id}/notification-data/count")
|
||||
async def get_user_notification_data_count(
|
||||
user_id: str,
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_role(["admin"])),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get count of notification data for a user (admin only)"""
|
||||
try:
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid user ID format"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.notifications import (
|
||||
NotificationPreference,
|
||||
NotificationQueue,
|
||||
NotificationLog
|
||||
)
|
||||
|
||||
# Count preferences
|
||||
preferences_count_query = select(func.count(NotificationPreference.id)).where(
|
||||
NotificationPreference.user_id == user_uuid
|
||||
)
|
||||
preferences_count_result = await db.execute(preferences_count_query)
|
||||
preferences_count = preferences_count_result.scalar()
|
||||
|
||||
# Count notifications
|
||||
notifications_count_query = select(func.count(NotificationQueue.id)).where(
|
||||
NotificationQueue.user_id == user_uuid
|
||||
)
|
||||
notifications_count_result = await db.execute(notifications_count_query)
|
||||
notifications_count = notifications_count_result.scalar()
|
||||
|
||||
# Count logs
|
||||
logs_count_query = select(func.count(NotificationLog.id)).where(
|
||||
NotificationLog.user_id == user_uuid
|
||||
)
|
||||
logs_count_result = await db.execute(logs_count_query)
|
||||
logs_count = logs_count_result.scalar()
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"preferences_count": preferences_count,
|
||||
"notifications_count": notifications_count,
|
||||
"logs_count": logs_count,
|
||||
"total_notification_data": preferences_count + notifications_count + logs_count
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get user notification data count",
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get notification data count"
|
||||
)
|
||||
Reference in New Issue
Block a user