""" Notification CRUD API endpoints (ATOMIC operations only) Handles basic notification retrieval and listing """ import structlog from fastapi import APIRouter, Depends, HTTPException, status, Query, Path from typing import List, Optional, Dict, Any from uuid import UUID from app.schemas.notifications import ( NotificationResponse, NotificationType, NotificationStatus ) from app.services.notification_service import EnhancedNotificationService from app.models.notifications import NotificationType as ModelNotificationType from shared.auth.decorators import get_current_user_dep from shared.auth.access_control import require_user_role from shared.routing.route_builder import RouteBuilder from shared.database.base import create_database_manager from shared.monitoring.metrics import track_endpoint_metrics logger = structlog.get_logger() router = APIRouter() route_builder = RouteBuilder('notifications') # Dependency injection for enhanced notification service def get_enhanced_notification_service(): from app.core.config import settings database_manager = create_database_manager(settings.DATABASE_URL, "notification") return EnhancedNotificationService(database_manager) # ============================================================================ # ATOMIC CRUD ENDPOINTS - Get/List notifications only # ============================================================================ @router.get( route_builder.build_resource_detail_route("{notification_id}"), response_model=NotificationResponse ) @require_user_role(['viewer', 'member', 'admin', 'owner']) @track_endpoint_metrics("notification_get") async def get_notification_enhanced( notification_id: UUID = Path(..., description="Notification ID"), tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), notification_service: EnhancedNotificationService = Depends(get_enhanced_notification_service) ): """Get a specific notification by ID with enhanced access control""" try: notification = await notification_service.get_notification_by_id(str(notification_id)) if not notification: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Notification not found" ) # Verify user has access to this notification if (notification.recipient_id != current_user["user_id"] and notification.sender_id != current_user["user_id"] and not notification.broadcast and current_user.get("role") not in ["admin", "manager"]): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to notification" ) return NotificationResponse.from_orm(notification) except HTTPException: raise except Exception as e: logger.error("Failed to get notification", notification_id=str(notification_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get notification" ) @router.get( route_builder.build_base_route("user/{user_id}"), response_model=List[NotificationResponse] ) @require_user_role(['viewer', 'member', 'admin', 'owner']) @track_endpoint_metrics("notification_get_user_notifications") async def get_user_notifications_enhanced( user_id: str = Path(..., description="User ID"), tenant_id: UUID = Path(..., description="Tenant ID"), unread_only: bool = Query(False, description="Only return unread notifications"), notification_type: Optional[NotificationType] = Query(None, description="Filter by notification type"), skip: int = Query(0, ge=0, description="Number of records to skip"), limit: int = Query(50, ge=1, le=100, description="Maximum number of records"), current_user: Dict[str, Any] = Depends(get_current_user_dep), notification_service: EnhancedNotificationService = Depends(get_enhanced_notification_service) ): """Get notifications for a user with enhanced filtering""" # Users can only get their own notifications unless they're admin # Handle demo user ID mismatch: frontend uses "demo-user" but token has "demo-user-{session-id}" is_demo_user = current_user["user_id"].startswith("demo-user-") and user_id == "demo-user" if user_id != current_user["user_id"] and not is_demo_user and current_user.get("role") not in ["admin", "manager"]: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Can only access your own notifications" ) try: # Convert string type to model enum if provided model_notification_type = None if notification_type: try: model_notification_type = ModelNotificationType(notification_type.value) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid notification type: {notification_type.value}" ) notifications = await notification_service.get_user_notifications( user_id=user_id, tenant_id=tenant_id, unread_only=unread_only, notification_type=model_notification_type, skip=skip, limit=limit ) return [NotificationResponse.from_orm(notification) for notification in notifications] except HTTPException: raise except Exception as e: logger.error("Failed to get user notifications", user_id=user_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get user notifications" ) @router.get( route_builder.build_base_route("list"), response_model=List[NotificationResponse] ) @require_user_role(["viewer", "member", "admin", "owner"]) @track_endpoint_metrics("notification_get_tenant_notifications") async def get_tenant_notifications_enhanced( tenant_id: str = Path(..., description="Tenant ID"), status_filter: Optional[NotificationStatus] = Query(None, description="Filter by status"), notification_type: Optional[NotificationType] = Query(None, description="Filter by type"), skip: int = Query(0, ge=0, description="Number of records to skip"), limit: int = Query(50, ge=1, le=100, description="Maximum number of records"), current_user: Dict[str, Any] = Depends(get_current_user_dep), notification_service: EnhancedNotificationService = Depends(get_enhanced_notification_service) ): """Get notifications for a tenant with enhanced filtering (admin/manager only)""" if current_user.get("role") not in ["admin", "manager"]: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only admins and managers can view tenant notifications" ) try: # Convert enums if provided model_notification_type = None if notification_type: try: model_notification_type = ModelNotificationType(notification_type.value) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid notification type: {notification_type.value}" ) model_status = None if status_filter: try: from app.models.notifications import NotificationStatus as ModelStatus model_status = ModelStatus(status_filter.value) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid status: {status_filter.value}" ) notifications = await notification_service.get_tenant_notifications( tenant_id=tenant_id, status=model_status, notification_type=model_notification_type, skip=skip, limit=limit ) return [NotificationResponse.from_orm(notification) for notification in notifications] except HTTPException: raise except Exception as e: logger.error("Failed to get tenant notifications", tenant_id=tenant_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get tenant notifications" )