from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from starlette.middleware.sessions import SessionMiddleware from app.config import Settings, get_settings from app.db.session import create_db_engine, create_session_factory, initialize_schema from app.fit.rewriter import convert_fit_device from app.garmin.uploader import GarminUploader from app.mywhoosh.client import MyWhooshClient from app.notifications.emailer import EmailNotifier from app.security.credentials import CredentialCipher from app.sync.manager import SyncManager from app.sync.scheduler import SyncScheduler from app.web.account import router as account_router from app.web.operations import router as operations_router from app.web.routes import router as web_router def create_app(settings: Settings | None = None) -> FastAPI: resolved = settings or get_settings() resolved.data_dir.mkdir(parents=True, exist_ok=True) resolved.tokens_dir.mkdir(parents=True, exist_ok=True) resolved.activities_dir.mkdir(parents=True, exist_ok=True) @asynccontextmanager async def lifespan(app: FastAPI): cipher = CredentialCipher(resolved.credential_encryption_key) def mywhoosh_factory(token_store): return MyWhooshClient(token_store) def garmin_factory(email, password, tokenstore): return GarminUploader(email=email, password=password, tokenstore=tokenstore) notifier = EmailNotifier( host=resolved.smtp_host, port=resolved.smtp_port, username=resolved.smtp_username, password=resolved.smtp_password, from_address=resolved.smtp_from_address, use_tls=resolved.smtp_use_tls, ) sync_manager = SyncManager( session_factory=app.state.session_factory, credential_cipher=cipher, settings=resolved, mywhoosh_factory=mywhoosh_factory, garmin_factory=garmin_factory, fit_converter=convert_fit_device, notifier=notifier, ) app.state.sync_manager = sync_manager scheduler = SyncScheduler(sync_manager, interval_seconds=resolved.sync_interval_minutes * 60) app.state.scheduler = scheduler await scheduler.start() yield await scheduler.stop() app = FastAPI(title="MyWhoosh Garmin Sync", lifespan=lifespan) app.state.settings = resolved engine = create_db_engine(resolved.database_url) initialize_schema(engine) app.state.db_engine = engine app.state.session_factory = create_session_factory(engine) app.add_middleware( SessionMiddleware, secret_key=resolved.secret_key, same_site="lax", https_only=resolved.session_https_only, ) app.include_router(web_router) app.include_router(operations_router) app.include_router(account_router) app.mount( "/static", StaticFiles(directory=str(Path(__file__).resolve().parent / "web" / "static")), name="static", ) @app.get("/healthz") def healthz() -> dict[str, str]: return {"status": "ok"} return app