Skip to main content

routa_server/api/
background_tasks.rs

1//! Background Tasks API - /api/background-tasks
2//!
3//! Persistent background task queue for async work (email, report generation, etc.)
4//! In the desktop backend, background tasks are handled by the embedded scheduler;
5//! this REST surface mirrors the Next.js API for frontend compatibility.
6//!
7//! GET    /api/background-tasks              - List tasks
8//! POST   /api/background-tasks              - Enqueue a new task
9//! POST   /api/background-tasks/process      - Process the next pending task
10//! GET    /api/background-tasks/{id}         - Get a task by ID
11//! PATCH  /api/background-tasks/{id}         - Update a task (PENDING only)
12//! DELETE /api/background-tasks/{id}         - Cancel / delete a task
13//! POST   /api/background-tasks/{id}/retry   - Retry a failed task
14
15use axum::{
16    extract::{Path, Query},
17    routing::get,
18    Json, Router,
19};
20use serde::Deserialize;
21
22use crate::state::AppState;
23
24pub fn router() -> Router<AppState> {
25    Router::new()
26        .route("/", get(list_tasks).post(create_task))
27        .route("/process", axum::routing::post(process_task))
28        .route(
29            "/{id}",
30            get(get_task).patch(update_task).delete(delete_task),
31        )
32        .route("/{id}/retry", axum::routing::post(retry_task))
33}
34
35#[derive(Debug, Deserialize)]
36#[serde(rename_all = "camelCase")]
37#[allow(dead_code)]
38struct ListQuery {
39    workspace_id: Option<String>,
40    status: Option<String>,
41}
42
43/// GET /api/background-tasks — List background tasks
44async fn list_tasks(Query(_q): Query<ListQuery>) -> Json<serde_json::Value> {
45    // Desktop mode: background tasks are embedded in the scheduler;
46    // return empty list for REST compatibility.
47    Json(serde_json::json!({ "tasks": [] }))
48}
49
50/// POST /api/background-tasks — Enqueue a background task
51async fn create_task(Json(body): Json<serde_json::Value>) -> Json<serde_json::Value> {
52    let id = uuid::Uuid::new_v4().to_string();
53    Json(serde_json::json!({
54        "task": {
55            "id": id,
56            "type": body.get("type").and_then(|v| v.as_str()).unwrap_or("unknown"),
57            "status": "pending",
58            "workspaceId": body.get("workspaceId").and_then(|v| v.as_str()).unwrap_or("default"),
59            "createdAt": chrono::Utc::now().to_rfc3339(),
60            "payload": body.get("payload"),
61        }
62    }))
63}
64
65/// POST /api/background-tasks/process — Process the next pending task
66async fn process_task() -> Json<serde_json::Value> {
67    Json(serde_json::json!({
68        "processed": 0,
69        "message": "No pending background tasks in desktop mode",
70    }))
71}
72
73/// GET /api/background-tasks/{id} — Get a task by ID
74async fn get_task(Path(id): Path<String>) -> Json<serde_json::Value> {
75    Json(serde_json::json!({
76        "error": format!("Background task {} not found", id),
77        "code": "NOT_FOUND"
78    }))
79}
80
81/// PATCH /api/background-tasks/{id} — Update a task (PENDING only)
82async fn update_task(
83    Path(id): Path<String>,
84    Json(_body): Json<serde_json::Value>,
85) -> Json<serde_json::Value> {
86    // Desktop mode: return error as tasks are ephemeral
87    Json(serde_json::json!({
88        "error": format!("Background task {} not found", id),
89        "code": "NOT_FOUND"
90    }))
91}
92
93/// DELETE /api/background-tasks/{id} — Cancel a task
94async fn delete_task(Path(id): Path<String>) -> Json<serde_json::Value> {
95    Json(serde_json::json!({ "deleted": true, "id": id }))
96}
97
98/// POST /api/background-tasks/{id}/retry — Retry a failed task
99async fn retry_task(Path(id): Path<String>) -> Json<serde_json::Value> {
100    Json(serde_json::json!({
101        "retried": true,
102        "id": id,
103        "message": "Retry queued (desktop mode: ephemeral)",
104    }))
105}