use std::sync::Arc;
use axum::Router;
use axum::extract::{Json, Path, State};
use axum::http::StatusCode;
use axum::routing::{get, post};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::api::{ApiError, AppState};
use crate::engine::models::Schedule;
use crate::engine::queue::JobOptions;
#[derive(Debug, Deserialize, ToSchema)]
pub struct CreateScheduleRequest {
pub name: String,
pub queue: String,
pub job_name: String,
#[serde(default)]
pub job_data: serde_json::Value,
#[serde(default)]
pub job_options: Option<JobOptions>,
pub cron_expr: Option<String>,
pub every_ms: Option<u64>,
pub timezone: Option<String>,
pub max_executions: Option<u64>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ScheduleResponse {
pub ok: bool,
pub schedule: Schedule,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ScheduleListResponse {
pub ok: bool,
pub schedules: Vec<Schedule>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct OkResponse {
pub ok: bool,
}
pub fn routes() -> Router<Arc<AppState>> {
Router::new()
.route(
"/api/v1/schedules",
post(create_schedule).get(list_schedules),
)
.route(
"/api/v1/schedules/{name}",
get(get_schedule).delete(delete_schedule),
)
.route("/api/v1/schedules/{name}/pause", post(pause_schedule))
.route("/api/v1/schedules/{name}/resume", post(resume_schedule))
}
#[utoipa::path(
post,
path = "/api/v1/schedules",
tag = "Schedules",
request_body = CreateScheduleRequest,
responses(
(status = 201, description = "Schedule created", body = ScheduleResponse),
(status = 400, description = "Validation error"),
(status = 401, description = "Unauthorized"),
)
)]
async fn create_schedule(
State(state): State<Arc<AppState>>,
Json(req): Json<CreateScheduleRequest>,
) -> Result<(StatusCode, Json<ScheduleResponse>), ApiError> {
let now = Utc::now();
let schedule = Schedule {
name: req.name,
queue: req.queue,
job_name: req.job_name,
job_data: req.job_data,
job_options: req.job_options,
cron_expr: req.cron_expr,
every_ms: req.every_ms,
timezone: req.timezone,
max_executions: req.max_executions,
execution_count: 0,
paused: false,
last_run_at: None,
next_run_at: None,
created_at: now,
updated_at: now,
};
state.queue_manager.create_schedule(&schedule).await?;
Ok((
StatusCode::CREATED,
Json(ScheduleResponse { ok: true, schedule }),
))
}
#[utoipa::path(
get,
path = "/api/v1/schedules",
tag = "Schedules",
responses(
(status = 200, description = "List of all schedules", body = ScheduleListResponse),
(status = 401, description = "Unauthorized"),
)
)]
async fn list_schedules(
State(state): State<Arc<AppState>>,
) -> Result<Json<ScheduleListResponse>, ApiError> {
let schedules = state.queue_manager.list_schedules().await?;
Ok(Json(ScheduleListResponse {
ok: true,
schedules,
}))
}
#[utoipa::path(
get,
path = "/api/v1/schedules/{name}",
tag = "Schedules",
params(("name" = String, Path, description = "Schedule name")),
responses(
(status = 200, description = "Schedule found", body = ScheduleResponse),
(status = 404, description = "Schedule not found"),
(status = 401, description = "Unauthorized"),
)
)]
async fn get_schedule(
State(state): State<Arc<AppState>>,
Path(name): Path<String>,
) -> Result<Json<ScheduleResponse>, ApiError> {
let schedule = state
.queue_manager
.get_schedule(&name)
.await?
.ok_or_else(|| {
ApiError::from(crate::engine::error::RustQueueError::ScheduleNotFound(
name.clone(),
))
})?;
Ok(Json(ScheduleResponse { ok: true, schedule }))
}
#[utoipa::path(
delete,
path = "/api/v1/schedules/{name}",
tag = "Schedules",
params(("name" = String, Path, description = "Schedule name")),
responses(
(status = 200, description = "Schedule deleted", body = OkResponse),
(status = 404, description = "Schedule not found"),
(status = 401, description = "Unauthorized"),
)
)]
async fn delete_schedule(
State(state): State<Arc<AppState>>,
Path(name): Path<String>,
) -> Result<Json<OkResponse>, ApiError> {
state.queue_manager.delete_schedule(&name).await?;
Ok(Json(OkResponse { ok: true }))
}
#[utoipa::path(
post,
path = "/api/v1/schedules/{name}/pause",
tag = "Schedules",
params(("name" = String, Path, description = "Schedule name")),
responses(
(status = 200, description = "Schedule paused", body = OkResponse),
(status = 404, description = "Schedule not found"),
(status = 401, description = "Unauthorized"),
)
)]
async fn pause_schedule(
State(state): State<Arc<AppState>>,
Path(name): Path<String>,
) -> Result<Json<OkResponse>, ApiError> {
state.queue_manager.pause_schedule(&name).await?;
Ok(Json(OkResponse { ok: true }))
}
#[utoipa::path(
post,
path = "/api/v1/schedules/{name}/resume",
tag = "Schedules",
params(("name" = String, Path, description = "Schedule name")),
responses(
(status = 200, description = "Schedule resumed", body = OkResponse),
(status = 404, description = "Schedule not found"),
(status = 401, description = "Unauthorized"),
)
)]
async fn resume_schedule(
State(state): State<Arc<AppState>>,
Path(name): Path<String>,
) -> Result<Json<OkResponse>, ApiError> {
state.queue_manager.resume_schedule(&name).await?;
Ok(Json(OkResponse { ok: true }))
}