feat: add Apprise sidecar user/admin notifications
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import asyncio
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
@@ -16,6 +17,8 @@ from ..db import (
|
||||
get_user_request_stats,
|
||||
get_global_request_leader,
|
||||
get_global_request_total,
|
||||
get_user_notification_settings,
|
||||
set_user_notification_settings,
|
||||
)
|
||||
from ..runtime import get_runtime_settings
|
||||
from ..clients.jellyfin import JellyfinClient
|
||||
@@ -28,6 +31,11 @@ from ..services.user_cache import (
|
||||
match_jellyseerr_user_id,
|
||||
save_jellyfin_users_cache,
|
||||
)
|
||||
from ..services.notifications import (
|
||||
notify_admins_new_signup,
|
||||
send_apprise_notification,
|
||||
validate_apprise_urls,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
@@ -119,10 +127,14 @@ async def jellyfin_login(form_data: OAuth2PasswordRequestForm = Depends()) -> di
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc
|
||||
if not isinstance(response, dict) or not response.get("User"):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Jellyfin credentials")
|
||||
create_user_if_missing(username, "jellyfin-user", role="user", auth_provider="jellyfin")
|
||||
created = create_user_if_missing(username, "jellyfin-user", role="user", auth_provider="jellyfin")
|
||||
user = get_user_by_username(username)
|
||||
if user and user.get("is_blocked"):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is blocked")
|
||||
if created:
|
||||
asyncio.create_task(
|
||||
asyncio.to_thread(notify_admins_new_signup, username, "jellyfin")
|
||||
)
|
||||
try:
|
||||
users = await client.get_users()
|
||||
if isinstance(users, list):
|
||||
@@ -159,7 +171,7 @@ async def jellyseerr_login(form_data: OAuth2PasswordRequestForm = Depends()) ->
|
||||
if not isinstance(response, dict):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Jellyseerr credentials")
|
||||
jellyseerr_user_id = _extract_jellyseerr_user_id(response)
|
||||
create_user_if_missing(
|
||||
created = create_user_if_missing(
|
||||
form_data.username,
|
||||
"jellyseerr-user",
|
||||
role="user",
|
||||
@@ -171,6 +183,10 @@ async def jellyseerr_login(form_data: OAuth2PasswordRequestForm = Depends()) ->
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is blocked")
|
||||
if jellyseerr_user_id is not None:
|
||||
set_user_jellyseerr_id(form_data.username, jellyseerr_user_id)
|
||||
if created:
|
||||
asyncio.create_task(
|
||||
asyncio.to_thread(notify_admins_new_signup, form_data.username, "jellyseerr")
|
||||
)
|
||||
token = create_access_token(form_data.username, "user")
|
||||
set_last_login(form_data.username)
|
||||
return {"access_token": token, "token_type": "bearer", "user": {"username": form_data.username, "role": "user"}}
|
||||
@@ -207,6 +223,48 @@ async def profile(current_user: dict = Depends(get_current_user)) -> dict:
|
||||
}
|
||||
|
||||
|
||||
@router.get("/notifications")
|
||||
async def get_notifications(current_user: dict = Depends(get_current_user)) -> dict:
|
||||
settings = get_user_notification_settings(current_user.get("username") or "")
|
||||
return settings
|
||||
|
||||
|
||||
@router.put("/notifications")
|
||||
async def update_notifications(payload: dict, current_user: dict = Depends(get_current_user)) -> dict:
|
||||
if not isinstance(payload, dict):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid payload")
|
||||
enabled = bool(payload.get("enabled"))
|
||||
urls_raw = payload.get("urls") or []
|
||||
if isinstance(urls_raw, str):
|
||||
urls = [line.strip() for line in urls_raw.splitlines() if line.strip()]
|
||||
elif isinstance(urls_raw, list):
|
||||
urls = [str(item).strip() for item in urls_raw if str(item).strip()]
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid URLs")
|
||||
try:
|
||||
validated = validate_apprise_urls(urls)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
|
||||
set_user_notification_settings(current_user.get("username") or "", enabled, validated)
|
||||
return {"status": "ok", "enabled": enabled, "urls": validated}
|
||||
|
||||
|
||||
@router.post("/notifications/test")
|
||||
async def test_notifications(current_user: dict = Depends(get_current_user)) -> dict:
|
||||
settings = get_user_notification_settings(current_user.get("username") or "")
|
||||
if not settings.get("enabled"):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Notifications are disabled")
|
||||
urls = settings.get("urls") or []
|
||||
if not urls:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No Apprise URLs configured")
|
||||
title = "Magent notification test"
|
||||
body = f"Hello {current_user.get('username')}, your Apprise notifications are working."
|
||||
sent = await asyncio.to_thread(send_apprise_notification, urls, title, body)
|
||||
if not sent:
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="Notification failed")
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@router.post("/password")
|
||||
async def change_password(payload: dict, current_user: dict = Depends(get_current_user)) -> dict:
|
||||
if current_user.get("auth_provider") != "local":
|
||||
|
||||
Reference in New Issue
Block a user