- Replace wttr.in (unreachable from Docker) with Open-Meteo API (free, no API key, reliable) with geocoding cache - WMO weather codes mapped to German descriptions + emoji icons - Add [WEATHER], [NEWS], [UNRAID], [DASHBOARD] log prefixes - Structured integration status table on startup - Suppress noisy httpx INFO logs (services log their own summaries) - Add logging to unraid_service (was completely silent on errors) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
172 lines
5.8 KiB
Python
172 lines
5.8 KiB
Python
"""Daily Briefing Dashboard — FastAPI Application."""
|
|
|
|
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.responses import FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from server.config import get_settings, reload_settings, settings
|
|
from server.services.mqtt_service import mqtt_service
|
|
|
|
logger = logging.getLogger("daily-briefing")
|
|
logging.basicConfig(
|
|
level=logging.DEBUG if settings.debug else logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
)
|
|
|
|
# Reduce noise from third-party libraries — our services log their own summaries
|
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Startup / shutdown lifecycle."""
|
|
from server import db
|
|
from server.migrations.runner import run_migrations
|
|
from server.services.seed_service import seed_if_empty
|
|
|
|
logger.info("Starting Daily Briefing Dashboard v2.1...")
|
|
|
|
# 1. Initialize shared database pool (bootstrap from ENV)
|
|
try:
|
|
pool = await db.init_pool(
|
|
host=settings.db_host,
|
|
port=settings.db_port,
|
|
dbname=settings.db_name,
|
|
user=settings.db_user,
|
|
password=settings.db_password,
|
|
)
|
|
logger.info("Database pool initialized")
|
|
except Exception:
|
|
logger.exception("Failed to initialize database pool — admin + news will be unavailable")
|
|
yield
|
|
return
|
|
|
|
# 2. Run database migrations
|
|
try:
|
|
await run_migrations(pool)
|
|
except Exception:
|
|
logger.exception("Migration error — some features may not work")
|
|
|
|
# 3. Seed database from ENV on first run
|
|
try:
|
|
await seed_if_empty()
|
|
except Exception:
|
|
logger.exception("Seeding error — admin panel may need manual setup")
|
|
|
|
# 4. Load settings from database (overrides ENV defaults)
|
|
try:
|
|
await reload_settings()
|
|
cfg = get_settings()
|
|
|
|
# Structured startup summary
|
|
integrations = [
|
|
("Weather", True, f"{cfg.weather_location} + {cfg.weather_location_secondary}"),
|
|
("HA", cfg.ha_enabled, cfg.ha_url or "not configured"),
|
|
("Vikunja", cfg.vikunja_enabled, cfg.vikunja_url or "not configured"),
|
|
("Unraid", cfg.unraid_enabled, f"{len(cfg.unraid_servers)} server(s)"),
|
|
("MQTT", cfg.mqtt_enabled, f"{cfg.mqtt_host}:{cfg.mqtt_port}" if cfg.mqtt_host else "not configured"),
|
|
("News", cfg.news_enabled, f"max_age={cfg.news_max_age_hours}h"),
|
|
]
|
|
logger.info("--- Integration Status ---")
|
|
for name, enabled, detail in integrations:
|
|
status = "ON " if enabled else "OFF"
|
|
logger.info(" [%s] %-10s %s", status, name, detail)
|
|
logger.info("--------------------------")
|
|
except Exception:
|
|
logger.exception("Failed to load settings from DB — using ENV defaults")
|
|
cfg = settings
|
|
|
|
# 5. Start MQTT service if enabled
|
|
if cfg.mqtt_enabled and cfg.mqtt_host:
|
|
try:
|
|
await mqtt_service.start(
|
|
host=cfg.mqtt_host,
|
|
port=cfg.mqtt_port,
|
|
username=cfg.mqtt_username or None,
|
|
password=cfg.mqtt_password or None,
|
|
topics=cfg.mqtt_topics,
|
|
client_id=cfg.mqtt_client_id,
|
|
)
|
|
logger.info("MQTT service started (%s:%d)", cfg.mqtt_host, cfg.mqtt_port)
|
|
except Exception:
|
|
logger.exception("Failed to start MQTT service")
|
|
else:
|
|
logger.info("MQTT disabled — configure via Admin Panel or MQTT_HOST env")
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
logger.info("Shutting down...")
|
|
await mqtt_service.stop()
|
|
await db.close_pool()
|
|
|
|
|
|
app = FastAPI(
|
|
title="Daily Briefing",
|
|
version="2.1.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# CORS — allow frontend dev server
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# --- Register Routers ---
|
|
from server.routers import admin, auth, dashboard, homeassistant, mqtt, news, servers, setup, tasks, weather # noqa: E402
|
|
|
|
app.include_router(setup.router)
|
|
app.include_router(auth.router)
|
|
app.include_router(admin.router)
|
|
app.include_router(weather.router)
|
|
app.include_router(news.router)
|
|
app.include_router(servers.router)
|
|
app.include_router(homeassistant.router)
|
|
app.include_router(tasks.router)
|
|
app.include_router(mqtt.router)
|
|
app.include_router(dashboard.router)
|
|
|
|
# --- Serve static frontend (production) ---
|
|
static_dir = Path(__file__).parent.parent / "static"
|
|
if static_dir.is_dir():
|
|
# SPA fallback: serve index.html for any non-API path
|
|
@app.get("/setup{full_path:path}")
|
|
async def setup_spa_fallback(full_path: str = ""):
|
|
index = static_dir / "index.html"
|
|
if index.exists():
|
|
return FileResponse(str(index))
|
|
return {"error": "Frontend not built"}
|
|
|
|
@app.get("/admin/{full_path:path}")
|
|
async def admin_spa_fallback(full_path: str):
|
|
index = static_dir / "index.html"
|
|
if index.exists():
|
|
return FileResponse(str(index))
|
|
return {"error": "Frontend not built"}
|
|
|
|
app.mount("/", StaticFiles(directory=str(static_dir), html=True), name="static")
|
|
logger.info("Serving static frontend from %s", static_dir)
|
|
else:
|
|
@app.get("/")
|
|
async def root():
|
|
return {
|
|
"status": "ok",
|
|
"message": "Daily Briefing API v2.1 — Frontend not built yet",
|
|
"endpoints": [
|
|
"/api/all", "/api/weather", "/api/news", "/api/servers",
|
|
"/api/ha", "/api/tasks", "/api/mqtt",
|
|
"/api/auth/login", "/api/admin/integrations",
|
|
],
|
|
}
|