"""FastAPI application entry point.""" from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from .config import settings from .database.engine import init_db from .services.scheduler import start_scheduler, stop_scheduler from .auth.routes import router as auth_router from .api.servers import router as servers_router from .api.trackers import router as trackers_router from .api.tracking_configs import router as tracking_configs_router from .api.template_configs import router as template_configs_router from .api.targets import router as targets_router from .api.users import router as users_router from .api.status import router as status_router from .api.sync import router as sync_router from .api.telegram_bots import router as telegram_bots_router from .ai.telegram_webhook import router as telegram_ai_router logging.basicConfig( level=logging.DEBUG if settings.debug else logging.INFO, format="%(asctime)s %(levelname)s [%(name)s] %(message)s", ) _LOGGER = logging.getLogger(__name__) async def _seed_default_templates(): """Create default EN/RU template configs if none exist.""" from sqlmodel import func, select from sqlmodel.ext.asyncio.session import AsyncSession from .database.engine import get_engine from .database.models import TemplateConfig, get_default_templates engine = get_engine() async with AsyncSession(engine) as session: result = await session.exec(select(func.count()).select_from(TemplateConfig)) count = result.one() if count > 0: return # user_id=0 means system-owned (available to all users) en = TemplateConfig(user_id=0, name="Default EN", description="Default English notification templates", icon="mdiTranslate", **get_default_templates("en")) ru = TemplateConfig(user_id=0, name="По умолчанию RU", description="Шаблоны уведомлений на русском языке", icon="mdiTranslate", **get_default_templates("ru")) session.add(en) session.add(ru) await session.commit() _LOGGER.info("Seeded default EN and RU template configs") @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan: startup and shutdown.""" # Startup settings.data_dir.mkdir(parents=True, exist_ok=True) await init_db() _LOGGER.info("Database initialized at %s", settings.effective_database_url) # Seed default templates if none exist await _seed_default_templates() await start_scheduler() yield # Shutdown await stop_scheduler() app = FastAPI( title="Immich Watcher", description="Standalone Immich album change notification server", version="0.1.0", lifespan=lifespan, ) # CORS: restrict to same-origin in production, allow all in debug mode app.add_middleware( CORSMiddleware, allow_origins=["*"] if settings.debug else [], allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) # API routes app.include_router(auth_router) app.include_router(servers_router) app.include_router(trackers_router) app.include_router(tracking_configs_router) app.include_router(template_configs_router) app.include_router(targets_router) app.include_router(users_router) app.include_router(status_router) app.include_router(sync_router) app.include_router(telegram_bots_router) app.include_router(telegram_ai_router) # Serve frontend static files if available _frontend_dist = Path(__file__).parent / "frontend" if _frontend_dist.is_dir(): app.mount("/", StaticFiles(directory=_frontend_dist, html=True), name="frontend") @app.get("/api/health") async def health(): """Health check endpoint.""" from .ai.service import is_ai_enabled return {"status": "ok", "version": "0.1.0", "ai_enabled": is_ai_enabled()} def run(): """Run the server (entry point for `immich-watcher` CLI command).""" import uvicorn uvicorn.run( "immich_watcher_server.main:app", host=settings.host, port=settings.port, reload=settings.debug, )