Files
OPNexus/backend/app/routers/notifications.py
T
Julian Boldt e51df4ba04
Release / verify (push) Canceled after 0s
CI / checks (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / backend (push) Canceled after 0s
Release / test (push) Canceled after 0s
Release / publish (backend) (push) Canceled after 0s
Release / publish (frontend) (push) Canceled after 0s
Release / publish (proxy) (push) Canceled after 0s
Release v1.11.0-rc1
Siehe CHANGELOG.md
2026-10-07 12:24:38 +02:00

82 lines
3.1 KiB
Python

from urllib.parse import urlsplit
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.errors import ErrorCode, api_error
from app.database import get_db
from app.models.user import AdminUser
from app.routers.auth import current_user
from app.models.notification import NotificationProblem
from app.schemas.notification import (
NotificationProblemRead,
NotificationSettingsRead,
NotificationSettingsUpdate,
TestNotificationRequest,
)
from app.services import notification_delivery, notification_settings_service
router = APIRouter(prefix="/notifications", tags=["notifications"])
def _redact_url(url: str | None) -> str | None:
"""Webhook URLs very often carry the credential itself (a token in the path or
query), so anyone who can read the URL can post to the receiver. Readers see
only where it points."""
if not url:
return url
parts = urlsplit(url)
if not parts.hostname:
return "…"
return f"{parts.scheme}://{parts.hostname}{f':{parts.port}' if parts.port else ''}/…"
@router.get("/settings", response_model=NotificationSettingsRead)
async def read_settings(db: AsyncSession = Depends(get_db), user: AdminUser = Depends(current_user)):
row = await notification_settings_service.get_settings(db)
result = NotificationSettingsRead.model_validate(row)
if user.role != "admin":
result = result.model_copy(update={"webhook_url": _redact_url(result.webhook_url)})
return result
@router.put("/settings", response_model=NotificationSettingsRead)
async def update_settings(payload: NotificationSettingsUpdate, db: AsyncSession = Depends(get_db)):
return await notification_settings_service.update_settings(db, payload)
@router.get("/problems", response_model=list[NotificationProblemRead])
async def list_problems(
include_resolved: bool = False,
limit: int = Query(default=100, ge=1, le=500),
db: AsyncSession = Depends(get_db),
):
clauses = [] if include_resolved else [NotificationProblem.status == "open"]
result = await db.execute(
select(NotificationProblem)
.where(*clauses)
.order_by(NotificationProblem.last_seen_at.desc())
.limit(limit)
)
return list(result.scalars().all())
@router.post("/test")
async def test_notification(payload: TestNotificationRequest, db: AsyncSession = Depends(get_db)):
settings_row = await notification_settings_service.get_settings(db)
fake_problem = NotificationProblem(
problem_type="test",
dedup_key="test",
context={"message": "Dies ist eine Testbenachrichtigung von OPNexus."},
)
try:
if payload.channel == "webhook":
await notification_delivery.send_webhook(settings_row, fake_problem, "opened")
else:
subject, body = notification_delivery.render_message(fake_problem, "opened")
await notification_delivery.send_email(settings_row, subject, body)
except notification_delivery.NotificationDeliveryError as exc:
raise api_error(502, ErrorCode.UPSTREAM_REQUEST_FAILED) from exc
return {"success": True}