Files
bakery-ia/services/notification/app/api/notifications.py

211 lines
8.6 KiB
Python

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