"""Immich server management API routes.""" from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from sqlmodel import select from sqlmodel.ext.asyncio.session import AsyncSession import aiohttp from immich_watcher_core.immich_client import ImmichClient from ..auth.dependencies import get_current_user from ..database.engine import get_session from ..database.models import ImmichServer, User router = APIRouter(prefix="/api/servers", tags=["servers"]) class ServerCreate(BaseModel): name: str = "Immich" url: str api_key: str class ServerUpdate(BaseModel): name: str | None = None url: str | None = None api_key: str | None = None class ServerResponse(BaseModel): id: int name: str url: str created_at: str @router.get("") async def list_servers( user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): """List all Immich servers for the current user.""" result = await session.exec( select(ImmichServer).where(ImmichServer.user_id == user.id) ) servers = result.all() return [ {"id": s.id, "name": s.name, "url": s.url, "created_at": s.created_at.isoformat()} for s in servers ] @router.post("", status_code=status.HTTP_201_CREATED) async def create_server( body: ServerCreate, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): """Add a new Immich server (validates connection).""" # Validate connection async with aiohttp.ClientSession() as http_session: client = ImmichClient(http_session, body.url, body.api_key) if not await client.ping(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Cannot connect to Immich server at {body.url}", ) # Fetch external domain external_domain = await client.get_server_config() server = ImmichServer( user_id=user.id, name=body.name, url=body.url, api_key=body.api_key, external_domain=external_domain, ) session.add(server) await session.commit() await session.refresh(server) return {"id": server.id, "name": server.name, "url": server.url} @router.get("/{server_id}") async def get_server( server_id: int, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): """Get a specific Immich server.""" server = await _get_user_server(session, server_id, user.id) return {"id": server.id, "name": server.name, "url": server.url, "created_at": server.created_at.isoformat()} @router.put("/{server_id}") async def update_server( server_id: int, body: ServerUpdate, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): """Update an Immich server.""" server = await _get_user_server(session, server_id, user.id) if body.name is not None: server.name = body.name url_changed = body.url is not None and body.url != server.url key_changed = body.api_key is not None and body.api_key != server.api_key if body.url is not None: server.url = body.url if body.api_key is not None: server.api_key = body.api_key # Re-validate and refresh external_domain when URL or API key changes if url_changed or key_changed: try: async with aiohttp.ClientSession() as http_session: client = ImmichClient(http_session, server.url, server.api_key) if not await client.ping(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Cannot connect to Immich server at {server.url}", ) server.external_domain = await client.get_server_config() except aiohttp.ClientError as err: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Connection error: {err}", ) session.add(server) await session.commit() await session.refresh(server) return {"id": server.id, "name": server.name, "url": server.url} @router.delete("/{server_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_server( server_id: int, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): """Delete an Immich server.""" server = await _get_user_server(session, server_id, user.id) await session.delete(server) await session.commit() @router.get("/{server_id}/ping") async def ping_server( server_id: int, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): """Check if an Immich server is reachable.""" server = await _get_user_server(session, server_id, user.id) async with aiohttp.ClientSession() as http_session: client = ImmichClient(http_session, server.url, server.api_key) ok = await client.ping() return {"online": ok} @router.get("/{server_id}/albums") async def list_albums( server_id: int, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): """Fetch albums from an Immich server.""" server = await _get_user_server(session, server_id, user.id) async with aiohttp.ClientSession() as http_session: client = ImmichClient(http_session, server.url, server.api_key) albums = await client.get_albums() return [ { "id": a.get("id"), "albumName": a.get("albumName"), "assetCount": a.get("assetCount", 0), "shared": a.get("shared", False), "updatedAt": a.get("updatedAt", ""), } for a in albums ] async def _get_user_server( session: AsyncSession, server_id: int, user_id: int ) -> ImmichServer: """Get a server owned by the user, or raise 404.""" server = await session.get(ImmichServer, server_id) if not server or server.user_id != user_id: raise HTTPException(status_code=404, detail="Server not found") return server