464 lines
17 KiB
Python
464 lines
17 KiB
Python
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Request, Query
|
|
from typing import List, Dict, Any, Optional
|
|
from fastapi import status
|
|
import logging
|
|
import json
|
|
|
|
# Import auth module
|
|
from modules.auth import limiter, getCurrentUser
|
|
|
|
# Import interfaces
|
|
import modules.interfaces.interfaceDbComponentObjects as interfaceDbComponentObjects
|
|
from modules.datamodels.datamodelMessaging import (
|
|
MessagingSubscription,
|
|
MessagingSubscriptionRegistration,
|
|
MessagingDelivery,
|
|
MessagingChannel,
|
|
MessagingEventParameters,
|
|
MessagingSubscriptionExecutionResult
|
|
)
|
|
from modules.datamodels.datamodelUam import User
|
|
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata
|
|
|
|
# Configure logger
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Create router for messaging endpoints
|
|
router = APIRouter(
|
|
prefix="/api/messaging",
|
|
tags=["Messaging"],
|
|
responses={404: {"description": "Not found"}}
|
|
)
|
|
|
|
|
|
# Subscription Endpoints
|
|
|
|
@router.get("/subscriptions", response_model=PaginatedResponse[MessagingSubscription])
|
|
@limiter.limit("60/minute")
|
|
async def getSubscriptions(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> PaginatedResponse[MessagingSubscription]:
|
|
"""Get subscriptions with optional pagination, sorting, and filtering."""
|
|
paginationParams = None
|
|
if pagination:
|
|
try:
|
|
paginationDict = json.loads(pagination)
|
|
paginationParams = PaginationParams(**paginationDict) if paginationDict else None
|
|
except (json.JSONDecodeError, ValueError) as e:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Invalid pagination parameter: {str(e)}"
|
|
)
|
|
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
result = managementInterface.getAllSubscriptions(pagination=paginationParams)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page,
|
|
pageSize=paginationParams.pageSize,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort,
|
|
filters=paginationParams.filters
|
|
)
|
|
)
|
|
else:
|
|
return PaginatedResponse(
|
|
items=result,
|
|
pagination=None
|
|
)
|
|
|
|
|
|
@router.post("/subscriptions", response_model=MessagingSubscription)
|
|
@limiter.limit("60/minute")
|
|
async def createSubscription(
|
|
request: Request,
|
|
subscription: MessagingSubscription,
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> MessagingSubscription:
|
|
"""Create a new subscription"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
subscriptionData = subscription.model_dump(exclude={"id"})
|
|
newSubscription = managementInterface.createSubscription(subscriptionData)
|
|
|
|
return MessagingSubscription(**newSubscription)
|
|
|
|
|
|
@router.get("/subscriptions/{subscriptionId}", response_model=MessagingSubscription)
|
|
@limiter.limit("60/minute")
|
|
async def getSubscription(
|
|
request: Request,
|
|
subscriptionId: str = Path(..., description="ID of the subscription"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> MessagingSubscription:
|
|
"""Get a specific subscription"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
subscription = managementInterface.getSubscription(subscriptionId)
|
|
if not subscription:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Subscription with ID {subscriptionId} not found"
|
|
)
|
|
|
|
return subscription
|
|
|
|
|
|
@router.put("/subscriptions/{subscriptionId}", response_model=MessagingSubscription)
|
|
@limiter.limit("60/minute")
|
|
async def updateSubscription(
|
|
request: Request,
|
|
subscriptionId: str = Path(..., description="ID of the subscription to update"),
|
|
subscriptionData: MessagingSubscription = Body(...),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> MessagingSubscription:
|
|
"""Update an existing subscription"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
existingSubscription = managementInterface.getSubscription(subscriptionId)
|
|
if not existingSubscription:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Subscription with ID {subscriptionId} not found"
|
|
)
|
|
|
|
updateData = subscriptionData.model_dump(exclude={"id", "subscriptionId"})
|
|
updatedSubscription = managementInterface.updateSubscription(subscriptionId, updateData)
|
|
|
|
if not updatedSubscription:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error updating the subscription"
|
|
)
|
|
|
|
return MessagingSubscription(**updatedSubscription)
|
|
|
|
|
|
@router.delete("/subscriptions/{subscriptionId}", response_model=Dict[str, Any])
|
|
@limiter.limit("60/minute")
|
|
async def deleteSubscription(
|
|
request: Request,
|
|
subscriptionId: str = Path(..., description="ID of the subscription to delete"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> Dict[str, Any]:
|
|
"""Delete a subscription"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
existingSubscription = managementInterface.getSubscription(subscriptionId)
|
|
if not existingSubscription:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Subscription with ID {subscriptionId} not found"
|
|
)
|
|
|
|
success = managementInterface.deleteSubscription(subscriptionId)
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error deleting the subscription"
|
|
)
|
|
|
|
return {"message": f"Subscription with ID {subscriptionId} successfully deleted"}
|
|
|
|
|
|
# Registration Endpoints
|
|
|
|
@router.get("/subscriptions/{subscriptionId}/registrations", response_model=PaginatedResponse[MessagingSubscriptionRegistration])
|
|
@limiter.limit("60/minute")
|
|
async def getSubscriptionRegistrations(
|
|
request: Request,
|
|
subscriptionId: str = Path(..., description="ID of the subscription"),
|
|
pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> PaginatedResponse[MessagingSubscriptionRegistration]:
|
|
"""Get registrations for a subscription"""
|
|
paginationParams = None
|
|
if pagination:
|
|
try:
|
|
paginationDict = json.loads(pagination)
|
|
paginationParams = PaginationParams(**paginationDict) if paginationDict else None
|
|
except (json.JSONDecodeError, ValueError) as e:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Invalid pagination parameter: {str(e)}"
|
|
)
|
|
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
result = managementInterface.getAllRegistrations(
|
|
subscriptionId=subscriptionId,
|
|
pagination=paginationParams
|
|
)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page,
|
|
pageSize=paginationParams.pageSize,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort,
|
|
filters=paginationParams.filters
|
|
)
|
|
)
|
|
else:
|
|
return PaginatedResponse(
|
|
items=result,
|
|
pagination=None
|
|
)
|
|
|
|
|
|
@router.post("/subscriptions/{subscriptionId}/subscribe", response_model=MessagingSubscriptionRegistration)
|
|
@limiter.limit("60/minute")
|
|
async def subscribeUser(
|
|
request: Request,
|
|
subscriptionId: str = Path(..., description="ID of the subscription"),
|
|
channel: MessagingChannel = Body(..., embed=True),
|
|
channelConfig: str = Body(..., embed=True),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> MessagingSubscriptionRegistration:
|
|
"""Subscribe user to a subscription with a specific channel"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
registration = managementInterface.subscribeUser(
|
|
subscriptionId=subscriptionId,
|
|
userId=currentUser.id,
|
|
channel=channel,
|
|
channelConfig=channelConfig
|
|
)
|
|
|
|
return MessagingSubscriptionRegistration(**registration)
|
|
|
|
|
|
@router.delete("/subscriptions/{subscriptionId}/unsubscribe", response_model=Dict[str, Any])
|
|
@limiter.limit("60/minute")
|
|
async def unsubscribeUser(
|
|
request: Request,
|
|
subscriptionId: str = Path(..., description="ID of the subscription"),
|
|
channel: MessagingChannel = Body(..., embed=True),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> Dict[str, Any]:
|
|
"""Unsubscribe user from a subscription for a specific channel"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
success = managementInterface.unsubscribeUser(
|
|
subscriptionId=subscriptionId,
|
|
userId=currentUser.id,
|
|
channel=channel
|
|
)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Registration not found"
|
|
)
|
|
|
|
return {"message": f"Successfully unsubscribed from {subscriptionId} for channel {channel.value}"}
|
|
|
|
|
|
@router.get("/registrations", response_model=PaginatedResponse[MessagingSubscriptionRegistration])
|
|
@limiter.limit("60/minute")
|
|
async def getMyRegistrations(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> PaginatedResponse[MessagingSubscriptionRegistration]:
|
|
"""Get own registrations"""
|
|
paginationParams = None
|
|
if pagination:
|
|
try:
|
|
paginationDict = json.loads(pagination)
|
|
paginationParams = PaginationParams(**paginationDict) if paginationDict else None
|
|
except (json.JSONDecodeError, ValueError) as e:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Invalid pagination parameter: {str(e)}"
|
|
)
|
|
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
result = managementInterface.getAllRegistrations(
|
|
userId=currentUser.id,
|
|
pagination=paginationParams
|
|
)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page,
|
|
pageSize=paginationParams.pageSize,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort,
|
|
filters=paginationParams.filters
|
|
)
|
|
)
|
|
else:
|
|
return PaginatedResponse(
|
|
items=result,
|
|
pagination=None
|
|
)
|
|
|
|
|
|
@router.put("/registrations/{registrationId}", response_model=MessagingSubscriptionRegistration)
|
|
@limiter.limit("60/minute")
|
|
async def updateRegistration(
|
|
request: Request,
|
|
registrationId: str = Path(..., description="ID of the registration to update"),
|
|
registrationData: MessagingSubscriptionRegistration = Body(...),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> MessagingSubscriptionRegistration:
|
|
"""Update a registration"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
existingRegistration = managementInterface.getRegistration(registrationId)
|
|
if not existingRegistration:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Registration with ID {registrationId} not found"
|
|
)
|
|
|
|
updateData = registrationData.model_dump(exclude={"id", "subscriptionId", "userId"})
|
|
updatedRegistration = managementInterface.updateRegistration(registrationId, updateData)
|
|
|
|
if not updatedRegistration:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error updating the registration"
|
|
)
|
|
|
|
return MessagingSubscriptionRegistration(**updatedRegistration)
|
|
|
|
|
|
@router.delete("/registrations/{registrationId}", response_model=Dict[str, Any])
|
|
@limiter.limit("60/minute")
|
|
async def deleteRegistration(
|
|
request: Request,
|
|
registrationId: str = Path(..., description="ID of the registration to delete"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> Dict[str, Any]:
|
|
"""Delete a registration"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
existingRegistration = managementInterface.getRegistration(registrationId)
|
|
if not existingRegistration:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Registration with ID {registrationId} not found"
|
|
)
|
|
|
|
success = managementInterface.deleteRegistration(registrationId)
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error deleting the registration"
|
|
)
|
|
|
|
return {"message": f"Registration with ID {registrationId} successfully deleted"}
|
|
|
|
|
|
# Trigger Endpoints
|
|
|
|
def _getTriggerKey(request: Request) -> str:
|
|
"""Custom key function for trigger rate limiting per subscriptionId"""
|
|
subscriptionId = request.path_params.get("subscriptionId", "unknown")
|
|
return f"{request.client.host}:{subscriptionId}"
|
|
|
|
|
|
@router.post("/trigger/{subscriptionId}", response_model=MessagingSubscriptionExecutionResult)
|
|
@limiter.limit("60/minute", key_func=_getTriggerKey)
|
|
async def triggerSubscription(
|
|
request: Request,
|
|
subscriptionId: str = Path(..., description="ID of the subscription to trigger"),
|
|
eventParameters: Dict[str, Any] = Body(...),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> MessagingSubscriptionExecutionResult:
|
|
"""Trigger a subscription with event parameters"""
|
|
# RBAC-Check: Nur Admin/Mandate-Admin kann triggern
|
|
# TODO: Add proper RBAC check here
|
|
|
|
# Get messaging service from request app state
|
|
# We need to access services through the request
|
|
from modules.services import getInterface as getServicesInterface
|
|
services = getServicesInterface(currentUser, None)
|
|
|
|
# Konvertiere Dict zu Pydantic Model
|
|
eventParams = MessagingEventParameters(triggerData=eventParameters)
|
|
|
|
executionResult = services.messaging.executeSubscription(subscriptionId, eventParams)
|
|
return executionResult
|
|
|
|
|
|
# Delivery Endpoints
|
|
|
|
@router.get("/deliveries", response_model=PaginatedResponse[MessagingDelivery])
|
|
@limiter.limit("60/minute")
|
|
async def getDeliveries(
|
|
request: Request,
|
|
subscriptionId: Optional[str] = Query(None, description="Filter by subscription ID"),
|
|
pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> PaginatedResponse[MessagingDelivery]:
|
|
"""Get delivery history"""
|
|
paginationParams = None
|
|
if pagination:
|
|
try:
|
|
paginationDict = json.loads(pagination)
|
|
paginationParams = PaginationParams(**paginationDict) if paginationDict else None
|
|
except (json.JSONDecodeError, ValueError) as e:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Invalid pagination parameter: {str(e)}"
|
|
)
|
|
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
result = managementInterface.getDeliveries(
|
|
subscriptionId=subscriptionId,
|
|
userId=currentUser.id, # Users can only see their own deliveries
|
|
pagination=paginationParams
|
|
)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page,
|
|
pageSize=paginationParams.pageSize,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort,
|
|
filters=paginationParams.filters
|
|
)
|
|
)
|
|
else:
|
|
return PaginatedResponse(
|
|
items=result,
|
|
pagination=None
|
|
)
|
|
|
|
|
|
@router.get("/deliveries/{deliveryId}", response_model=MessagingDelivery)
|
|
@limiter.limit("60/minute")
|
|
async def getDelivery(
|
|
request: Request,
|
|
deliveryId: str = Path(..., description="ID of the delivery"),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> MessagingDelivery:
|
|
"""Get a specific delivery"""
|
|
managementInterface = interfaceDbComponentObjects.getInterface(currentUser)
|
|
|
|
delivery = managementInterface.getDelivery(deliveryId)
|
|
if not delivery:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Delivery with ID {deliveryId} not found"
|
|
)
|
|
|
|
return delivery
|
|
|