feat: Background tasks table + tasks route migrated to Rust
- New migration: background_tasks table
- New model: BackgroundTask (background_task.rs)
- GET /api/tasks with limit param
- GET /api/tasks/{id} with 404 handling
- 18/18 tests pass
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
-- Background tasks tracking
|
||||
CREATE TABLE IF NOT EXISTS background_tasks (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
task_type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
message TEXT DEFAULT '',
|
||||
error TEXT,
|
||||
dedupe_key TEXT,
|
||||
success_title TEXT,
|
||||
failure_title TEXT,
|
||||
notify_on_success INTEGER DEFAULT true,
|
||||
notify_on_failure INTEGER DEFAULT true,
|
||||
result_json TEXT,
|
||||
progress INTEGER DEFAULT 0,
|
||||
progress_total INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
started_at TEXT,
|
||||
finished_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS background_tasks_status_idx
|
||||
ON background_tasks (status);
|
||||
@@ -0,0 +1,25 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
/// Background task for tracking async operations (deployments, syncs, etc.)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct BackgroundTask {
|
||||
pub id: String,
|
||||
pub task_type: String,
|
||||
pub title: String,
|
||||
pub status: String,
|
||||
pub message: Option<String>,
|
||||
pub error: Option<String>,
|
||||
pub dedupe_key: Option<String>,
|
||||
pub success_title: Option<String>,
|
||||
pub failure_title: Option<String>,
|
||||
pub notify_on_success: Option<bool>,
|
||||
pub notify_on_failure: Option<bool>,
|
||||
pub result_json: Option<String>,
|
||||
pub progress: Option<i64>,
|
||||
pub progress_total: Option<i64>,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
pub started_at: Option<String>,
|
||||
pub finished_at: Option<String>,
|
||||
}
|
||||
@@ -7,5 +7,6 @@ pub mod route_channel;
|
||||
pub mod setting;
|
||||
pub mod site;
|
||||
pub mod site_announcement;
|
||||
pub mod background_task;
|
||||
pub mod token_route;
|
||||
pub mod user;
|
||||
|
||||
@@ -9,6 +9,7 @@ mod events;
|
||||
mod health;
|
||||
mod proxy;
|
||||
mod site_announcements;
|
||||
mod tasks;
|
||||
mod user_api_keys;
|
||||
|
||||
pub fn build_router(state: AppState) -> Router {
|
||||
@@ -21,6 +22,7 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.merge(events::routes())
|
||||
.merge(checkin::routes())
|
||||
.merge(site_announcements::routes())
|
||||
.merge(tasks::routes())
|
||||
.merge(user_api_keys::routes())
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Background tasks routes — list and inspect async operations
|
||||
|
||||
use crate::{
|
||||
db::models::background_task::BackgroundTask,
|
||||
error::AppResult,
|
||||
AppState,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ListTasksQuery {
|
||||
limit: Option<i64>,
|
||||
}
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/api/tasks", get(list_tasks))
|
||||
.route("/api/tasks/{id}", get(get_task))
|
||||
}
|
||||
|
||||
/// GET /api/tasks — list background tasks
|
||||
async fn list_tasks(
|
||||
State(state): State<AppState>,
|
||||
Query(q): Query<ListTasksQuery>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let limit = q.limit.unwrap_or(50).max(1).min(500);
|
||||
info!("Listing background tasks, limit={}", limit);
|
||||
|
||||
let tasks: Vec<BackgroundTask> = sqlx::query_as::<_, BackgroundTask>(
|
||||
"SELECT * FROM background_tasks ORDER BY created_at DESC LIMIT ?"
|
||||
)
|
||||
.bind(limit)
|
||||
.fetch_all(&*state.db)
|
||||
.await?;
|
||||
|
||||
Ok(Json(json!({ "tasks": tasks })))
|
||||
}
|
||||
|
||||
/// GET /api/tasks/{id} — get a single background task
|
||||
async fn get_task(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
info!("Getting background task: {}", id);
|
||||
|
||||
let task: Option<BackgroundTask> = sqlx::query_as::<_, BackgroundTask>(
|
||||
"SELECT * FROM background_tasks WHERE id = ?"
|
||||
)
|
||||
.bind(&id)
|
||||
.fetch_optional(&*state.db)
|
||||
.await?;
|
||||
|
||||
match task {
|
||||
Some(t) => Ok(Json(json!({ "success": true, "task": t }))),
|
||||
None => Ok(Json(json!({ "success": false, "message": "task not found" }))),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user