Initial commit - production deployment
This commit is contained in:
210
services/notification/app/api/notifications.py
Normal file
210
services/notification/app/api/notifications.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""
|
||||
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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user