From c0c0b13029af35f99c65361e97a3f8dcf6e9cc58 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 2 Jul 2026 17:54:31 +0200 Subject: [PATCH] feat(automations): add watch rest routes --- .../app/automations/api/__init__.py | 4 ++ .../app/automations/api/chat_watch.py | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 surfsense_backend/app/automations/api/chat_watch.py diff --git a/surfsense_backend/app/automations/api/__init__.py b/surfsense_backend/app/automations/api/__init__.py index a18e91a95..fa9f1ec05 100644 --- a/surfsense_backend/app/automations/api/__init__.py +++ b/surfsense_backend/app/automations/api/__init__.py @@ -5,10 +5,14 @@ from __future__ import annotations from fastapi import APIRouter from .automation import router as automation_router +from .chat_watch import router as watch_router from .run import router as run_router from .trigger import router as trigger_router router = APIRouter() +# Before automation_router so the literal ``/automations/watches`` path is not +# shadowed by the ``/automations/{automation_id}`` route. +router.include_router(watch_router) router.include_router(automation_router) router.include_router(trigger_router) router.include_router(run_router) diff --git a/surfsense_backend/app/automations/api/chat_watch.py b/surfsense_backend/app/automations/api/chat_watch.py new file mode 100644 index 000000000..1b5348f1a --- /dev/null +++ b/surfsense_backend/app/automations/api/chat_watch.py @@ -0,0 +1,50 @@ +"""HTTP routes for chat watches (automations bound to a chat thread). + +A watch is an automation whose plan re-posts a question into a chat on a +schedule. These routes let the chat UI show watch state and controls: + +* ``GET /automations/watches`` — the watches bound to a thread (is-watched + list). +* ``POST /automations/{automation_id}/run`` — run a watch now (manual refresh). + +Stopping a watch is a plain ``DELETE /automations/{automation_id}``. +""" + +from __future__ import annotations + +from fastapi import APIRouter, Depends, Query, status + +from app.automations.schemas.api import AutomationList, AutomationSummary, RunSummary +from app.automations.services import AutomationService, get_automation_service +from app.automations.services.chat_watch import find_watches_for_thread, run_watch_now + +router = APIRouter() + + +@router.get("/automations/watches", response_model=AutomationList) +async def list_watches( + workspace_id: int = Query(...), + thread_id: int = Query(...), + service: AutomationService = Depends(get_automation_service), +) -> AutomationList: + """List the watches bound to a chat thread (empty when it isn't watched).""" + watches = await find_watches_for_thread( + service, workspace_id=workspace_id, thread_id=thread_id + ) + return AutomationList( + items=[AutomationSummary.model_validate(a) for a in watches], + total=len(watches), + ) + + +@router.post( + "/automations/{automation_id}/run", + response_model=RunSummary, + status_code=status.HTTP_202_ACCEPTED, +) +async def run_watch( + automation_id: int, + service: AutomationService = Depends(get_automation_service), +) -> RunSummary: + """Enqueue an immediate run of a watch (manual refresh).""" + run = await run_watch_now(service, automation_id=automation_id) + return RunSummary.model_validate(run)