from __future__ import annotations from datetime import UTC, datetime from fastapi import APIRouter, Depends, HTTPException from mousehold_domain import ImportConnectionStatus, ImportConnectionType from sqlalchemy import select from sqlalchemy.orm import Session from .. import models from ..db import get_session from ..schemas import ( ImportConnectionCreate, ImportConnectionRead, ImportConnectionUpdate, LocalAgentHeartbeat, LocalAgentRegister, ) router = APIRouter(tags=["connections"]) @router.get("/connections/supported") def supported_connections() -> dict[str, object]: return { "connectors": [ { "type": ImportConnectionType.FINTS, "runs_locally": True, "status": "local-agent-ready", "notes": "PIN/TAN auth runs in the local agent; normalized observations can be synced to the API.", }, { "type": ImportConnectionType.PAYPAL_API, "runs_locally": True, "status": "experimental-off-ui", "notes": "PayPal CSV statements and email evidence are the active PoC path.", }, { "type": ImportConnectionType.IMAP, "runs_locally": True, "status": "scaffold", "notes": "Local agent can filter Klarna, PayPal, invoices, and order confirmations via IMAP.", }, { "type": ImportConnectionType.CSV_FOLDER, "runs_locally": True, "status": "ready-shape", "notes": "CSV rows can be normalized into /observations/sync payloads.", }, ] } @router.post("/connections", response_model=ImportConnectionRead) def create_connection( payload: ImportConnectionCreate, session: Session = Depends(get_session), ) -> models.ImportConnection: connection = models.ImportConnection(**payload.model_dump()) session.add(connection) session.commit() session.refresh(connection) return connection @router.get("/connections", response_model=list[ImportConnectionRead]) def list_connections( household_id: str, session: Session = Depends(get_session), ) -> list[models.ImportConnection]: return list( session.scalars( select(models.ImportConnection) .where(models.ImportConnection.household_id == household_id) .order_by(models.ImportConnection.created_at.desc()) ).all() ) @router.patch("/connections/{connection_id}", response_model=ImportConnectionRead) def update_connection( connection_id: str, payload: ImportConnectionUpdate, session: Session = Depends(get_session), ) -> models.ImportConnection: connection = session.get(models.ImportConnection, connection_id) if not connection: raise HTTPException(status_code=404, detail="Connection not found") for field, value in payload.model_dump(exclude_unset=True).items(): setattr(connection, field, value) session.add(connection) session.commit() session.refresh(connection) return connection @router.delete("/connections/{connection_id}") def delete_connection(connection_id: str, session: Session = Depends(get_session)) -> dict[str, bool]: connection = session.get(models.ImportConnection, connection_id) if not connection: raise HTTPException(status_code=404, detail="Connection not found") session.delete(connection) session.commit() return {"deleted": True} @router.post("/local-agent/register", response_model=ImportConnectionRead) def register_local_agent( payload: LocalAgentRegister, session: Session = Depends(get_session), ) -> models.ImportConnection: connection = models.ImportConnection( household_id=payload.household_id, owner_user_id=payload.owner_user_id, connection_type=ImportConnectionType.LOCAL_AGENT, display_name=payload.display_name, status=ImportConnectionStatus.ACTIVE, runs_locally=True, connector_config_json={ "capabilities": ["bank_csv", "paypal_csv", "imap", "fints_placeholder"] }, ) session.add(connection) session.commit() session.refresh(connection) return connection @router.post("/local-agent/heartbeat", response_model=ImportConnectionRead) def local_agent_heartbeat( payload: LocalAgentHeartbeat, session: Session = Depends(get_session), ) -> models.ImportConnection: connection = session.get(models.ImportConnection, payload.connection_id) if not connection: raise HTTPException(status_code=404, detail="Connection not found") connection.status = payload.status connection.last_error = payload.last_error connection.last_sync_at = datetime.now(UTC) session.add(connection) session.commit() session.refresh(connection) return connection