2025-07-21 22:44:11 +02:00
|
|
|
"""
|
2025-10-06 15:27:01 +02:00
|
|
|
Notification CRUD API endpoints (ATOMIC operations only)
|
|
|
|
|
Handles basic notification retrieval and listing
|
2025-07-21 22:44:11 +02:00
|
|
|
"""
|
|
|
|
|
|
2025-07-21 14:41:33 +02:00
|
|
|
import structlog
|
2025-10-06 15:27:01 +02:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Query, Path
|
2025-08-08 09:08:41 +02:00
|
|
|
from typing import List, Optional, Dict, Any
|
|
|
|
|
from uuid import UUID
|
2025-07-21 14:41:33 +02:00
|
|
|
|
2025-07-21 22:44:11 +02:00
|
|
|
from app.schemas.notifications import (
|
2025-10-06 15:27:01 +02:00
|
|
|
NotificationResponse, NotificationType, NotificationStatus
|
2025-07-21 22:44:11 +02:00
|
|
|
)
|
2025-08-08 09:08:41 +02:00
|
|
|
from app.services.notification_service import EnhancedNotificationService
|
|
|
|
|
from app.models.notifications import NotificationType as ModelNotificationType
|
2025-10-06 15:27:01 +02:00
|
|
|
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
|
2025-08-08 09:08:41 +02:00
|
|
|
from shared.database.base import create_database_manager
|
|
|
|
|
from shared.monitoring.metrics import track_endpoint_metrics
|
2025-07-21 14:41:33 +02:00
|
|
|
|
|
|
|
|
logger = structlog.get_logger()
|
2025-08-08 09:08:41 +02:00
|
|
|
router = APIRouter()
|
2025-10-06 15:27:01 +02:00
|
|
|
route_builder = RouteBuilder("notification")
|
2025-07-21 14:41:33 +02:00
|
|
|
|
2025-08-08 09:08:41 +02:00
|
|
|
# Dependency injection for enhanced notification service
|
|
|
|
|
def get_enhanced_notification_service():
|
|
|
|
|
database_manager = create_database_manager()
|
|
|
|
|
return EnhancedNotificationService(database_manager)
|
2025-07-21 22:44:11 +02:00
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
# ============================================================================
|
|
|
|
|
# ATOMIC CRUD ENDPOINTS - Get/List notifications only
|
|
|
|
|
# ============================================================================
|
2025-07-21 14:41:33 +02:00
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
@router.get(
|
|
|
|
|
route_builder.build_resource_detail_route("{notification_id}"),
|
|
|
|
|
response_model=NotificationResponse
|
|
|
|
|
)
|
|
|
|
|
@require_user_role(['viewer', 'member', 'admin', 'owner'])
|
2025-08-08 09:08:41 +02:00
|
|
|
@track_endpoint_metrics("notification_get")
|
|
|
|
|
async def get_notification_enhanced(
|
|
|
|
|
notification_id: UUID = Path(..., description="Notification ID"),
|
2025-10-06 15:27:01 +02:00
|
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
2025-07-21 22:44:11 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-08 09:08:41 +02:00
|
|
|
notification_service: EnhancedNotificationService = Depends(get_enhanced_notification_service)
|
2025-07-21 22:44:11 +02:00
|
|
|
):
|
2025-08-08 09:08:41 +02:00
|
|
|
"""Get a specific notification by ID with enhanced access control"""
|
|
|
|
|
|
2025-07-21 22:44:11 +02:00
|
|
|
try:
|
2025-08-08 09:08:41 +02:00
|
|
|
notification = await notification_service.get_notification_by_id(str(notification_id))
|
|
|
|
|
|
|
|
|
|
if not notification:
|
2025-07-21 22:44:11 +02:00
|
|
|
raise HTTPException(
|
2025-08-08 09:08:41 +02:00
|
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
|
detail="Notification not found"
|
2025-07-21 22:44:11 +02:00
|
|
|
)
|
|
|
|
|
|
2025-08-08 09:08:41 +02:00
|
|
|
# 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"
|
|
|
|
|
)
|
2025-07-21 22:44:11 +02:00
|
|
|
|
2025-08-08 09:08:41 +02:00
|
|
|
return NotificationResponse.from_orm(notification)
|
2025-07-21 22:44:11 +02:00
|
|
|
|
|
|
|
|
except HTTPException:
|
|
|
|
|
raise
|
|
|
|
|
except Exception as e:
|
2025-08-08 09:08:41 +02:00
|
|
|
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"
|
|
|
|
|
)
|
2025-07-21 22:44:11 +02:00
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
@router.get(
|
|
|
|
|
route_builder.build_base_route("user/{user_id}"),
|
|
|
|
|
response_model=List[NotificationResponse]
|
|
|
|
|
)
|
|
|
|
|
@require_user_role(['viewer', 'member', 'admin', 'owner'])
|
2025-08-08 09:08:41 +02:00
|
|
|
@track_endpoint_metrics("notification_get_user_notifications")
|
|
|
|
|
async def get_user_notifications_enhanced(
|
|
|
|
|
user_id: str = Path(..., description="User ID"),
|
2025-10-06 15:27:01 +02:00
|
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
2025-08-08 09:08:41 +02:00
|
|
|
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"),
|
2025-07-21 22:44:11 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-08 09:08:41 +02:00
|
|
|
notification_service: EnhancedNotificationService = Depends(get_enhanced_notification_service)
|
2025-07-21 22:44:11 +02:00
|
|
|
):
|
2025-08-08 09:08:41 +02:00
|
|
|
"""Get notifications for a user with enhanced filtering"""
|
|
|
|
|
|
|
|
|
|
# Users can only get their own notifications unless they're admin
|
|
|
|
|
if user_id != current_user["user_id"] and current_user.get("role") not in ["admin", "manager"]:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
|
|
|
detail="Can only access your own notifications"
|
2025-07-21 22:44:11 +02:00
|
|
|
)
|
2025-08-08 09:08:41 +02:00
|
|
|
|
2025-07-21 22:44:11 +02:00
|
|
|
try:
|
2025-08-08 09:08:41 +02:00
|
|
|
# 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,
|
2025-07-21 22:44:11 +02:00
|
|
|
tenant_id=tenant_id,
|
2025-08-08 09:08:41 +02:00
|
|
|
unread_only=unread_only,
|
|
|
|
|
notification_type=model_notification_type,
|
|
|
|
|
skip=skip,
|
|
|
|
|
limit=limit
|
2025-07-21 22:44:11 +02:00
|
|
|
)
|
|
|
|
|
|
2025-08-08 09:08:41 +02:00
|
|
|
return [NotificationResponse.from_orm(notification) for notification in notifications]
|
2025-07-21 22:44:11 +02:00
|
|
|
|
|
|
|
|
except HTTPException:
|
|
|
|
|
raise
|
|
|
|
|
except Exception as e:
|
2025-08-08 09:08:41 +02:00
|
|
|
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"
|
2025-07-21 14:41:33 +02:00
|
|
|
)
|
2025-07-21 22:44:11 +02:00
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
@router.get(
|
|
|
|
|
route_builder.build_base_route("list"),
|
|
|
|
|
response_model=List[NotificationResponse]
|
|
|
|
|
)
|
|
|
|
|
@require_user_role(["viewer", "member", "admin", "owner"])
|
2025-08-08 09:08:41 +02:00
|
|
|
@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"),
|
2025-07-21 22:44:11 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-08 09:08:41 +02:00
|
|
|
notification_service: EnhancedNotificationService = Depends(get_enhanced_notification_service)
|
2025-07-21 22:44:11 +02:00
|
|
|
):
|
2025-08-08 09:08:41 +02:00
|
|
|
"""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"
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-21 22:44:11 +02:00
|
|
|
try:
|
2025-08-08 09:08:41 +02:00
|
|
|
# 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(
|
2025-07-21 22:44:11 +02:00
|
|
|
tenant_id=tenant_id,
|
2025-08-08 09:08:41 +02:00
|
|
|
status=model_status,
|
|
|
|
|
notification_type=model_notification_type,
|
|
|
|
|
skip=skip,
|
|
|
|
|
limit=limit
|
2025-07-21 22:44:11 +02:00
|
|
|
)
|
|
|
|
|
|
2025-08-08 09:08:41 +02:00
|
|
|
return [NotificationResponse.from_orm(notification) for notification in notifications]
|
2025-07-21 22:44:11 +02:00
|
|
|
|
|
|
|
|
except HTTPException:
|
|
|
|
|
raise
|
|
|
|
|
except Exception as e:
|
2025-08-08 09:08:41 +02:00
|
|
|
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"
|
|
|
|
|
)
|
2025-07-21 22:44:11 +02:00
|
|
|
|