intermittent commit

This commit is contained in:
2026-07-14 13:22:11 +02:00
parent 3444a4920f
commit 6163c8f733
24 changed files with 2367 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from govoplan_core.auth import ApiPrincipal, get_api_principal, has_scope
from govoplan_core.db.session import get_session
from govoplan_notifications.backend.manifest import ADMIN_SCOPE, DISPATCH_SCOPE, READ_SCOPE, WRITE_SCOPE
from govoplan_notifications.backend.schemas import (
NotificationCreateRequest,
NotificationDeliverPendingRequest,
NotificationDeliveryResultResponse,
NotificationListResponse,
NotificationPreferencesResponse,
NotificationPreferencesUpdateRequest,
NotificationResponse,
NotificationSummaryResponse,
NotificationUpdateRequest,
)
from govoplan_notifications.backend.service import (
NotificationError,
create_notification,
deliver_notification,
deliver_pending,
get_notification,
get_notification_preferences,
list_notifications,
notification_preferences_response,
notification_response,
notification_summary,
update_notification_preferences,
update_notification,
)
router = APIRouter(prefix="/notifications", tags=["notifications"])
def _require_scope(principal: ApiPrincipal, scope: str) -> None:
if not has_scope(principal, scope):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"Missing scope: {scope}")
def _notification_http_error(exc: NotificationError) -> HTTPException:
if str(exc) == "Notification not found":
return HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
return HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
def _response(notification) -> NotificationResponse:
return NotificationResponse.model_validate(notification_response(notification))
@router.get("", response_model=NotificationListResponse)
def api_list_notifications(
status_filter: str | None = Query(default=None, alias="status"),
channel: str | None = None,
source_module: str | None = None,
recipient_id: str | None = None,
limit: int = Query(default=100, ge=1, le=500),
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationListResponse:
_require_scope(principal, READ_SCOPE)
notifications = list_notifications(
session,
tenant_id=principal.tenant_id,
status=status_filter,
channel=channel,
source_module=source_module,
recipient_id=recipient_id,
limit=limit,
)
return NotificationListResponse(notifications=[_response(notification) for notification in notifications])
@router.get("/summary", response_model=NotificationSummaryResponse)
def api_notification_summary(
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationSummaryResponse:
_require_scope(principal, READ_SCOPE)
return NotificationSummaryResponse.model_validate(notification_summary(session, tenant_id=principal.tenant_id, user_id=principal.user.id))
@router.get("/preferences/me", response_model=NotificationPreferencesResponse)
def api_get_my_notification_preferences(
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationPreferencesResponse:
_require_scope(principal, READ_SCOPE)
preference = get_notification_preferences(session, tenant_id=principal.tenant_id, user_id=principal.user.id)
return NotificationPreferencesResponse.model_validate(notification_preferences_response(preference))
@router.put("/preferences/me", response_model=NotificationPreferencesResponse)
def api_update_my_notification_preferences(
payload: NotificationPreferencesUpdateRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationPreferencesResponse:
_require_scope(principal, WRITE_SCOPE)
preference = update_notification_preferences(session, tenant_id=principal.tenant_id, user_id=principal.user.id, payload=payload)
session.commit()
return NotificationPreferencesResponse.model_validate(notification_preferences_response(preference))
@router.post("", response_model=NotificationResponse, status_code=status.HTTP_201_CREATED)
def api_create_notification(
payload: NotificationCreateRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationResponse:
_require_scope(principal, WRITE_SCOPE)
try:
notification = create_notification(session, tenant_id=principal.tenant_id, payload=payload)
except NotificationError as exc:
raise _notification_http_error(exc) from exc
session.commit()
return _response(notification)
@router.post("/deliver-pending", response_model=NotificationDeliveryResultResponse)
def api_deliver_pending(
payload: NotificationDeliverPendingRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationDeliveryResultResponse:
_require_scope(principal, DISPATCH_SCOPE)
if payload.tenant_id is not None:
_require_scope(principal, ADMIN_SCOPE)
result = deliver_pending(session, tenant_id=payload.tenant_id or principal.tenant_id, limit=payload.limit)
session.commit()
return NotificationDeliveryResultResponse.model_validate(result)
@router.get("/{notification_id}", response_model=NotificationResponse)
def api_get_notification(
notification_id: str,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationResponse:
_require_scope(principal, READ_SCOPE)
try:
return _response(get_notification(session, tenant_id=principal.tenant_id, notification_id=notification_id))
except NotificationError as exc:
raise _notification_http_error(exc) from exc
@router.patch("/{notification_id}", response_model=NotificationResponse)
def api_update_notification(
notification_id: str,
payload: NotificationUpdateRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationResponse:
_require_scope(principal, WRITE_SCOPE)
try:
notification = update_notification(session, tenant_id=principal.tenant_id, notification_id=notification_id, payload=payload)
except NotificationError as exc:
raise _notification_http_error(exc) from exc
session.commit()
return _response(notification)
@router.post("/{notification_id}/deliver", response_model=NotificationDeliveryResultResponse)
def api_deliver_notification(
notification_id: str,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> NotificationDeliveryResultResponse:
_require_scope(principal, DISPATCH_SCOPE)
try:
notification = deliver_notification(session, notification_id=notification_id)
except NotificationError as exc:
raise _notification_http_error(exc) from exc
session.commit()
sent = 1 if notification.status == "sent" else 0
failed = 1 if notification.status == "failed" else 0
skipped = 1 if notification.status == "skipped" else 0
return NotificationDeliveryResultResponse(
notification=_response(notification),
processed=1,
sent=sent,
failed=failed,
skipped=skipped,
errors=[notification.last_error] if notification.last_error else [],
)