Some checks failed
Validate / Hassfest (push) Has been cancelled
JinjaEditor:
- Custom StreamLanguage parser for Jinja2 syntax highlighting:
{{ variables }} in blue, {% statements %} in purple, {# comments #} in gray
- Replaced HTML mode (didn't understand Jinja2 syntax)
- Proper monospace font (Consolas/Monaco)
TemplateConfig:
- Added `description` field to model + seed defaults with descriptions
- Description shown on template cards instead of raw template text
- Description input in create/edit form
Preview:
- Toggle behavior: clicking Preview again hides the preview
- Per-slot preview uses preview-raw API (renders current editor content)
i18n: added common.description, templateConfig.descriptionPlaceholder
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
130 lines
4.2 KiB
Python
130 lines
4.2 KiB
Python
"""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,
|
|
)
|