feat(notify-bridge): phase 6 - database models and server API
New database schema with ServiceProvider abstraction: - ServiceProvider (replaces ImmichServer): type + JSON config - Tracker (replaces AlbumTracker): owns tracking_config_id - TrackingConfig: provider_type scoped, owned by Tracker - TemplateConfig: provider_type scoped, owned by Target - NotificationTarget: owns template_config_id (not tracking_config_id) - TrackerState, EventLog, User, TelegramBot, TelegramChat Full FastAPI server: - /api/providers: CRUD + test connection + list collections - /api/trackers: CRUD - /api/tracking-configs: CRUD with provider_type filter - /api/template-configs: CRUD with provider_type filter, system defaults - /api/targets: CRUD - /api/template-vars: variable docs filtered by provider type - /api/auth: setup, login, refresh, me, password change - /api/health: health check - Default template seeding on first startup (EN/RU for Immich) - pydantic-settings with NOTIFY_BRIDGE_ env prefix Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""Service Provider CRUD API routes."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import Any
|
||||
|
||||
from ..auth.dependencies import get_current_user
|
||||
from ..database.engine import get_session
|
||||
from ..database.models import ServiceProvider, User
|
||||
|
||||
router = APIRouter(prefix="/api/providers", tags=["providers"])
|
||||
|
||||
|
||||
class ProviderCreate(BaseModel):
|
||||
type: str
|
||||
name: str
|
||||
icon: str = ""
|
||||
config: dict[str, Any] = {}
|
||||
|
||||
|
||||
class ProviderUpdate(BaseModel):
|
||||
name: str | None = None
|
||||
icon: str | None = None
|
||||
config: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_providers(
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.exec(
|
||||
select(ServiceProvider).where(ServiceProvider.user_id == user.id)
|
||||
)
|
||||
return result.all()
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
async def create_provider(
|
||||
body: ProviderCreate,
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
provider = ServiceProvider(
|
||||
user_id=user.id,
|
||||
type=body.type,
|
||||
name=body.name,
|
||||
icon=body.icon,
|
||||
config=body.config,
|
||||
)
|
||||
session.add(provider)
|
||||
await session.commit()
|
||||
await session.refresh(provider)
|
||||
return provider
|
||||
|
||||
|
||||
@router.get("/{provider_id}")
|
||||
async def get_provider(
|
||||
provider_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
provider = await session.get(ServiceProvider, provider_id)
|
||||
if not provider or provider.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
return provider
|
||||
|
||||
|
||||
@router.put("/{provider_id}")
|
||||
async def update_provider(
|
||||
provider_id: int,
|
||||
body: ProviderUpdate,
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
provider = await session.get(ServiceProvider, provider_id)
|
||||
if not provider or provider.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
if body.name is not None:
|
||||
provider.name = body.name
|
||||
if body.icon is not None:
|
||||
provider.icon = body.icon
|
||||
if body.config is not None:
|
||||
provider.config = body.config
|
||||
|
||||
session.add(provider)
|
||||
await session.commit()
|
||||
await session.refresh(provider)
|
||||
return provider
|
||||
|
||||
|
||||
@router.delete("/{provider_id}", status_code=204)
|
||||
async def delete_provider(
|
||||
provider_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
provider = await session.get(ServiceProvider, provider_id)
|
||||
if not provider or provider.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
await session.delete(provider)
|
||||
await session.commit()
|
||||
|
||||
|
||||
@router.post("/{provider_id}/test")
|
||||
async def test_provider(
|
||||
provider_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
provider = await session.get(ServiceProvider, provider_id)
|
||||
if not provider or provider.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
if provider.type == "immich":
|
||||
import aiohttp
|
||||
from notify_bridge_core.providers.immich import ImmichServiceProvider
|
||||
config = provider.config
|
||||
async with aiohttp.ClientSession() as http_session:
|
||||
immich = ImmichServiceProvider(
|
||||
http_session,
|
||||
config.get("url", ""),
|
||||
config.get("api_key", ""),
|
||||
config.get("external_domain"),
|
||||
provider.name,
|
||||
)
|
||||
return await immich.test_connection()
|
||||
|
||||
return {"ok": False, "message": f"Unknown provider type: {provider.type}"}
|
||||
|
||||
|
||||
@router.get("/{provider_id}/collections")
|
||||
async def list_collections(
|
||||
provider_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
provider = await session.get(ServiceProvider, provider_id)
|
||||
if not provider or provider.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Provider not found")
|
||||
|
||||
if provider.type == "immich":
|
||||
import aiohttp
|
||||
from notify_bridge_core.providers.immich import ImmichServiceProvider
|
||||
config = provider.config
|
||||
async with aiohttp.ClientSession() as http_session:
|
||||
immich = ImmichServiceProvider(
|
||||
http_session,
|
||||
config.get("url", ""),
|
||||
config.get("api_key", ""),
|
||||
config.get("external_domain"),
|
||||
provider.name,
|
||||
)
|
||||
return await immich.list_collections()
|
||||
|
||||
return []
|
||||
Reference in New Issue
Block a user