204 lines
5.8 KiB
Rust
204 lines
5.8 KiB
Rust
use axum::{
|
|
Json,
|
|
extract::{Path, State},
|
|
};
|
|
use sea_orm::{EntityTrait, QueryOrder, QuerySelect};
|
|
use std::sync::Arc;
|
|
use uuid::Uuid;
|
|
|
|
use super::AppState;
|
|
use super::auth::AuthenticatedUser;
|
|
use crate::domain::tasks::{
|
|
self, CreateTaskRequest, RecentRunResponse, TaskResponse, UpdateTaskRequest,
|
|
};
|
|
|
|
use crate::error::AppResult;
|
|
|
|
pub async fn list_tasks(
|
|
_user: AuthenticatedUser,
|
|
State(state): State<Arc<AppState>>,
|
|
) -> AppResult<Json<Vec<TaskResponse>>> {
|
|
let tasks = crate::entities::task::Entity::find()
|
|
.find_with_related(crate::entities::task_run::Entity)
|
|
.order_by_desc(crate::entities::task::Column::CreatedAt)
|
|
.all(&state.db)
|
|
.await?;
|
|
|
|
let response = tasks
|
|
.into_iter()
|
|
.map(|(t, mut runs)| {
|
|
runs.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
|
TaskResponse {
|
|
id: t.id,
|
|
goal: t.goal,
|
|
cron: t.cron,
|
|
created_at: t.created_at,
|
|
runs: runs
|
|
.into_iter()
|
|
.map(|r| tasks::TaskRunResponse {
|
|
id: r.id,
|
|
status: r.status,
|
|
logs: r.logs,
|
|
answer: r.answer,
|
|
created_at: r.created_at,
|
|
})
|
|
.collect(),
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
pub async fn create_task(
|
|
_user: AuthenticatedUser,
|
|
State(state): State<Arc<AppState>>,
|
|
Json(payload): Json<CreateTaskRequest>,
|
|
) -> AppResult<Json<TaskResponse>> {
|
|
if payload.goal.trim().is_empty() {
|
|
return Err(crate::error::AppError::InvalidRequest(
|
|
"Goal cannot be empty".into(),
|
|
));
|
|
}
|
|
|
|
if payload.goal.trim().len() < 5 {
|
|
return Err(crate::error::AppError::InvalidRequest(
|
|
"Goal is too short (min 5 characters)".into(),
|
|
));
|
|
}
|
|
|
|
let task_id = Uuid::new_v4();
|
|
tracing::info!(%task_id, goal = %payload.goal, "Creating new task");
|
|
|
|
let new_task = crate::entities::task::ActiveModel {
|
|
id: sea_orm::Set(task_id),
|
|
goal: sea_orm::Set(payload.goal),
|
|
cron: sea_orm::Set(payload.cron.clone()),
|
|
created_at: sea_orm::Set(chrono::Utc::now().into()),
|
|
};
|
|
|
|
use sea_orm::ActiveModelTrait;
|
|
new_task.insert(&state.db).await?;
|
|
|
|
if let Some(cron) = &payload.cron {
|
|
let _ = state.scheduler.add_task_job(task_id, cron).await;
|
|
}
|
|
|
|
let task_response = tasks::get_task_inner(task_id, &state.db).await?;
|
|
let _ = state
|
|
.tx
|
|
.send(crate::server::notifications::WsEvent::TaskCreated(
|
|
task_response.clone(),
|
|
));
|
|
Ok(Json(task_response))
|
|
}
|
|
|
|
pub async fn rerun_task(
|
|
_user: AuthenticatedUser,
|
|
State(state): State<Arc<AppState>>,
|
|
Path(id): Path<Uuid>,
|
|
) -> AppResult<Json<TaskResponse>> {
|
|
let task = crate::entities::task::Entity::find_by_id(id)
|
|
.one(&state.db)
|
|
.await?
|
|
.ok_or_else(|| crate::error::AppError::NotFound("Task not found".to_string()))?;
|
|
|
|
tracing::info!(task_id = %task.id, "Manually triggering task rerun");
|
|
|
|
tasks::execute_agent_run(
|
|
&state.db,
|
|
&state.scheduler,
|
|
&state.config,
|
|
task.id,
|
|
task.goal,
|
|
)
|
|
.await
|
|
.map(Json)
|
|
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
|
|
}
|
|
|
|
pub async fn update_task(
|
|
_user: AuthenticatedUser,
|
|
State(state): State<Arc<AppState>>,
|
|
Path(id): Path<Uuid>,
|
|
Json(payload): Json<UpdateTaskRequest>,
|
|
) -> AppResult<Json<TaskResponse>> {
|
|
if payload.goal.trim().is_empty() {
|
|
return Err(crate::error::AppError::InvalidRequest(
|
|
"Goal cannot be empty".into(),
|
|
));
|
|
}
|
|
|
|
if payload.goal.trim().len() < 5 {
|
|
return Err(crate::error::AppError::InvalidRequest(
|
|
"Goal is too short (min 5 characters)".into(),
|
|
));
|
|
}
|
|
|
|
let task: crate::entities::task::ActiveModel = crate::entities::task::Entity::find_by_id(id)
|
|
.one(&state.db)
|
|
.await?
|
|
.ok_or_else(|| crate::error::AppError::NotFound("Task not found".to_string()))?
|
|
.into();
|
|
|
|
let mut task = task;
|
|
tracing::info!(task_id = %id, goal = %payload.goal, "Updating task");
|
|
|
|
task.goal = sea_orm::Set(payload.goal);
|
|
task.cron = sea_orm::Set(payload.cron.clone());
|
|
|
|
use sea_orm::ActiveModelTrait;
|
|
task.update(&state.db).await?;
|
|
|
|
if let Some(cron) = &payload.cron {
|
|
let _ = state.scheduler.add_task_job(id, cron).await;
|
|
} else {
|
|
let _ = state.scheduler.remove_task_job(id).await;
|
|
}
|
|
|
|
let task_response = tasks::get_task_inner(id, &state.db).await?;
|
|
let _ = state
|
|
.tx
|
|
.send(crate::server::notifications::WsEvent::TaskUpdated(
|
|
task_response.clone(),
|
|
));
|
|
Ok(Json(task_response))
|
|
}
|
|
|
|
pub async fn get_task(
|
|
_user: AuthenticatedUser,
|
|
Path(id): Path<Uuid>,
|
|
State(state): State<Arc<AppState>>,
|
|
) -> AppResult<Json<TaskResponse>> {
|
|
tasks::get_task_inner(id, &state.db)
|
|
.await
|
|
.map(Json)
|
|
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
|
|
}
|
|
|
|
pub async fn get_recent_runs(
|
|
_user: AuthenticatedUser,
|
|
State(state): State<Arc<AppState>>,
|
|
) -> AppResult<Json<Vec<RecentRunResponse>>> {
|
|
let results = crate::entities::task_run::Entity::find()
|
|
.find_also_related(crate::entities::task::Entity)
|
|
.order_by_desc(crate::entities::task_run::Column::CreatedAt)
|
|
.limit(50)
|
|
.all(&state.db)
|
|
.await?;
|
|
|
|
let response = results
|
|
.into_iter()
|
|
.filter_map(|(run, task_opt)| {
|
|
task_opt.map(|task| RecentRunResponse {
|
|
id: run.id,
|
|
task_id: run.task_id,
|
|
goal: task.goal,
|
|
status: run.status,
|
|
created_at: run.created_at,
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
Ok(Json(response))
|
|
}
|