init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,106 @@
|
||||
"""The ASGI application.
|
||||
|
||||
On start it creates the schema, seeds the settings from the environment, starts
|
||||
the background jobs and the Telegram bot, and — when a built frontend is present
|
||||
in `static/` — serves the web UI from the same origin, which is what the Docker
|
||||
image does.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app import __version__
|
||||
from app.api import api_router
|
||||
from app.api.deps import require_token
|
||||
from app.config import env_settings
|
||||
from app.db import init_db, session_scope
|
||||
from app.services import scheduler, settings_service, telegram_bot
|
||||
|
||||
logging.basicConfig(
|
||||
level=os.environ.get("LOG_LEVEL", "INFO").upper(),
|
||||
format="%(asctime)s %(levelname)-8s %(name)s: %(message)s",
|
||||
)
|
||||
log = logging.getLogger("kadence")
|
||||
|
||||
STATIC_DIR = Path(__file__).resolve().parent.parent / "static"
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
init_db()
|
||||
with session_scope() as session:
|
||||
settings = settings_service.load_settings(session)
|
||||
log.info("settings loaded (timezone=%s)", settings.timezone)
|
||||
|
||||
scheduler.start(settings)
|
||||
if settings.notifications.telegram.enabled:
|
||||
telegram_bot.start()
|
||||
# The bot has to come up later too, if Telegram is switched on in the UI.
|
||||
settings_service.on_change(
|
||||
lambda new: telegram_bot.start() if new.notifications.telegram.enabled else None
|
||||
)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
scheduler.shutdown()
|
||||
telegram_bot.shutdown()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Kadence",
|
||||
version=__version__,
|
||||
summary="Pomodoro timer, todo aggregation, blocking policy and daily reports.",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=env_settings().cors_origin_list,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(api_router, dependencies=[Depends(require_token)])
|
||||
|
||||
|
||||
@app.get("/api/health", tags=["meta"])
|
||||
def health() -> dict:
|
||||
return {"status": "ok", "version": __version__}
|
||||
|
||||
|
||||
@app.get("/api/info", tags=["meta"])
|
||||
def info() -> dict:
|
||||
"""What a client needs to know before it has a token: who we are, what is on."""
|
||||
env = env_settings()
|
||||
return {
|
||||
"version": __version__,
|
||||
"auth_required": bool(env.api_token),
|
||||
"scheduler": env.scheduler_enabled,
|
||||
"jobs": scheduler.describe(),
|
||||
}
|
||||
|
||||
|
||||
# ---- the web UI, when it has been built into ./static ----------------------
|
||||
if STATIC_DIR.is_dir():
|
||||
app.mount("/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="assets")
|
||||
|
||||
@app.get("/{path:path}", include_in_schema=False)
|
||||
def spa(path: str):
|
||||
"""Serve the single-page app, letting the client router own the URL."""
|
||||
candidate = STATIC_DIR / path
|
||||
if path and candidate.is_file():
|
||||
return FileResponse(candidate)
|
||||
index = STATIC_DIR / "index.html"
|
||||
if index.is_file():
|
||||
return FileResponse(index)
|
||||
return JSONResponse({"detail": "Not found"}, status_code=404)
|
||||
Reference in new issue
Block a user