Files
haos-hacs-immich-album-watcher/packages/server/src/immich_watcher_server/main.py
alexei.dolgolyov ce21733ae6
Some checks failed
Validate / Hassfest (push) Has been cancelled
Jinja2 syntax highlighting + description field + preview toggle
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>
2026-03-19 19:28:00 +03:00

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,
)