From ece5ae935a7a8ea171e258b8ff212ff5b146aee5 Mon Sep 17 00:00:00 2001 From: MYUU <1405758738@qq.com> Date: Tue, 2 Sep 2025 08:16:04 +0800 Subject: [PATCH 1/2] refactor(orion-server): support multiple builds per task instead of single build BREAKING CHANGE: Previously, each task was bound to a single build. Now, tasks can have multiple associated builds. This change modifies the database schema, task scheduling logic, and API contracts. Signed-off-by: MYUU <1405758738@qq.com> --- orion-server/README.md | 115 ++++++++-- orion-server/db/schema.sql | 11 +- orion-server/src/api.rs | 311 +++++++++++++++++++++----- orion-server/src/model/builds.rs | 38 ---- orion-server/src/model/mod.rs | 2 +- orion-server/src/model/tasks.rs | 69 ++++++ orion-server/src/scheduler.rs | 89 ++++---- orion-server/src/server.rs | 14 +- orion-server/tests/log_segment_api.rs | 8 +- 9 files changed, 483 insertions(+), 174 deletions(-) delete mode 100644 orion-server/src/model/builds.rs create mode 100644 orion-server/src/model/tasks.rs diff --git a/orion-server/README.md b/orion-server/README.md index ae6cf9326..381dac218 100644 --- a/orion-server/README.md +++ b/orion-server/README.md @@ -71,14 +71,42 @@ curl -X POST http://localhost:8004/task \ "mr": "123" }' ``` -#### 3. Query Task Status +#### 3. Start Build Task + +- **`POST /task-build/{id}`** + Start a build task that was previously created. + - **Request Body:** + ```json + { + "repo": "string", + "buck_hash": "string", + "buckconfig_hash": "string", + "args": ["string", ...], // optional + "mr": "string" // optional, Merge Request number + } + ``` + - **Response:** + ```json + { + "task_id": "string", + "build_id": "string", + "client_id": "string", + "status": "dispatched" + } + ``` + - **Errors:** + - `{ "message": "Invalid task ID format" }` if ID is invalid. + - `{ "message": "Task ID does not exist" }` if task not found. + - `{ "message": "No available workers at the moment" }` if no build agents are available. + +#### 4. Query Task Status - **`GET /task-status/{id}`** Query the status of a build task by its ID. - **Response:** ```json { - "status": "Building|Interrupted|Failed|Completed|NotFound", + "status": "Building|Interrupted|Failed|Completed|NotFound|Pending", "exit_code": 0, // optional "message": "string" // optional } @@ -88,23 +116,18 @@ curl -X POST http://localhost:8004/task \ - `404 Not Found` if task does not exist - `400 Bad Request` if ID is invalid -#### 4. Real-time Task Output (Logs) - -- **`GET /task-output/{id}`** - Streams real-time build logs for a task using Server-Sent Events (SSE). - - **Response:** - - SSE stream of log lines as they are produced. - - If the log file does not exist: - `data: Task output file not found` - -#### 5. Query Builds by Merge Request +#### 5. Get Task Build IDs -- **`GET /mr-task/{mr}`** - Query historical build records by Merge Request (MR) number. +- **`GET /task-build-list/{id}`** + Get a list of build IDs associated with a task. - **Response:** - - `200 OK` with a JSON array of build records if found. - - `404 Not Found` with `{ "message": "No builds found for the given MR" }` if none. - - `500 Internal Server Error` on database errors. + ```json + ["build_id1", "build_id2", ...] + ``` + - **Status Codes:** + - `200 OK` if found + - `404 Not Found` if task does not exist + - `400 Bad Request` if ID is invalid **Example:** ```bash @@ -115,7 +138,56 @@ curl -X GET http://localhost:8004/mr-task/123 - All endpoints except `/ws` are intended for HTTP clients (frontends, automation, etc.). - WebSocket clients must implement the protocol defined in `orion::ws::WSMessage` for task handling and reporting. - SSE endpoints require clients to support Server-Sent Events. -#### 6. Query Historical Task Logs +#### 6. Query Builds by Merge Request + +- **`GET /mr-task/{mr}`** + Query historical build records by Merge Request (MR) number. + - **Response:** + - `200 OK` with a JSON array of build records if found: + ```json + [ + { + "task_id": "string", + "build_ids": ["string", ...], + "output_files": ["string", ...], + "exit_code": 0, + "start_at": "2023-01-01T00:00:00Z", + "end_at": "2023-01-01T00:00:00Z", + "repo_name": "string", + "target": "string", + "arguments": "string", + "mr": "string" + } + ] + ``` + - `404 Not Found` with `{ "message": "No builds found for the given MR" }` if none. + - `500 Internal Server Error` on database errors. + +#### 7. Query Builds with Status by Merge Request + +- **`GET /tasks/{mr}`** + Query all tasks with their current status by Merge Request (MR) number. + - **Response:** + - `200 OK` with a JSON array of build records with status if found: + ```json + [ + { + "build_id": ["string", ...], + "output_files": ["string", ...], + "exit_code": 0, + "start_at": "2023-01-01T00:00:00Z", + "end_at": "2023-01-01T00:00:00Z", + "repo_name": "string", + "target": "string", + "arguments": "string", + "mr": "string", + "status": "Building|Interrupted|Failed|Completed|NotFound|Pending" + } + ] + ``` + - `500 Internal Server Error` on database errors. + +#### 8. Query Historical Task Logs - **`GET /task-history-output/{id}`** Provides the ability to read historical task logs, supporting either retrieving the **entire log at once** or **retrieving by line segments**. @@ -133,7 +205,10 @@ curl -X GET http://localhost:8004/mr-task/123 - **Responses:** - `200 OK` → Returns the log content in JSON: ```json - { "data": "log content..." } + { + "data": ["line1", "line2", "..."], + "len": 100 + } ``` - `400 Bad Request` → Invalid parameters: ```json @@ -156,7 +231,7 @@ curl -X GET http://localhost:8004/mr-task/123 ```bash curl -X GET "http://localhost:8004/task-history-output/abc123?type=segment&offset=100&limit=50" ``` -#### 7. Real-time Task Output via SSE +#### 9. Real-time Task Output via SSE - **`GET /task-output/{id}`** Streams the build output logs for a specific task in real time using Server-Sent Events (SSE). diff --git a/orion-server/db/schema.sql b/orion-server/db/schema.sql index 362293bc1..534875e01 100644 --- a/orion-server/db/schema.sql +++ b/orion-server/db/schema.sql @@ -1,7 +1,8 @@ -- Orion schema (no CREATE DATABASE here) -CREATE TABLE IF NOT EXISTS public.builds ( - build_id UUID PRIMARY KEY, - output_file TEXT NOT NULL, +CREATE TABLE IF NOT EXISTS public.tasks ( + task_id UUID PRIMARY KEY, + build_ids JSONB NOT NULL, + output_files JSONB NOT NULL, exit_code INTEGER, start_at TIMESTAMPTZ NOT NULL, end_at TIMESTAMPTZ, @@ -11,5 +12,5 @@ CREATE TABLE IF NOT EXISTS public.builds ( mr TEXT NOT NULL ); -CREATE INDEX IF NOT EXISTS idx_builds_mr ON public.builds (mr); -CREATE INDEX IF NOT EXISTS idx_builds_start_at ON public.builds (start_at); +CREATE INDEX IF NOT EXISTS idx_tasks_mr ON public.tasks (mr); +CREATE INDEX IF NOT EXISTS idx_tasks_start_at ON public.tasks (start_at); \ No newline at end of file diff --git a/orion-server/src/api.rs b/orion-server/src/api.rs index 727f86eda..96dc7b384 100644 --- a/orion-server/src/api.rs +++ b/orion-server/src/api.rs @@ -1,4 +1,4 @@ -use crate::model::builds; +use crate::model::tasks; use crate::scheduler::{ BuildInfo, BuildRequest, TaskQueueStats, TaskScheduler, WorkerInfo, WorkerStatus, create_log_file, get_build_log_dir, @@ -22,7 +22,7 @@ use sea_orm::{ ActiveModelTrait, ActiveValue::Set, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter as _, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::convert::Infallible; use std::io::Write; use std::net::SocketAddr; @@ -55,6 +55,13 @@ const DEFAULT_LOG_LIMIT: usize = 4096; /// Default log offset for segmented log retrieval const DEFAULT_LOG_OFFSET: u64 = 1; +/// Response structure for task status queries +#[derive(Debug, Clone, Deserialize, ToSchema)] +pub struct TaskRequest { + pub repo: String, + pub mr: Option, +} + /// Response structure for task status queries #[derive(Debug, Serialize, Default, ToSchema)] pub struct TaskStatus { @@ -91,7 +98,9 @@ pub fn routers() -> Router { Router::new() .route("/ws", any(ws_handler)) .route("/task", axum::routing::post(task_handler)) + .route("/task-build/{id}", axum::routing::post(task_build_handler)) .route("/task-status/{id}", get(task_status_handler)) + .route("/task-build-list/{id}", get(task_build_list_handler)) .route("/task-output/{id}", get(task_output_handler)) .route( "/task-history-output/{id}", @@ -150,7 +159,7 @@ pub async fn task_status_handler( // Check database for completed/historical tasks match Uuid::parse_str(&id) { Ok(id_uuid) => { - let output = builds::Model::get_by_build_id(id_uuid, &state.conn).await; + let output = tasks::Model::get_by_task_id(id_uuid, &state.conn).await; match output { Some(model) => { // Determine task status based on database fields @@ -202,7 +211,7 @@ pub async fn task_status_handler( get, path = "/task-output/{id}", params( - ("id" = String, Path, description = "Task ID for which to stream output logs") + ("id" = String, Path, description = "Build ID for which to stream output logs") ), responses( (status = 200, description = "Server-Sent Events stream of build output logs"), @@ -266,7 +275,7 @@ pub async fn task_output_handler( get, path = "/task-history-output/{id}", params( - ("id" = String, Path, description = "Task ID whose log to read"), + ("id" = String, Path, description = "Build ID whose log to read"), ("type" = String, Query, description = "The type of log retrieval: \"full\" indicates full retrieval, while \"segment\" indicates retrieval of segments.",example = "full"), ("offset" = Option, Query, description = "Start line number (1-based)"), ("limit" = Option, Query, description = "Max number of lines to return"), @@ -317,7 +326,14 @@ pub async fn task_history_output_handler( ); } - (StatusCode::OK, Json(serde_json::json!({ "data": buf }))) + // Split the content into lines and count them + let lines: Vec<&str> = buf.lines().collect(); + let len = lines.len(); + + (StatusCode::OK, Json(serde_json::json!({ + "data": lines, + "len": len + }))) } Some("segment") => { // Parse offset @@ -342,15 +358,14 @@ pub async fn task_history_output_handler( } }; let reader = tokio::io::BufReader::new(file); - let mut buf = String::new(); + let mut lines_vec = Vec::new(); let mut lines = reader.lines(); let mut idx = 0; let mut count = 0; while let Ok(Some(line)) = lines.next_line().await { if idx >= offset { - buf.push_str(&line); - buf.push('\n'); + lines_vec.push(line); count += 1; if count >= limit { break; @@ -358,7 +373,11 @@ pub async fn task_history_output_handler( } idx += 1; } - (StatusCode::OK, Json(serde_json::json!({ "data": buf }))) + + (StatusCode::OK, Json(serde_json::json!({ + "data": lines_vec, + "len": lines_vec.len() + }))) } _ => ( StatusCode::BAD_REQUEST, @@ -369,19 +388,45 @@ pub async fn task_history_output_handler( #[utoipa::path( post, - path = "/task", + path = "/task-build/{id}", + params( + ("id" = String, Path, description = "Task ID to get build IDs for") + ), request_body = BuildRequest, responses( - (status = 200, description = "Task created", body = serde_json::Value), + (status = 200, description = "Task start build", body = serde_json::Value), + (status = 401, description = "Not found task id", body = serde_json::Value), (status = 503, description = "Queue is full", body = serde_json::Value) - ) + ), )] -/// Creates a new build task and either assigns it immediately or queues it for later processing -/// Returns task ID and status information upon successful creation -pub async fn task_handler( +pub async fn task_build_handler( State(state): State, + Path(id): Path, Json(req): Json, ) -> impl IntoResponse { + let db = &state.conn; + + // 解析task_id + let task_id = match id.parse::() { + Ok(uuid) => uuid, + Err(_) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"message": "Invalid task ID format"})), + ) + .into_response(); + } + }; + + // 检查task_id是否存在 + if !tasks::Model::exists_by_task_id(task_id, db).await { + return ( + StatusCode::UNAUTHORIZED, + Json(serde_json::json!({"message": "Task ID does not exist"})), + ) + .into_response(); + } + // Download and get buck2 targets first // let target = match download_and_get_buck2_targets(&req.buck_hash, &req.buckconfig_hash).await { // Ok(target) => target, @@ -395,25 +440,29 @@ pub async fn task_handler( // }; // for now we do not extract from file, just use the fixed build target. let target = "//...".to_string(); - // Check if there are idle workers available if state.scheduler.has_idle_workers() { // Have idle workers, directly dispatch task (keep original logic) - handle_immediate_task_dispatch(state, req, target).await + handle_immediate_task_dispatch(state, req, target,task_id).await } else { // No idle workers, add task to queue match state .scheduler - .enqueue_task(req.clone(), target.clone()) + .enqueue_task(req.clone(), target.clone(), task_id) .await { - Ok(task_id) => { - tracing::info!("Task {} queued for later processing", task_id); + Ok( build_id) => { + tracing::info!("Build {}/{} queued for later processing", task_id, build_id); // Save to database (mark as Pending status) - let model = builds::ActiveModel { - build_id: Set(task_id), - output_file: Set(format!("{}/{}", get_build_log_dir(), task_id)), + let model = tasks::ActiveModel { + task_id: Set(task_id), + build_ids: Set(serde_json::json!([build_id])), + output_files: Set(serde_json::json!([format!( + "{}/{}", + get_build_log_dir(), + build_id + )])), exit_code: Set(None), start_at: Set(chrono::Utc::now().naive_utc()), end_at: Set(None), @@ -431,6 +480,7 @@ pub async fn task_handler( StatusCode::OK, Json(serde_json::json!({ "task_id": task_id.to_string(), + "build_id":build_id.to_string(), "status": "queued", "message": "Task queued for processing when workers become available" })), @@ -451,11 +501,69 @@ pub async fn task_handler( } } +#[utoipa::path( + post, + path = "/task", + request_body = TaskRequest, + responses( + (status = 200, description = "Task created successfully", body = serde_json::Value), + (status = 500, description = "Failed to create task", body = serde_json::Value) + ) +)] +/// Creates a new build task and either assigns it immediately or queues it for later processing +/// Returns task ID and status information upon successful creation +pub async fn task_handler( + State(state): State, + Json(req): Json, +) -> impl IntoResponse { + let task_id = Uuid::now_v7(); + let db = &state.conn; + + // Create empty JSON arrays for build_ids and output_files + let empty_json_array = serde_json::Value::Array(vec![]); + + // Insert new task into database + let task_model = tasks::ActiveModel { + task_id: Set(task_id), + build_ids: Set(empty_json_array.clone()), + output_files: Set(empty_json_array), + exit_code: Set(None), + start_at: Set(chrono::Utc::now().naive_utc()), + end_at: Set(None), + repo_name: Set(req.repo), + target: Set(String::new()), // Empty for now, may be populated from request in the future + arguments: Set(String::new()), // Empty for now, may be populated from request in the future + mr: Set(req.mr.unwrap_or_default()), + }; + + match task_model.insert(db).await { + Ok(_) => { + // Return task_id + ( + StatusCode::OK, + Json(serde_json::json!({ + "task_id": task_id.to_string() + })) + ).into_response() + } + Err(e) => { + tracing::error!("Failed to insert task into database: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({ + "message": "Failed to create task" + })) + ).into_response() + } + } +} + /// Handle immediate task dispatch logic (original task_handler logic) async fn handle_immediate_task_dispatch( state: AppState, req: BuildRequest, target: String, + task_id: Uuid ) -> axum::response::Response { // Find all idle workers let idle_workers = state.scheduler.get_idle_workers(); @@ -475,13 +583,18 @@ async fn handle_immediate_task_dispatch( rng.random_range(0..idle_workers.len()) }; let chosen_id = idle_workers[chosen_index].clone(); - let task_id = Uuid::now_v7(); + let build_id = Uuid::now_v7(); // Create log file for the task - let log_file = match create_log_file(&task_id.to_string()) { + let log_file = match create_log_file(&build_id.to_string()) { Ok(file) => Arc::new(Mutex::new(file)), Err(e) => { - tracing::error!("Failed to create log file for task {}: {}", task_id, e); + tracing::error!( + "Failed to create log file for build {}/{}: {}", + task_id, + build_id, + e + ); return ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"message": "Failed to create log file"})), @@ -500,11 +613,39 @@ async fn handle_immediate_task_dispatch( _worker_id: chosen_id.clone(), log_file, }; + + // Retrieve existing task to get current build_ids and output_files + let existing_task = match tasks::Model::get_by_task_id(task_id, &state.conn).await { + Some(task) => task, + None => { + tracing::error!("Task not found: {}", task_id); + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"message": "Task not found"})), + ) + .into_response(); + } + }; + + // Update build_ids and output_files arrays + let mut build_ids: Vec = existing_task.build_ids.as_array() + .cloned() + .unwrap_or_default(); + build_ids.push(serde_json::Value::String(build_id.to_string())); - // Save task to database - let model = builds::ActiveModel { - build_id: Set(task_id), - output_file: Set(format!("{}/{}", get_build_log_dir(), task_id)), + let mut output_files: Vec = existing_task.output_files.as_array() + .cloned() + .unwrap_or_default(); + output_files.push(serde_json::Value::String(format!( + "{}/{}", + get_build_log_dir(), + build_id + ))); + + let model = tasks::ActiveModel { + task_id: Set(task_id), + build_ids: Set(serde_json::Value::Array(build_ids)), + output_files: Set(serde_json::Value::Array(output_files)), exit_code: Set(None), start_at: Set(build_info.start_at.naive_utc()), end_at: Set(None), @@ -513,7 +654,7 @@ async fn handle_immediate_task_dispatch( arguments: Set(build_info.args.clone().unwrap_or_default().join(" ")), mr: Set(build_info.mr.clone().unwrap_or_default()), }; - if let Err(e) = model.insert(&state.conn).await { + if let Err(e) = model.update(&state.conn).await { tracing::error!("Failed to insert new build task into DB: {}", e); return ( StatusCode::INTERNAL_SERVER_ERROR, @@ -524,7 +665,7 @@ async fn handle_immediate_task_dispatch( // Create WebSocket message for the worker let msg = WSMessage::Task { - id: task_id.to_string(), + id: build_id.to_string(), repo: req.repo, target, args: req.args, @@ -534,20 +675,22 @@ async fn handle_immediate_task_dispatch( // Send task to the selected worker if let Some(mut worker) = state.scheduler.workers.get_mut(&chosen_id) { if worker.sender.send(msg).is_ok() { - worker.status = WorkerStatus::Busy(task_id.to_string()); + worker.status = WorkerStatus::Busy(build_id.to_string()); state .scheduler .active_builds - .insert(task_id.to_string(), build_info); + .insert(build_id.to_string(), build_info); tracing::info!( - "Task {} dispatched immediately to worker {}", + "Build {}/{} dispatched immediately to worker {}", task_id, + build_id, chosen_id ); ( StatusCode::OK, Json(serde_json::json!({ "task_id": task_id.to_string(), + "build_id": build_id.to_string(), "client_id": chosen_id, "status": "dispatched" })), @@ -578,6 +721,52 @@ async fn handle_immediate_task_dispatch( } } +#[utoipa::path( + get, + path = "/task-build-list/{id}", + params( + ("id" = String, Path, description = "Task ID to get build IDs for") + ), + responses( + (status = 200, description = "List of build IDs associated with the task", body = [String]), + (status = 400, description = "Invalid task ID format", body = serde_json::Value), + (status = 404, description = "Task not found", body = serde_json::Value), + (status = 500, description = "Internal server error", body = serde_json::Value) + ) +)] +pub async fn task_build_list_handler( + State(state): State, + Path(id): Path, +) -> impl IntoResponse { + let db = &state.conn; + + // 解析task_id + let task_id = match id.parse::() { + Ok(uuid) => uuid, + Err(_) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"message": "Invalid task ID format"})), + ) + .into_response(); + } + }; + + // 使用get_builds_by_task_id方法获取build_ids + match tasks::Model::get_builds_by_task_id(task_id, db).await { + Some(build_ids) => { + let build_ids_str: Vec = + build_ids.into_iter().map(|uuid| uuid.to_string()).collect(); + (StatusCode::OK, Json(build_ids_str)).into_response() + } + None => ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"message": "Task not found"})), + ) + .into_response(), + } +} + /// Handles WebSocket upgrade requests from workers /// Establishes bidirectional communication channel with worker nodes async fn ws_handler( @@ -739,13 +928,13 @@ async fn process_message( // Remove from active builds and update database state.scheduler.active_builds.remove(&id); - let _ = builds::Entity::update_many() - .set(builds::ActiveModel { + let _ = tasks::Entity::update_many() + .set(tasks::ActiveModel { exit_code: Set(exit_code), end_at: Set(Some(chrono::Utc::now().naive_utc())), ..Default::default() }) - .filter(builds::Column::BuildId.eq(id.parse::().unwrap())) + .filter(tasks::Column::TaskId.eq(id.parse::().unwrap())) .exec(&state.conn) .await; @@ -771,9 +960,10 @@ async fn process_message( /// Data transfer object for build information in API responses #[derive(Debug, Serialize, ToSchema)] -pub struct BuildDTO { - pub build_id: String, - pub output_file: String, +pub struct TaskDTO { + pub task_id: String, + pub build_ids: serde_json::Value, + pub output_files: serde_json::Value, pub exit_code: Option, pub start_at: String, pub end_at: Option, @@ -783,12 +973,13 @@ pub struct BuildDTO { pub mr: String, } -impl BuildDTO { +impl TaskDTO { /// Converts a database model to a DTO for API responses - pub fn from_model(model: builds::Model) -> Self { + pub fn from_model(model: tasks::Model) -> Self { Self { - build_id: model.build_id.to_string(), - output_file: model.output_file, + task_id: model.task_id.to_string(), + build_ids: model.build_ids, + output_files: model.output_files, exit_code: model.exit_code, start_at: DateTime::::from_naive_utc_and_offset(model.start_at, Utc).to_rfc3339(), end_at: model @@ -809,25 +1000,25 @@ impl BuildDTO { ("mr" = String, Path, description = "MR number") ), responses( - (status = 200, description = "Builds for MR", body = [BuildDTO]), + (status = 200, description = "Task for MR", body = [TaskDTO]), (status = 404, description = "No builds found for the given MR", body = serde_json::Value), (status = 500, description = "Internal server error", body = serde_json::Value) ) )] /// Retrieves all build tasks associated with a specific merge request -/// Returns a list of builds filtered by MR number +/// Returns a list of task filtered by MR number pub async fn task_query_by_mr( State(state): State, Path(mr): Path, -) -> Result>, (StatusCode, Json)> { +) -> Result>, (StatusCode, Json)> { let db = &state.conn; - match builds::Entity::find() - .filter(builds::Column::Mr.eq(mr)) + match tasks::Entity::find() + .filter(tasks::Column::Mr.eq(mr)) .all(db) .await { Ok(models) if !models.is_empty() => { - let dtos = models.into_iter().map(BuildDTO::from_model).collect(); + let dtos = models.into_iter().map(TaskDTO::from_model).collect(); Ok(Json(dtos)) } Ok(_) => Err(( @@ -847,8 +1038,8 @@ pub async fn task_query_by_mr( /// Task information including current status #[derive(Debug, Serialize, ToSchema)] pub struct TaskInfoDTO { - pub build_id: String, - pub output_file: String, + pub build_id: serde_json::Value, + pub output_files: serde_json::Value, pub exit_code: Option, pub start_at: String, pub end_at: Option, @@ -860,10 +1051,10 @@ pub struct TaskInfoDTO { } impl TaskInfoDTO { - fn from_model_with_status(model: builds::Model, status: TaskStatusEnum) -> Self { + fn from_model_with_status(model: tasks::Model, status: TaskStatusEnum) -> Self { Self { - build_id: model.build_id.to_string(), - output_file: model.output_file, + build_id: model.build_ids, + output_files: model.output_files, exit_code: model.exit_code, start_at: DateTime::::from_naive_utc_and_offset(model.start_at, Utc).to_rfc3339(), end_at: model @@ -896,8 +1087,8 @@ pub async fn tasks_handler( ) -> Result>, (StatusCode, Json)> { let db = &state.conn; let active_builds = state.scheduler.active_builds.clone(); - match builds::Entity::find() - .filter(builds::Column::Mr.eq(mr)) + match tasks::Entity::find() + .filter(tasks::Column::Mr.eq(mr)) .all(db) .await { @@ -905,7 +1096,7 @@ pub async fn tasks_handler( let tasks: Vec = models .into_iter() .map(|m| { - let id_str = m.build_id.to_string(); + let id_str = m.task_id.to_string(); let status = if active_builds.contains_key(&id_str) { TaskStatusEnum::Building } else if m.end_at.is_none() { diff --git a/orion-server/src/model/builds.rs b/orion-server/src/model/builds.rs deleted file mode 100644 index 5d553972a..000000000 --- a/orion-server/src/model/builds.rs +++ /dev/null @@ -1,38 +0,0 @@ -use sea_orm::entity::prelude::*; -use serde::Serialize; - -/// Database model for build tasks -/// Stores information about build jobs including their status, timing, and metadata -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Default)] -#[sea_orm(table_name = "builds")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub build_id: Uuid, - pub exit_code: Option, - pub start_at: DateTime, - pub end_at: Option, - pub repo_name: String, - pub target: String, - #[sea_orm(column_type = "Text")] - pub output_file: String, - #[sea_orm(column_type = "Text")] - pub arguments: String, - #[sea_orm(column_type = "Text")] - pub mr: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -impl Model { - /// Retrieves a build record by its UUID from the database - pub async fn get_by_build_id(build_id: Uuid, conn: &DatabaseConnection) -> Option { - Entity::find() - .filter(Column::BuildId.eq(build_id)) - .one(conn) - .await - .expect("Failed to get by `build_id`") - } -} diff --git a/orion-server/src/model/mod.rs b/orion-server/src/model/mod.rs index 875fab08a..f053a00b2 100644 --- a/orion-server/src/model/mod.rs +++ b/orion-server/src/model/mod.rs @@ -1 +1 @@ -pub mod builds; +pub mod tasks; diff --git a/orion-server/src/model/tasks.rs b/orion-server/src/model/tasks.rs new file mode 100644 index 000000000..d8a3acb0b --- /dev/null +++ b/orion-server/src/model/tasks.rs @@ -0,0 +1,69 @@ +use sea_orm::QuerySelect; +use sea_orm::entity::prelude::*; +use serde::Serialize; + +/// Database model for build tasks +/// Stores information about build jobs including their status, timing, and metadata +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Default)] +#[sea_orm(table_name = "tasks")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub task_id: Uuid, + #[sea_orm(column_type = "JsonBinary")] + pub build_ids: serde_json::Value, + pub exit_code: Option, + pub start_at: DateTime, + pub end_at: Option, + pub repo_name: String, + pub target: String, + #[sea_orm(column_type = "JsonBinary")] + pub output_files: serde_json::Value, + #[sea_orm(column_type = "Text")] + pub arguments: String, + #[sea_orm(column_type = "Text")] + pub mr: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +impl Model { + /// Retrieves a task record by its UUID from the database + pub async fn get_by_task_id(task_id: Uuid, conn: &DatabaseConnection) -> Option { + Entity::find() + .filter(Column::TaskId.eq(task_id)) + .one(conn) + .await + .expect("Failed to get by `task_id`") + } + + /// Retrieves build_ids list by task_id + pub async fn get_builds_by_task_id( + task_id: Uuid, + conn: &DatabaseConnection, + ) -> Option> { + let build_ids: Option = Entity::find() + .filter(Column::TaskId.eq(task_id)) + .select_only() + .column(Column::BuildIds) + .into_tuple::() + .one(conn) + .await + .expect("Failed to get `build_ids` by `task_id`"); + + build_ids + .map(|json| serde_json::from_value::>(json).unwrap_or_else(|_| Vec::new())) + } + + /// Checks if a task with the given task_id exists in the database + pub async fn exists_by_task_id(task_id: Uuid, conn: &DatabaseConnection) -> bool { + Entity::find() + .filter(Column::TaskId.eq(task_id)) + .count(conn) + .await + .expect("Failed to check if task exists") + > 0 + } +} diff --git a/orion-server/src/scheduler.rs b/orion-server/src/scheduler.rs index f500771a6..9c87005a4 100644 --- a/orion-server/src/scheduler.rs +++ b/orion-server/src/scheduler.rs @@ -1,4 +1,4 @@ -use crate::model::builds; +use crate::model::tasks; use dashmap::DashMap; use orion::ws::WSMessage; use rand::Rng; @@ -14,8 +14,8 @@ use utoipa::ToSchema; use uuid::Uuid; /// Request payload for creating a new build task -#[derive(Debug, Clone, Deserialize, ToSchema)] #[allow(dead_code)] +#[derive(Debug, Clone, Deserialize, ToSchema)] pub struct BuildRequest { pub repo: String, pub buck_hash: String, @@ -28,6 +28,7 @@ pub struct BuildRequest { #[derive(Debug, Clone)] pub struct PendingTask { pub task_id: Uuid, + pub build_id: Uuid, pub request: BuildRequest, pub target: String, pub created_at: Instant, @@ -71,7 +72,7 @@ impl TaskQueue { } } - /// Add task to the end of queue + /// Add task-bound build to the end of queue pub fn enqueue(&mut self, task: PendingTask) -> Result<(), String> { // Check if queue is full if self.queue.len() >= self.config.max_queue_size { @@ -82,12 +83,12 @@ impl TaskQueue { Ok(()) } - /// Remove task from the front of queue + /// Remove task-bound build from the front of queue pub fn dequeue(&mut self) -> Option { self.queue.pop_front() } - /// Clean up expired tasks + /// Clean up expired task-bound build pub fn cleanup_expired(&mut self) -> Vec { let now = Instant::now(); let mut expired_tasks = Vec::new(); @@ -107,7 +108,7 @@ impl TaskQueue { /// Get queue statistics pub fn get_stats(&self) -> TaskQueueStats { TaskQueueStats { - total_queued: self.queue.len(), + total_queued: self.queue.len(), oldest_task_age_seconds: self .queue .front() @@ -169,8 +170,8 @@ pub struct TaskScheduler { /// Log segment read result #[derive(Debug, Clone, Serialize, ToSchema)] pub struct LogSegment { - /// Task id / log file name - pub task_id: String, + /// build id / log file name + pub build_id: String, /// Requested starting offset pub offset: u64, /// Bytes actually read @@ -222,23 +223,25 @@ impl TaskScheduler { #[allow(dead_code)] async fn read_log_segment( &self, - task_id: &str, + build_id: &str, offset: u64, max_len: usize, ) -> Result { - read_log_segment_raw(task_id, offset, max_len).await + read_log_segment_raw(build_id, offset, max_len).await } - /// Add task to queue + /// Add task-bound build to queue pub async fn enqueue_task( &self, request: BuildRequest, target: String, + task_id: Uuid, ) -> Result { - let task_id = Uuid::now_v7(); + let build_id = Uuid::now_v7(); let pending_task = PendingTask { task_id, + build_id, request, target, created_at: Instant::now(), @@ -251,7 +254,7 @@ impl TaskScheduler { // Notify that there's a new task to process self.task_notifier.notify_one(); - Ok(task_id) + Ok(build_id) } /// Get queue statistics @@ -260,7 +263,7 @@ impl TaskScheduler { queue.get_stats() } - /// Clean up expired tasks + /// Clean up expired task-bound builds pub async fn cleanup_expired_tasks(&self) -> Vec { let mut queue = self.pending_tasks.lock().await; queue.cleanup_expired() @@ -282,7 +285,7 @@ impl TaskScheduler { .collect() } - /// Try to dispatch queued tasks (concurrent safe) + /// Try to dispatch queued task-bound builds (concurrent safe) pub async fn process_pending_tasks(&self) { // Get available workers let idle_workers = self.get_idle_workers(); @@ -342,12 +345,13 @@ impl TaskScheduler { let chosen_id = idle_workers[chosen_index].clone(); // Create log file - let log_file = match create_log_file(&pending_task.task_id.to_string()) { + let log_file = match create_log_file(&pending_task.build_id.to_string()) { Ok(file) => Arc::new(Mutex::new(file)), Err(e) => { tracing::error!( - "Failed to create log file for task {}: {}", + "Failed to create log file for task {}/{}: {}", pending_task.task_id, + pending_task.build_id, e ); return Err(format!("Failed to create log file: {e}")); @@ -366,9 +370,10 @@ impl TaskScheduler { }; // Save to database - let model = builds::ActiveModel { - build_id: Set(pending_task.task_id), - output_file: Set(format!("{}/{}", get_build_log_dir(), pending_task.task_id)), + let model = tasks::ActiveModel { + task_id: Set(pending_task.task_id), + build_ids: Set(serde_json::json!([pending_task.build_id])), + output_files: Set(serde_json::json!([format!("{}/{}", get_build_log_dir(), pending_task.build_id)])), exit_code: Set(None), start_at: Set(build_info.start_at.naive_utc()), end_at: Set(None), @@ -385,7 +390,7 @@ impl TaskScheduler { // Create WebSocket message let msg = WSMessage::Task { - id: pending_task.task_id.to_string(), + id: pending_task.build_id.to_string(), repo: pending_task.request.repo, target: pending_task.target, args: pending_task.request.args, @@ -395,12 +400,13 @@ impl TaskScheduler { // Send task to worker if let Some(mut worker) = self.workers.get_mut(&chosen_id) { if worker.sender.send(msg).is_ok() { - worker.status = WorkerStatus::Busy(pending_task.task_id.to_string()); + worker.status = WorkerStatus::Busy(pending_task.build_id.to_string()); self.active_builds - .insert(pending_task.task_id.to_string(), build_info); + .insert(pending_task.build_id.to_string(), build_info); tracing::info!( - "Queued task {} dispatched to worker {}", + "Queued task {}/{} dispatched to worker {}", pending_task.task_id, + pending_task.build_id, chosen_id ); Ok(()) @@ -460,7 +466,7 @@ impl TaskScheduler { // Log expired task information for task in expired_tasks { - tracing::debug!("Expired task: {} ({})", task.task_id, task.request.repo); + tracing::debug!("Expired build: {}/{} ({})", task.task_id,task.build_id, task.request.repo); } } } @@ -481,11 +487,11 @@ impl TaskScheduler { /// Read a segment of a task log file. /// Returns metadata and data slice (UTF-8 lossy converted). pub async fn read_log_segment_raw( - task_id: &str, + build_id: &str, offset: u64, max_len: usize, ) -> Result { - let log_path = format!("{}/{}", get_build_log_dir(), task_id); + let log_path = format!("{}/{}", get_build_log_dir(), build_id); let path = std::path::Path::new(&log_path); if !path.exists() { return Err(LogReadError::NotFound); @@ -500,7 +506,7 @@ pub async fn read_log_segment_raw( // Fast path: only metadata if max_len == 0 || offset == size { return Ok(LogSegment { - task_id: task_id.to_string(), + build_id: build_id.to_string(), offset, len: 0, data: String::new(), @@ -527,7 +533,7 @@ pub async fn read_log_segment_raw( let eof = next_offset >= size; Ok(LogSegment { - task_id: task_id.to_string(), + build_id: build_id.to_string(), offset, len: read_bytes, data, @@ -599,8 +605,8 @@ pub fn get_build_log_dir() -> &'static str { } /// Create log file -pub fn create_log_file(task_id: &str) -> Result { - let log_path = format!("{}/{}", get_build_log_dir(), task_id); +pub fn create_log_file(build_id: &str) -> Result { + let log_path = format!("{}/{}", get_build_log_dir(), build_id); let path = std::path::Path::new(&log_path); // Ensure parent directory exists @@ -629,6 +635,7 @@ mod tests { // Create test tasks let task1 = PendingTask { task_id: Uuid::now_v7(), + build_id: Uuid::now_v7(), request: BuildRequest { repo: "test1".to_string(), buck_hash: "hash1".to_string(), @@ -642,6 +649,7 @@ mod tests { let task2 = PendingTask { task_id: Uuid::now_v7(), + build_id: Uuid::now_v7(), request: BuildRequest { repo: "test2".to_string(), buck_hash: "hash2".to_string(), @@ -658,11 +666,11 @@ mod tests { assert!(queue.enqueue(task2.clone()).is_ok()); let dequeued1 = queue.dequeue().unwrap(); - assert_eq!(dequeued1.task_id, task1.task_id); + assert_eq!(dequeued1.build_id, task1.build_id); assert_eq!(dequeued1.request.repo, "test1"); let dequeued2 = queue.dequeue().unwrap(); - assert_eq!(dequeued2.task_id, task2.task_id); + assert_eq!(dequeued2.build_id, task2.build_id); assert_eq!(dequeued2.request.repo, "test2"); } @@ -678,6 +686,7 @@ mod tests { let task = PendingTask { task_id: Uuid::now_v7(), + build_id: Uuid::now_v7(), request: BuildRequest { repo: "test".to_string(), buck_hash: "hash".to_string(), @@ -704,19 +713,19 @@ mod tests { unsafe { std::env::set_var("BUILD_LOG_DIR", tmp.path().to_str().unwrap()); } - let task_id = "segment-test"; - let mut file = create_log_file(task_id).unwrap(); + let build_id = "segment-test"; + let mut file = create_log_file(build_id).unwrap(); write!(file, "Hello World! This is a test log.").unwrap(); // Read first 5 bytes - let seg = read_log_segment_raw(task_id, 0, 5).await.unwrap(); + let seg = read_log_segment_raw(build_id, 0, 5).await.unwrap(); assert_eq!(seg.offset, 0); assert_eq!(seg.len, 5); assert_eq!(seg.data, "Hello"); assert!(!seg.eof); // Read next bytes - let seg2 = read_log_segment_raw(task_id, seg.next_offset, 100) + let seg2 = read_log_segment_raw(build_id, seg.next_offset, 100) .await .unwrap(); assert!(seg2.data.starts_with(" World")); @@ -728,9 +737,9 @@ mod tests { unsafe { std::env::set_var("BUILD_LOG_DIR", tmp.path().to_str().unwrap()); } - let task_id = "segment-oob"; - let _ = create_log_file(task_id).unwrap(); - let res = read_log_segment_raw(task_id, 10, 10).await; + let build_id = "segment-oob"; + let _ = create_log_file(build_id).unwrap(); + let res = read_log_segment_raw(build_id, 10, 10).await; assert!(matches!(res, Err(LogReadError::OffsetOutOfRange { .. }))); } } diff --git a/orion-server/src/server.rs b/orion-server/src/server.rs index ae01e5608..8f204048b 100644 --- a/orion-server/src/server.rs +++ b/orion-server/src/server.rs @@ -15,13 +15,15 @@ use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; use crate::api::{self, AppState}; -use crate::model::builds; +use crate::model::tasks; /// OpenAPI documentation configuration #[derive(OpenApi)] #[openapi( paths( api::task_handler, + api::task_build_handler, api::task_status_handler, + api::task_build_list_handler, api::task_output_handler, api::task_history_output_handler, api::task_query_by_mr, @@ -33,7 +35,7 @@ use crate::model::builds; crate::scheduler::LogSegment, api::TaskStatus, api::TaskStatusEnum, - api::BuildDTO, + api::TaskDTO, api::TaskInfoDTO ) @@ -111,7 +113,7 @@ async fn setup_tables(conn: &DatabaseConnection) -> Result<(), DbErr> { let schema = Schema::new(builder); let statement = builder.build( schema - .create_table_from_entity(builds::Entity) + .create_table_from_entity(tasks::Entity) .if_not_exists(), ); trans.execute(statement).await?; @@ -166,12 +168,12 @@ async fn start_health_check_task(state: AppState) { ); state.scheduler.active_builds.remove(&task_id); - let update_res = builds::Entity::update_many() - .set(builds::ActiveModel { + let update_res = tasks::Entity::update_many() + .set(tasks::ActiveModel { end_at: Set(Some(chrono::Utc::now().naive_utc())), ..Default::default() }) - .filter(builds::Column::BuildId.eq(task_id.parse::().unwrap())) + .filter(tasks::Column::TaskId.eq(task_id.parse::().unwrap())) .exec(&state.conn) .await; diff --git a/orion-server/tests/log_segment_api.rs b/orion-server/tests/log_segment_api.rs index b5f0d48d0..f8faf945e 100644 --- a/orion-server/tests/log_segment_api.rs +++ b/orion-server/tests/log_segment_api.rs @@ -16,15 +16,15 @@ fn init_log_dir() { } } -fn write_log(task_id: &str, content: &str) { +fn write_log(build_id: &str, content: &str) { init_log_dir(); - let mut f = create_log_file(task_id).expect("create log file"); + let mut f = create_log_file(build_id).expect("create log file"); write!(f, "{content}").unwrap(); } -fn create_empty_log(task_id: &str) { +fn create_empty_log(build_id: &str) { init_log_dir(); - let _ = create_log_file(task_id).unwrap(); + let _ = create_log_file(build_id).unwrap(); } #[tokio::test] From f5634db5652e702d0c79f5da484d5887991247a4 Mon Sep 17 00:00:00 2001 From: MYUU <1405758738@qq.com> Date: Wed, 3 Sep 2025 11:54:40 +0800 Subject: [PATCH 2/2] feat(db): add migration to modify tasks table Signed-off-by: MYUU <1405758738@qq.com> --- ceres/src/pack/import_repo.rs | 3 +- jupiter/callisto/src/access_token.rs | 2 +- jupiter/callisto/src/check_result.rs | 2 +- jupiter/callisto/src/git_blob.rs | 2 +- jupiter/callisto/src/git_commit.rs | 2 +- jupiter/callisto/src/git_issue.rs | 2 +- jupiter/callisto/src/git_pr.rs | 2 +- jupiter/callisto/src/git_repo.rs | 2 +- jupiter/callisto/src/git_tag.rs | 2 +- jupiter/callisto/src/git_tree.rs | 2 +- jupiter/callisto/src/gpg_key.rs | 2 +- jupiter/callisto/src/import_refs.rs | 2 +- jupiter/callisto/src/item_assignees.rs | 2 +- jupiter/callisto/src/item_labels.rs | 2 +- jupiter/callisto/src/label.rs | 2 +- jupiter/callisto/src/lfs_locks.rs | 2 +- jupiter/callisto/src/lfs_objects.rs | 2 +- jupiter/callisto/src/lfs_split_relations.rs | 2 +- jupiter/callisto/src/mega_blob.rs | 2 +- jupiter/callisto/src/mega_commit.rs | 2 +- jupiter/callisto/src/mega_conversation.rs | 2 +- jupiter/callisto/src/mega_issue.rs | 2 +- jupiter/callisto/src/mega_mr.rs | 2 +- jupiter/callisto/src/mega_refs.rs | 2 +- jupiter/callisto/src/mega_tag.rs | 2 +- jupiter/callisto/src/mega_tree.rs | 2 +- jupiter/callisto/src/mod.rs | 4 +- jupiter/callisto/src/mq_storage.rs | 2 +- jupiter/callisto/src/notes.rs | 2 +- jupiter/callisto/src/path_check_configs.rs | 2 +- jupiter/callisto/src/prelude.rs | 4 +- jupiter/callisto/src/raw_blob.rs | 2 +- jupiter/callisto/src/reactions.rs | 2 +- jupiter/callisto/src/relay_lfs_info.rs | 2 +- jupiter/callisto/src/relay_node.rs | 2 +- jupiter/callisto/src/relay_nostr_event.rs | 2 +- jupiter/callisto/src/relay_nostr_req.rs | 2 +- jupiter/callisto/src/relay_path_mapping.rs | 2 +- jupiter/callisto/src/relay_repo_info.rs | 2 +- jupiter/callisto/src/sea_orm_active_enums.rs | 2 +- jupiter/callisto/src/ssh_keys.rs | 2 +- jupiter/callisto/src/tasks.rs | 27 ++++++ jupiter/callisto/src/user.rs | 2 +- jupiter/callisto/src/vault.rs | 2 +- .../m20250903_013904_create_task_table.rs | 89 +++++++++++++++++++ jupiter/src/migration/mod.rs | 2 + orion-server/src/api.rs | 82 +++++++++-------- orion-server/src/model/tasks.rs | 16 ++-- orion-server/src/scheduler.rs | 20 ++++- orion-server/src/server.rs | 5 +- 50 files changed, 237 insertions(+), 95 deletions(-) create mode 100644 jupiter/callisto/src/tasks.rs create mode 100644 jupiter/src/migration/m20250903_013904_create_task_table.rs diff --git a/ceres/src/pack/import_repo.rs b/ceres/src/pack/import_repo.rs index c2880f87d..228800187 100644 --- a/ceres/src/pack/import_repo.rs +++ b/ceres/src/pack/import_repo.rs @@ -39,11 +39,10 @@ pub struct ImportRepo { #[async_trait] impl RepoHandler for ImportRepo { - fn is_monorepo(&self) -> bool { false } - + async fn head_hash(&self) -> (String, Vec) { let result = self .storage diff --git a/jupiter/callisto/src/access_token.rs b/jupiter/callisto/src/access_token.rs index d97cf24ed..8c12f001c 100644 --- a/jupiter/callisto/src/access_token.rs +++ b/jupiter/callisto/src/access_token.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/check_result.rs b/jupiter/callisto/src/check_result.rs index b0d09c967..2cbe6bdfa 100644 --- a/jupiter/callisto/src/check_result.rs +++ b/jupiter/callisto/src/check_result.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use super::sea_orm_active_enums::CheckTypeEnum; use sea_orm::entity::prelude::*; diff --git a/jupiter/callisto/src/git_blob.rs b/jupiter/callisto/src/git_blob.rs index e10f4aab8..5762fbf88 100644 --- a/jupiter/callisto/src/git_blob.rs +++ b/jupiter/callisto/src/git_blob.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/git_commit.rs b/jupiter/callisto/src/git_commit.rs index f912735a9..f778b6a2a 100644 --- a/jupiter/callisto/src/git_commit.rs +++ b/jupiter/callisto/src/git_commit.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/git_issue.rs b/jupiter/callisto/src/git_issue.rs index 0ac0a2a33..eefdeb5f2 100644 --- a/jupiter/callisto/src/git_issue.rs +++ b/jupiter/callisto/src/git_issue.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/git_pr.rs b/jupiter/callisto/src/git_pr.rs index 9bfdd7065..56e63b6da 100644 --- a/jupiter/callisto/src/git_pr.rs +++ b/jupiter/callisto/src/git_pr.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/git_repo.rs b/jupiter/callisto/src/git_repo.rs index 1ddb57e81..8481e33d0 100644 --- a/jupiter/callisto/src/git_repo.rs +++ b/jupiter/callisto/src/git_repo.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/git_tag.rs b/jupiter/callisto/src/git_tag.rs index 947c78890..a67215a4e 100644 --- a/jupiter/callisto/src/git_tag.rs +++ b/jupiter/callisto/src/git_tag.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/git_tree.rs b/jupiter/callisto/src/git_tree.rs index 63a5b0558..5a546adf0 100644 --- a/jupiter/callisto/src/git_tree.rs +++ b/jupiter/callisto/src/git_tree.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/gpg_key.rs b/jupiter/callisto/src/gpg_key.rs index daab0a110..5bf9a96e5 100644 --- a/jupiter/callisto/src/gpg_key.rs +++ b/jupiter/callisto/src/gpg_key.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/import_refs.rs b/jupiter/callisto/src/import_refs.rs index e203d25d5..7a6f78b6f 100644 --- a/jupiter/callisto/src/import_refs.rs +++ b/jupiter/callisto/src/import_refs.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use super::sea_orm_active_enums::RefTypeEnum; use sea_orm::entity::prelude::*; diff --git a/jupiter/callisto/src/item_assignees.rs b/jupiter/callisto/src/item_assignees.rs index 851c62641..21d453c0c 100644 --- a/jupiter/callisto/src/item_assignees.rs +++ b/jupiter/callisto/src/item_assignees.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/item_labels.rs b/jupiter/callisto/src/item_labels.rs index 93fb1d655..5dc3eafe7 100644 --- a/jupiter/callisto/src/item_labels.rs +++ b/jupiter/callisto/src/item_labels.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/label.rs b/jupiter/callisto/src/label.rs index 49e120c3f..83c3fad71 100644 --- a/jupiter/callisto/src/label.rs +++ b/jupiter/callisto/src/label.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/lfs_locks.rs b/jupiter/callisto/src/lfs_locks.rs index 036dbbcc3..070effe84 100644 --- a/jupiter/callisto/src/lfs_locks.rs +++ b/jupiter/callisto/src/lfs_locks.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/lfs_objects.rs b/jupiter/callisto/src/lfs_objects.rs index cc32c282f..2bcf644fd 100644 --- a/jupiter/callisto/src/lfs_objects.rs +++ b/jupiter/callisto/src/lfs_objects.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/lfs_split_relations.rs b/jupiter/callisto/src/lfs_split_relations.rs index a8d1183a1..139fb9a87 100644 --- a/jupiter/callisto/src/lfs_split_relations.rs +++ b/jupiter/callisto/src/lfs_split_relations.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/mega_blob.rs b/jupiter/callisto/src/mega_blob.rs index 670a2244f..d6640817f 100644 --- a/jupiter/callisto/src/mega_blob.rs +++ b/jupiter/callisto/src/mega_blob.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/mega_commit.rs b/jupiter/callisto/src/mega_commit.rs index b1b2eb6f4..0be7fc96b 100644 --- a/jupiter/callisto/src/mega_commit.rs +++ b/jupiter/callisto/src/mega_commit.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/mega_conversation.rs b/jupiter/callisto/src/mega_conversation.rs index ecd6e8921..7609ed66e 100644 --- a/jupiter/callisto/src/mega_conversation.rs +++ b/jupiter/callisto/src/mega_conversation.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use super::sea_orm_active_enums::ConvTypeEnum; use sea_orm::entity::prelude::*; diff --git a/jupiter/callisto/src/mega_issue.rs b/jupiter/callisto/src/mega_issue.rs index 6f87173e7..a9a0ed892 100644 --- a/jupiter/callisto/src/mega_issue.rs +++ b/jupiter/callisto/src/mega_issue.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/mega_mr.rs b/jupiter/callisto/src/mega_mr.rs index bb4bd5580..baaccbd7f 100644 --- a/jupiter/callisto/src/mega_mr.rs +++ b/jupiter/callisto/src/mega_mr.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use super::sea_orm_active_enums::MergeStatusEnum; use sea_orm::entity::prelude::*; diff --git a/jupiter/callisto/src/mega_refs.rs b/jupiter/callisto/src/mega_refs.rs index 689311da3..a05741424 100644 --- a/jupiter/callisto/src/mega_refs.rs +++ b/jupiter/callisto/src/mega_refs.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/mega_tag.rs b/jupiter/callisto/src/mega_tag.rs index 5aae18742..df3084542 100644 --- a/jupiter/callisto/src/mega_tag.rs +++ b/jupiter/callisto/src/mega_tag.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/mega_tree.rs b/jupiter/callisto/src/mega_tree.rs index 299ad7c74..6705d391f 100644 --- a/jupiter/callisto/src/mega_tree.rs +++ b/jupiter/callisto/src/mega_tree.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/mod.rs b/jupiter/callisto/src/mod.rs index e70edbe81..671f46c57 100644 --- a/jupiter/callisto/src/mod.rs +++ b/jupiter/callisto/src/mod.rs @@ -1,9 +1,8 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 pub mod prelude; pub mod access_token; -pub mod builds; pub mod check_result; pub mod entity_ext; pub mod git_blob; @@ -42,5 +41,6 @@ pub mod relay_path_mapping; pub mod relay_repo_info; pub mod sea_orm_active_enums; pub mod ssh_keys; +pub mod tasks; pub mod user; pub mod vault; diff --git a/jupiter/callisto/src/mq_storage.rs b/jupiter/callisto/src/mq_storage.rs index 5265533f6..1b87d5cf7 100644 --- a/jupiter/callisto/src/mq_storage.rs +++ b/jupiter/callisto/src/mq_storage.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/notes.rs b/jupiter/callisto/src/notes.rs index 6ac2f5e2c..9eece1cbb 100644 --- a/jupiter/callisto/src/notes.rs +++ b/jupiter/callisto/src/notes.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/path_check_configs.rs b/jupiter/callisto/src/path_check_configs.rs index 82bade468..c823573e2 100644 --- a/jupiter/callisto/src/path_check_configs.rs +++ b/jupiter/callisto/src/path_check_configs.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use super::sea_orm_active_enums::CheckTypeEnum; use sea_orm::entity::prelude::*; diff --git a/jupiter/callisto/src/prelude.rs b/jupiter/callisto/src/prelude.rs index e25d12b44..7994f8b21 100644 --- a/jupiter/callisto/src/prelude.rs +++ b/jupiter/callisto/src/prelude.rs @@ -1,7 +1,6 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 pub use super::access_token::Entity as AccessToken; -pub use super::builds::Entity as Builds; pub use super::check_result::Entity as CheckResult; pub use super::git_blob::Entity as GitBlob; pub use super::git_commit::Entity as GitCommit; @@ -38,5 +37,6 @@ pub use super::relay_nostr_req::Entity as RelayNostrReq; pub use super::relay_path_mapping::Entity as RelayPathMapping; pub use super::relay_repo_info::Entity as RelayRepoInfo; pub use super::ssh_keys::Entity as SshKeys; +pub use super::tasks::Entity as Tasks; pub use super::user::Entity as User; pub use super::vault::Entity as Vault; diff --git a/jupiter/callisto/src/raw_blob.rs b/jupiter/callisto/src/raw_blob.rs index d682f6b70..038d482de 100644 --- a/jupiter/callisto/src/raw_blob.rs +++ b/jupiter/callisto/src/raw_blob.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use super::sea_orm_active_enums::StorageTypeEnum; use sea_orm::entity::prelude::*; diff --git a/jupiter/callisto/src/reactions.rs b/jupiter/callisto/src/reactions.rs index 88c8d5ab8..bc4a9b0b7 100644 --- a/jupiter/callisto/src/reactions.rs +++ b/jupiter/callisto/src/reactions.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/relay_lfs_info.rs b/jupiter/callisto/src/relay_lfs_info.rs index 65257708f..ff84a0bd1 100644 --- a/jupiter/callisto/src/relay_lfs_info.rs +++ b/jupiter/callisto/src/relay_lfs_info.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/relay_node.rs b/jupiter/callisto/src/relay_node.rs index 518bc467a..d6885fc30 100644 --- a/jupiter/callisto/src/relay_node.rs +++ b/jupiter/callisto/src/relay_node.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/relay_nostr_event.rs b/jupiter/callisto/src/relay_nostr_event.rs index 3046b592c..10114ec2f 100644 --- a/jupiter/callisto/src/relay_nostr_event.rs +++ b/jupiter/callisto/src/relay_nostr_event.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/relay_nostr_req.rs b/jupiter/callisto/src/relay_nostr_req.rs index e17d3b1d7..c9c7df59a 100644 --- a/jupiter/callisto/src/relay_nostr_req.rs +++ b/jupiter/callisto/src/relay_nostr_req.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/relay_path_mapping.rs b/jupiter/callisto/src/relay_path_mapping.rs index 1358e528a..1f0695bc4 100644 --- a/jupiter/callisto/src/relay_path_mapping.rs +++ b/jupiter/callisto/src/relay_path_mapping.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/relay_repo_info.rs b/jupiter/callisto/src/relay_repo_info.rs index 954d9e659..3d9d24768 100644 --- a/jupiter/callisto/src/relay_repo_info.rs +++ b/jupiter/callisto/src/relay_repo_info.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/sea_orm_active_enums.rs b/jupiter/callisto/src/sea_orm_active_enums.rs index aae4c4142..81d65f28c 100644 --- a/jupiter/callisto/src/sea_orm_active_enums.rs +++ b/jupiter/callisto/src/sea_orm_active_enums.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/ssh_keys.rs b/jupiter/callisto/src/ssh_keys.rs index c4d75bc9c..2b1d9472b 100644 --- a/jupiter/callisto/src/ssh_keys.rs +++ b/jupiter/callisto/src/ssh_keys.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/tasks.rs b/jupiter/callisto/src/tasks.rs new file mode 100644 index 000000000..238b195e0 --- /dev/null +++ b/jupiter/callisto/src/tasks.rs @@ -0,0 +1,27 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "tasks")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub task_id: Uuid, + #[sea_orm(column_type = "JsonBinary")] + pub build_ids: Json, + #[sea_orm(column_type = "JsonBinary")] + pub output_files: Json, + pub exit_code: Option, + pub start_at: DateTimeWithTimeZone, + pub end_at: Option, + pub repo_name: String, + pub target: String, + pub arguments: String, + pub mr: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/jupiter/callisto/src/user.rs b/jupiter/callisto/src/user.rs index 2f5241f75..c5024dd0d 100644 --- a/jupiter/callisto/src/user.rs +++ b/jupiter/callisto/src/user.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/callisto/src/vault.rs b/jupiter/callisto/src/vault.rs index 7a2379824..18c0a4366 100644 --- a/jupiter/callisto/src/vault.rs +++ b/jupiter/callisto/src/vault.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.14 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.15 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/jupiter/src/migration/m20250903_013904_create_task_table.rs b/jupiter/src/migration/m20250903_013904_create_task_table.rs new file mode 100644 index 000000000..23582c8b7 --- /dev/null +++ b/jupiter/src/migration/m20250903_013904_create_task_table.rs @@ -0,0 +1,89 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Builds::Table).to_owned()) + .await?; + + manager + .create_table( + Table::create() + .table(Tasks::Table) + .if_not_exists() + .col( + ColumnDef::new(Tasks::TaskId) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Tasks::BuildIds).json_binary().not_null()) + .col(ColumnDef::new(Tasks::OutputFiles).json_binary().not_null()) + .col(ColumnDef::new(Tasks::ExitCode).integer()) + .col( + ColumnDef::new(Tasks::StartAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col(ColumnDef::new(Tasks::EndAt).timestamp_with_time_zone()) + .col(ColumnDef::new(Tasks::RepoName).string().not_null()) + .col(ColumnDef::new(Tasks::Target).string().not_null()) + .col(ColumnDef::new(Tasks::Arguments).string().not_null()) + .col(ColumnDef::new(Tasks::Mr).string().not_null()) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_tasks_mr") + .table(Tasks::Table) + .col(Tasks::Mr) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_tasks_start_at") + .table(Tasks::Table) + .col(Tasks::StartAt) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} + +#[derive(DeriveIden)] +enum Builds { + Table, +} + +#[derive(DeriveIden)] +enum Tasks { + Table, + TaskId, + BuildIds, + OutputFiles, + ExitCode, + StartAt, + EndAt, + RepoName, + Target, + Arguments, + Mr, +} diff --git a/jupiter/src/migration/mod.rs b/jupiter/src/migration/mod.rs index f727bd1ce..ec067e545 100644 --- a/jupiter/src/migration/mod.rs +++ b/jupiter/src/migration/mod.rs @@ -53,6 +53,7 @@ mod m20250820_102133_gpgkey; mod m20250821_083749_add_checks; mod m20250828_092459_remove_gpg_table; mod m20250828_092729_create_standalone_table; +mod m20250903_013904_create_task_table; /// Creates a primary key column definition with big integer type. /// /// # Arguments @@ -93,6 +94,7 @@ impl MigratorTrait for Migrator { Box::new(m20250821_083749_add_checks::Migration), Box::new(m20250828_092459_remove_gpg_table::Migration), Box::new(m20250828_092729_create_standalone_table::Migration), + Box::new(m20250903_013904_create_task_table::Migration), ] } } diff --git a/orion-server/src/api.rs b/orion-server/src/api.rs index 96dc7b384..a8de222b4 100644 --- a/orion-server/src/api.rs +++ b/orion-server/src/api.rs @@ -13,7 +13,7 @@ use axum::{ response::{IntoResponse, Sse, sse::Event, sse::KeepAlive}, routing::{any, get}, }; -use chrono::{DateTime, Utc}; +use chrono::{FixedOffset, Utc}; use dashmap::DashMap; use futures_util::{SinkExt, Stream, StreamExt}; use orion::ws::WSMessage; @@ -330,10 +330,13 @@ pub async fn task_history_output_handler( let lines: Vec<&str> = buf.lines().collect(); let len = lines.len(); - (StatusCode::OK, Json(serde_json::json!({ - "data": lines, - "len": len - }))) + ( + StatusCode::OK, + Json(serde_json::json!({ + "data": lines, + "len": len + })), + ) } Some("segment") => { // Parse offset @@ -373,11 +376,14 @@ pub async fn task_history_output_handler( } idx += 1; } - - (StatusCode::OK, Json(serde_json::json!({ - "data": lines_vec, - "len": lines_vec.len() - }))) + + ( + StatusCode::OK, + Json(serde_json::json!({ + "data": lines_vec, + "len": lines_vec.len() + })), + ) } _ => ( StatusCode::BAD_REQUEST, @@ -443,7 +449,7 @@ pub async fn task_build_handler( // Check if there are idle workers available if state.scheduler.has_idle_workers() { // Have idle workers, directly dispatch task (keep original logic) - handle_immediate_task_dispatch(state, req, target,task_id).await + handle_immediate_task_dispatch(state, req, target, task_id).await } else { // No idle workers, add task to queue match state @@ -451,7 +457,7 @@ pub async fn task_build_handler( .enqueue_task(req.clone(), target.clone(), task_id) .await { - Ok( build_id) => { + Ok(build_id) => { tracing::info!("Build {}/{} queued for later processing", task_id, build_id); // Save to database (mark as Pending status) @@ -464,7 +470,7 @@ pub async fn task_build_handler( build_id )])), exit_code: Set(None), - start_at: Set(chrono::Utc::now().naive_utc()), + start_at: Set(Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap())), end_at: Set(None), repo_name: Set(req.repo.clone()), target: Set(target.clone()), @@ -518,24 +524,24 @@ pub async fn task_handler( ) -> impl IntoResponse { let task_id = Uuid::now_v7(); let db = &state.conn; - + // Create empty JSON arrays for build_ids and output_files let empty_json_array = serde_json::Value::Array(vec![]); - + // Insert new task into database let task_model = tasks::ActiveModel { task_id: Set(task_id), build_ids: Set(empty_json_array.clone()), output_files: Set(empty_json_array), exit_code: Set(None), - start_at: Set(chrono::Utc::now().naive_utc()), + start_at: Set(Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap())), end_at: Set(None), repo_name: Set(req.repo), target: Set(String::new()), // Empty for now, may be populated from request in the future arguments: Set(String::new()), // Empty for now, may be populated from request in the future mr: Set(req.mr.unwrap_or_default()), }; - + match task_model.insert(db).await { Ok(_) => { // Return task_id @@ -543,8 +549,9 @@ pub async fn task_handler( StatusCode::OK, Json(serde_json::json!({ "task_id": task_id.to_string() - })) - ).into_response() + })), + ) + .into_response() } Err(e) => { tracing::error!("Failed to insert task into database: {}", e); @@ -552,8 +559,9 @@ pub async fn task_handler( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "message": "Failed to create task" - })) - ).into_response() + })), + ) + .into_response() } } } @@ -563,7 +571,7 @@ async fn handle_immediate_task_dispatch( state: AppState, req: BuildRequest, target: String, - task_id: Uuid + task_id: Uuid, ) -> axum::response::Response { // Find all idle workers let idle_workers = state.scheduler.get_idle_workers(); @@ -613,7 +621,7 @@ async fn handle_immediate_task_dispatch( _worker_id: chosen_id.clone(), log_file, }; - + // Retrieve existing task to get current build_ids and output_files let existing_task = match tasks::Model::get_by_task_id(task_id, &state.conn).await { Some(task) => task, @@ -628,12 +636,16 @@ async fn handle_immediate_task_dispatch( }; // Update build_ids and output_files arrays - let mut build_ids: Vec = existing_task.build_ids.as_array() + let mut build_ids: Vec = existing_task + .build_ids + .as_array() .cloned() .unwrap_or_default(); build_ids.push(serde_json::Value::String(build_id.to_string())); - let mut output_files: Vec = existing_task.output_files.as_array() + let mut output_files: Vec = existing_task + .output_files + .as_array() .cloned() .unwrap_or_default(); output_files.push(serde_json::Value::String(format!( @@ -647,7 +659,9 @@ async fn handle_immediate_task_dispatch( build_ids: Set(serde_json::Value::Array(build_ids)), output_files: Set(serde_json::Value::Array(output_files)), exit_code: Set(None), - start_at: Set(build_info.start_at.naive_utc()), + start_at: Set(build_info + .start_at + .with_timezone(&FixedOffset::east_opt(0).unwrap())), end_at: Set(None), repo_name: Set(build_info.repo.clone()), target: Set(build_info.target.clone()), @@ -931,7 +945,9 @@ async fn process_message( let _ = tasks::Entity::update_many() .set(tasks::ActiveModel { exit_code: Set(exit_code), - end_at: Set(Some(chrono::Utc::now().naive_utc())), + end_at: Set(Some( + Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap()), + )), ..Default::default() }) .filter(tasks::Column::TaskId.eq(id.parse::().unwrap())) @@ -981,10 +997,8 @@ impl TaskDTO { build_ids: model.build_ids, output_files: model.output_files, exit_code: model.exit_code, - start_at: DateTime::::from_naive_utc_and_offset(model.start_at, Utc).to_rfc3339(), - end_at: model - .end_at - .map(|dt| DateTime::::from_naive_utc_and_offset(dt, Utc).to_rfc3339()), + start_at: model.start_at.with_timezone(&Utc).to_rfc3339(), + end_at: model.end_at.map(|dt| dt.with_timezone(&Utc).to_rfc3339()), repo_name: model.repo_name, target: model.target, arguments: model.arguments, @@ -1056,10 +1070,8 @@ impl TaskInfoDTO { build_id: model.build_ids, output_files: model.output_files, exit_code: model.exit_code, - start_at: DateTime::::from_naive_utc_and_offset(model.start_at, Utc).to_rfc3339(), - end_at: model - .end_at - .map(|dt| DateTime::::from_naive_utc_and_offset(dt, Utc).to_rfc3339()), + start_at: model.start_at.with_timezone(&Utc).to_rfc3339(), + end_at: model.end_at.map(|dt| dt.with_timezone(&Utc).to_rfc3339()), repo_name: model.repo_name, target: model.target, arguments: model.arguments, diff --git a/orion-server/src/model/tasks.rs b/orion-server/src/model/tasks.rs index d8a3acb0b..5b54cbb0c 100644 --- a/orion-server/src/model/tasks.rs +++ b/orion-server/src/model/tasks.rs @@ -1,26 +1,24 @@ use sea_orm::QuerySelect; use sea_orm::entity::prelude::*; -use serde::Serialize; +use serde::{Deserialize, Serialize}; /// Database model for build tasks /// Stores information about build jobs including their status, timing, and metadata -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Default)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "tasks")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub task_id: Uuid, #[sea_orm(column_type = "JsonBinary")] - pub build_ids: serde_json::Value, + pub build_ids: Json, + #[sea_orm(column_type = "JsonBinary")] + pub output_files: Json, pub exit_code: Option, - pub start_at: DateTime, - pub end_at: Option, + pub start_at: DateTimeWithTimeZone, + pub end_at: Option, pub repo_name: String, pub target: String, - #[sea_orm(column_type = "JsonBinary")] - pub output_files: serde_json::Value, - #[sea_orm(column_type = "Text")] pub arguments: String, - #[sea_orm(column_type = "Text")] pub mr: String, } diff --git a/orion-server/src/scheduler.rs b/orion-server/src/scheduler.rs index 9c87005a4..029485877 100644 --- a/orion-server/src/scheduler.rs +++ b/orion-server/src/scheduler.rs @@ -1,4 +1,5 @@ use crate::model::tasks; +use chrono::FixedOffset; use dashmap::DashMap; use orion::ws::WSMessage; use rand::Rng; @@ -108,7 +109,7 @@ impl TaskQueue { /// Get queue statistics pub fn get_stats(&self) -> TaskQueueStats { TaskQueueStats { - total_queued: self.queue.len(), + total_queued: self.queue.len(), oldest_task_age_seconds: self .queue .front() @@ -373,9 +374,15 @@ impl TaskScheduler { let model = tasks::ActiveModel { task_id: Set(pending_task.task_id), build_ids: Set(serde_json::json!([pending_task.build_id])), - output_files: Set(serde_json::json!([format!("{}/{}", get_build_log_dir(), pending_task.build_id)])), + output_files: Set(serde_json::json!([format!( + "{}/{}", + get_build_log_dir(), + pending_task.build_id + )])), exit_code: Set(None), - start_at: Set(build_info.start_at.naive_utc()), + start_at: Set(build_info + .start_at + .with_timezone(&FixedOffset::east_opt(0).unwrap())), end_at: Set(None), repo_name: Set(build_info.repo.clone()), target: Set(build_info.target.clone()), @@ -466,7 +473,12 @@ impl TaskScheduler { // Log expired task information for task in expired_tasks { - tracing::debug!("Expired build: {}/{} ({})", task.task_id,task.build_id, task.request.repo); + tracing::debug!( + "Expired build: {}/{} ({})", + task.task_id, + task.build_id, + task.request.repo + ); } } } diff --git a/orion-server/src/server.rs b/orion-server/src/server.rs index 8f204048b..f519a00c2 100644 --- a/orion-server/src/server.rs +++ b/orion-server/src/server.rs @@ -3,6 +3,7 @@ use std::time::Duration; use axum::Router; use axum::routing::get; +use chrono::{FixedOffset, Utc}; use http::{HeaderValue, Method}; use sea_orm::{ ActiveValue::Set, ColumnTrait, ConnectionTrait, Database, DatabaseConnection, DbErr, @@ -170,7 +171,9 @@ async fn start_health_check_task(state: AppState) { let update_res = tasks::Entity::update_many() .set(tasks::ActiveModel { - end_at: Set(Some(chrono::Utc::now().naive_utc())), + end_at: Set(Some( + Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap()), + )), ..Default::default() }) .filter(tasks::Column::TaskId.eq(task_id.parse::().unwrap()))