"""Authenticated, secret-free status of configured integrations and cached data.""" import asyncio import os from datetime import datetime, timezone from quart import Blueprint, jsonify from quart_jwt_extended import get_jwt_identity from blueprints.users.decorators import jwt_access_required from blueprints.users.models import User from blueprints.ynab.sync import _current_state, ynab_configured integrations_blueprint = Blueprint( "integrations_api", __name__, url_prefix="/api/integrations" ) def configuration_status(name: str, configured: bool) -> dict: return { "name": name, "status": "configured" if configured else "not_configured", "last_success_at": None, "message": "Configured; connection has not been checked." if configured else "Not configured.", } async def _ynab_status() -> dict: status = configuration_status("YNAB", ynab_configured()) if not ynab_configured(): return status try: state = await _current_state() if state is None or state.last_synced_at is None: return { **status, "status": "unavailable", "message": "No synchronized budget data is available.", } stamp = state.last_synced_at if stamp.tzinfo is None: stamp = stamp.replace(tzinfo=timezone.utc) age = max(0, (datetime.now(timezone.utc) - stamp).total_seconds()) stale = age > 120 or bool(state.last_error) return { **status, "status": "stale" if stale else "ready", "last_success_at": stamp.isoformat(), "message": "Using cached budget data; synchronization is overdue or failed." if stale else "Budget data is current.", } except Exception: return { **status, "status": "unavailable", "message": "Budget synchronization status is unavailable.", } async def integration_statuses(*, is_admin: bool) -> list[dict]: result = [await _ynab_status()] notes = configuration_status("Document search", bool(os.getenv("DATABASE_URL"))) try: from blueprints.rag.logic import get_vector_store_stats stats = await asyncio.to_thread(get_vector_store_stats) count = stats.get("total_documents", 0) notes.update( status="ready" if count else "unavailable", indexed_chunks=count, message=f"{count} indexed document chunks available." if count else "No documents are indexed yet.", ) except Exception: notes.update( status="unavailable", message="Document index status is unavailable." ) result.append(notes) for name, keys in [ ("Mealie", ("MEALIE_BASE_URL", "MEALIE_API_TOKEN")), ("Obsidian", ("OBSIDIAN_VAULT_PATH",)), ("Web search", ("TAVILY_API_KEY",)), ("Local model", ("LLAMA_SERVER_URL",)), ("Fallback model", ("OPENAI_API_KEY",)), ("SimbaKit", ("SIMBAKIT_URL",)), ]: result.append(configuration_status(name, all(os.getenv(k) for k in keys))) if is_admin: for name, keys in [ ("Homebox", ("HOMEBOX_URL", "HOMEBOX_TOKEN")), ( "BookStack", ("BOOKSTACK_BASE_URL", "BOOKSTACK_TOKEN_ID", "BOOKSTACK_TOKEN_SECRET"), ), ("Google", ("GOOGLE_TOKEN_FILE",)), ("iMessage", ("SENDBLUE_API_KEY", "SENDBLUE_API_SECRET")), ("Email", ("MAILGUN_API_KEY", "MAILGUN_DOMAIN")), ]: result.append(configuration_status(name, all(os.getenv(k) for k in keys))) return result @integrations_blueprint.get("/") @jwt_access_required async def status(): user = await User.get(id=get_jwt_identity()) return jsonify( { "checked_at": datetime.now(timezone.utc).isoformat(), "integrations": await integration_statuses(is_admin=user.is_admin()), } )