diff --git a/crates/common/src/bootstrap.rs b/crates/common/src/bootstrap.rs index c49e92f..c092f84 100644 --- a/crates/common/src/bootstrap.rs +++ b/crates/common/src/bootstrap.rs @@ -36,7 +36,12 @@ fn expand_path(path: &str) -> String { if let Some(end) = result[start..].find('}') { let var_name = &result[start + 2..start + end]; let replacement = std::env::var(var_name).unwrap_or_default(); - result = format!("{}{}{}", &result[..start], replacement, &result[start + end + 1..]); + result = format!( + "{}{}{}", + &result[..start], + replacement, + &result[start + end + 1..] + ); } else { break; } @@ -112,22 +117,24 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { for decl_jobset in &decl_project.jobsets { // Parse state string to JobsetState enum - let state = decl_jobset.state.as_ref().map(|s| match s.as_str() { - "disabled" => JobsetState::Disabled, - "enabled" => JobsetState::Enabled, - "one_shot" => JobsetState::OneShot, - "one_at_a_time" => JobsetState::OneAtATime, - _ => JobsetState::Enabled, // Default to enabled for unknown values + let state = decl_jobset.state.as_ref().map(|s| { + match s.as_str() { + "disabled" => JobsetState::Disabled, + "enabled" => JobsetState::Enabled, + "one_shot" => JobsetState::OneShot, + "one_at_a_time" => JobsetState::OneAtATime, + _ => JobsetState::Enabled, // Default to enabled for unknown values + } }); let jobset = repo::jobsets::upsert(pool, CreateJobset { - project_id: project.id, - name: decl_jobset.name.clone(), - nix_expression: decl_jobset.nix_expression.clone(), - enabled: Some(decl_jobset.enabled), - flake_mode: Some(decl_jobset.flake_mode), - check_interval: Some(decl_jobset.check_interval), - branch: decl_jobset.branch.clone(), + project_id: project.id, + name: decl_jobset.name.clone(), + nix_expression: decl_jobset.nix_expression.clone(), + enabled: Some(decl_jobset.enabled), + flake_mode: Some(decl_jobset.flake_mode), + check_interval: Some(decl_jobset.check_interval), + branch: decl_jobset.branch.clone(), scheduling_shares: Some(decl_jobset.scheduling_shares), state, }) @@ -141,8 +148,12 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { // Sync jobset inputs if !decl_jobset.inputs.is_empty() { - repo::jobset_inputs::sync_for_jobset(pool, jobset.id, &decl_jobset.inputs) - .await?; + repo::jobset_inputs::sync_for_jobset( + pool, + jobset.id, + &decl_jobset.inputs, + ) + .await?; tracing::info!( project = %project.name, jobset = %jobset.name, @@ -192,9 +203,12 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { // Sync channels if !decl_project.channels.is_empty() { - repo::channels::sync_for_project(pool, project.id, &decl_project.channels, |name| { - jobset_map.get(name).copied() - }) + repo::channels::sync_for_project( + pool, + project.id, + &decl_project.channels, + |name| jobset_map.get(name).copied(), + ) .await?; tracing::info!( project = %project.name, @@ -202,7 +216,6 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { "Synced declarative channels" ); } - } // Upsert API keys @@ -251,11 +264,11 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { if let Some(user) = existing { // Update existing user let update = crate::models::UpdateUser { - email: Some(decl_user.email.clone()), - full_name: decl_user.full_name.clone(), + email: Some(decl_user.email.clone()), + full_name: decl_user.full_name.clone(), password, - role: Some(decl_user.role.clone()), - enabled: Some(decl_user.enabled), + role: Some(decl_user.role.clone()), + enabled: Some(decl_user.enabled), public_dashboard: None, }; if let Err(e) = repo::users::update(pool, user.id, &update).await { @@ -287,12 +300,12 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { // Set enabled status if false (users are enabled by default) if !decl_user.enabled && let Err(e) = repo::users::set_enabled(pool, user.id, false).await - { - tracing::warn!( - username = %user.username, - "Failed to disable declarative user: {e}" - ); - } + { + tracing::warn!( + username = %user.username, + "Failed to disable declarative user: {e}" + ); + } }, Err(e) => { tracing::warn!( @@ -313,8 +326,8 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { if !config.remote_builders.is_empty() { repo::remote_builders::sync_all(pool, &config.remote_builders).await?; tracing::info!( - builders = config.remote_builders.len(), - "Synced declarative remote builders" + builders = config.remote_builders.len(), + "Synced declarative remote builders" ); } @@ -332,7 +345,9 @@ pub async fn run(pool: &PgPool, config: &DeclarativeConfig) -> Result<()> { } // Get project by name (already exists from earlier upsert) - if let Ok(project) = repo::projects::get_by_name(pool, &decl_project.name).await { + if let Ok(project) = + repo::projects::get_by_name(pool, &decl_project.name).await + { repo::project_members::sync_for_project( pool, project.id, diff --git a/crates/common/src/config.rs b/crates/common/src/config.rs index acb3f8a..32981c6 100644 --- a/crates/common/src/config.rs +++ b/crates/common/src/config.rs @@ -220,7 +220,8 @@ pub struct DeclarativeProject { /// Declarative notification configuration. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DeclarativeNotification { - /// Notification type: github_status, email, gitlab_status, gitea_status, run_command + /// Notification type: github_status, email, gitlab_status, gitea_status, + /// run_command pub notification_type: String, /// Type-specific configuration (JSON object) pub config: serde_json::Value, diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs index 12c6059..2c15db0 100644 --- a/crates/common/src/error.rs +++ b/crates/common/src/error.rs @@ -51,7 +51,7 @@ pub enum CiError { } impl CiError { - #[must_use] + #[must_use] pub fn is_disk_full(&self) -> bool { let msg = self.to_string().to_lowercase(); msg.contains("no space left on device") @@ -186,19 +186,19 @@ pub struct DiskSpaceInfo { impl DiskSpaceInfo { /// Check if disk space is critically low (less than 1GB available) - #[must_use] + #[must_use] pub fn is_critical(&self) -> bool { self.available_gb < 1.0 } /// Check if disk space is low (less than 5GB available) - #[must_use] + #[must_use] pub fn is_low(&self) -> bool { self.available_gb < 5.0 } /// Get a human-readable summary - #[must_use] + #[must_use] pub fn summary(&self) -> String { format!( "Total: {:.1}GB, Free: {:.1}GB ({:.1}%), Available: {:.1}GB", diff --git a/crates/common/src/log_storage.rs b/crates/common/src/log_storage.rs index 3b9e181..5e8ca79 100644 --- a/crates/common/src/log_storage.rs +++ b/crates/common/src/log_storage.rs @@ -15,13 +15,13 @@ impl LogStorage { } /// Returns the filesystem path where a build's log should be stored - #[must_use] + #[must_use] pub fn log_path(&self, build_id: &Uuid) -> PathBuf { self.log_dir.join(format!("{build_id}.log")) } /// Returns the filesystem path for an active (in-progress) build log - #[must_use] + #[must_use] pub fn log_path_for_active(&self, build_id: &Uuid) -> PathBuf { self.log_dir.join(format!("{build_id}.active.log")) } diff --git a/crates/common/src/models.rs b/crates/common/src/models.rs index 9960a52..45e73ed 100644 --- a/crates/common/src/models.rs +++ b/crates/common/src/models.rs @@ -77,13 +77,13 @@ pub enum JobsetState { impl JobsetState { /// Returns true if this jobset state allows evaluation. - #[must_use] + #[must_use] pub const fn is_evaluable(&self) -> bool { matches!(self, Self::Enabled | Self::OneShot | Self::OneAtATime) } /// Returns the database string representation of this state. - #[must_use] + #[must_use] pub const fn as_str(&self) -> &'static str { match self { Self::Disabled => "disabled", @@ -339,12 +339,12 @@ pub struct PaginationParams { } impl PaginationParams { - #[must_use] + #[must_use] pub fn limit(&self) -> i64 { self.limit.unwrap_or(50).clamp(1, 200) } - #[must_use] + #[must_use] pub fn offset(&self) -> i64 { self.offset.unwrap_or(0).max(0) } diff --git a/crates/common/src/nix_probe.rs b/crates/common/src/nix_probe.rs index 481b10a..5385605 100644 --- a/crates/common/src/nix_probe.rs +++ b/crates/common/src/nix_probe.rs @@ -95,28 +95,25 @@ pub async fn probe_flake( base_ref }; - let output = - tokio::time::timeout(std::time::Duration::from_mins(1), async { - tokio::process::Command::new("nix") - .args([ - "--extra-experimental-features", - "nix-command flakes", - "flake", - "show", - "--json", - "--no-write-lock-file", - &flake_ref, - ]) - .output() - .await - }) - .await - .map_err(|_| { - CiError::Timeout("Flake probe timed out after 60s".to_string()) - })? - .map_err(|e| { - CiError::NixEval(format!("Failed to run nix flake show: {e}")) - })?; + let output = tokio::time::timeout(std::time::Duration::from_mins(1), async { + tokio::process::Command::new("nix") + .args([ + "--extra-experimental-features", + "nix-command flakes", + "flake", + "show", + "--json", + "--no-write-lock-file", + &flake_ref, + ]) + .output() + .await + }) + .await + .map_err(|_| CiError::Timeout("Flake probe timed out after 60s".to_string()))? + .map_err(|e| { + CiError::NixEval(format!("Failed to run nix flake show: {e}")) + })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); diff --git a/crates/common/src/notifications.rs b/crates/common/src/notifications.rs index 4030f47..45f6d25 100644 --- a/crates/common/src/notifications.rs +++ b/crates/common/src/notifications.rs @@ -90,7 +90,9 @@ async fn set_github_status( build: &Build, ) { // Parse owner/repo from URL - let (owner, repo) = if let Some(v) = parse_github_repo(repo_url) { v } else { + let (owner, repo) = if let Some(v) = parse_github_repo(repo_url) { + v + } else { warn!("Cannot parse GitHub owner/repo from {repo_url}"); return; }; @@ -142,7 +144,9 @@ async fn set_gitea_status( build: &Build, ) { // Parse owner/repo from URL (try to extract from the gitea URL) - let (owner, repo) = if let Some(v) = parse_gitea_repo(repo_url, base_url) { v } else { + let (owner, repo) = if let Some(v) = parse_gitea_repo(repo_url, base_url) { + v + } else { warn!("Cannot parse Gitea owner/repo from {repo_url}"); return; }; @@ -191,7 +195,9 @@ async fn set_gitlab_status( build: &Build, ) { // Parse project path from URL - let project_path = if let Some(p) = parse_gitlab_project(repo_url, base_url) { p } else { + let project_path = if let Some(p) = parse_gitlab_project(repo_url, base_url) { + p + } else { warn!("Cannot parse GitLab project from {repo_url}"); return; }; diff --git a/crates/common/src/repo/channels.rs b/crates/common/src/repo/channels.rs index e61635a..812f749 100644 --- a/crates/common/src/repo/channels.rs +++ b/crates/common/src/repo/channels.rs @@ -96,8 +96,8 @@ pub async fn upsert( ) -> Result { sqlx::query_as::<_, Channel>( "INSERT INTO channels (project_id, name, jobset_id) VALUES ($1, $2, $3) \ - ON CONFLICT (project_id, name) DO UPDATE SET jobset_id = EXCLUDED.jobset_id \ - RETURNING *", + ON CONFLICT (project_id, name) DO UPDATE SET jobset_id = \ + EXCLUDED.jobset_id RETURNING *", ) .bind(project_id) .bind(name) @@ -119,12 +119,14 @@ pub async fn sync_for_project( let names: Vec<&str> = channels.iter().map(|c| c.name.as_str()).collect(); // Delete channels not in declarative config - sqlx::query("DELETE FROM channels WHERE project_id = $1 AND name != ALL($2::text[])") - .bind(project_id) - .bind(&names) - .execute(pool) - .await - .map_err(CiError::Database)?; + sqlx::query( + "DELETE FROM channels WHERE project_id = $1 AND name != ALL($2::text[])", + ) + .bind(project_id) + .bind(&names) + .execute(pool) + .await + .map_err(CiError::Database)?; // Upsert each channel for channel in channels { diff --git a/crates/common/src/repo/jobset_inputs.rs b/crates/common/src/repo/jobset_inputs.rs index 48f4ee7..eca2485 100644 --- a/crates/common/src/repo/jobset_inputs.rs +++ b/crates/common/src/repo/jobset_inputs.rs @@ -73,12 +73,9 @@ pub async fn upsert( ) -> Result { sqlx::query_as::<_, JobsetInput>( "INSERT INTO jobset_inputs (jobset_id, name, input_type, value, revision) \ - VALUES ($1, $2, $3, $4, $5) \ - ON CONFLICT (jobset_id, name) DO UPDATE SET \ - input_type = EXCLUDED.input_type, \ - value = EXCLUDED.value, \ - revision = EXCLUDED.revision \ - RETURNING *", + VALUES ($1, $2, $3, $4, $5) ON CONFLICT (jobset_id, name) DO UPDATE SET \ + input_type = EXCLUDED.input_type, value = EXCLUDED.value, revision = \ + EXCLUDED.revision RETURNING *", ) .bind(jobset_id) .bind(name) @@ -102,7 +99,8 @@ pub async fn sync_for_jobset( // Delete inputs not in declarative config sqlx::query( - "DELETE FROM jobset_inputs WHERE jobset_id = $1 AND name != ALL($2::text[])", + "DELETE FROM jobset_inputs WHERE jobset_id = $1 AND name != \ + ALL($2::text[])", ) .bind(jobset_id) .bind(&names) diff --git a/crates/common/src/repo/jobsets.rs b/crates/common/src/repo/jobsets.rs index 12abab1..bd83e01 100644 --- a/crates/common/src/repo/jobsets.rs +++ b/crates/common/src/repo/jobsets.rs @@ -231,8 +231,8 @@ pub async fn has_running_builds( } /// List jobsets that are due for evaluation based on their `check_interval`. -/// Returns jobsets where `last_checked_at` is NULL or older than `check_interval` -/// seconds. +/// Returns jobsets where `last_checked_at` is NULL or older than +/// `check_interval` seconds. pub async fn list_due_for_eval( pool: &PgPool, limit: i64, diff --git a/crates/common/src/repo/notification_configs.rs b/crates/common/src/repo/notification_configs.rs index 57d766f..164d00e 100644 --- a/crates/common/src/repo/notification_configs.rs +++ b/crates/common/src/repo/notification_configs.rs @@ -70,9 +70,9 @@ pub async fn upsert( ) -> Result { sqlx::query_as::<_, NotificationConfig>( "INSERT INTO notification_configs (project_id, notification_type, config, \ - enabled) VALUES ($1, $2, $3, $4) ON CONFLICT (project_id, notification_type) \ - DO UPDATE SET config = EXCLUDED.config, enabled = EXCLUDED.enabled \ - RETURNING *", + enabled) VALUES ($1, $2, $3, $4) ON CONFLICT (project_id, \ + notification_type) DO UPDATE SET config = EXCLUDED.config, enabled = \ + EXCLUDED.enabled RETURNING *", ) .bind(project_id) .bind(notification_type) diff --git a/crates/common/src/repo/project_members.rs b/crates/common/src/repo/project_members.rs index 81cbc04..1571353 100644 --- a/crates/common/src/repo/project_members.rs +++ b/crates/common/src/repo/project_members.rs @@ -141,9 +141,7 @@ pub async fn delete(pool: &PgPool, id: Uuid) -> Result<()> { .execute(pool) .await?; if result.rows_affected() == 0 { - return Err(CiError::NotFound(format!( - "Project member {id} not found" - ))); + return Err(CiError::NotFound(format!("Project member {id} not found"))); } Ok(()) } @@ -199,8 +197,8 @@ pub async fn upsert( .map_err(|e| CiError::Validation(e.to_string()))?; sqlx::query_as::<_, ProjectMember>( - "INSERT INTO project_members (project_id, user_id, role) VALUES ($1, $2, $3) \ - ON CONFLICT (project_id, user_id) DO UPDATE SET role = EXCLUDED.role \ + "INSERT INTO project_members (project_id, user_id, role) VALUES ($1, $2, \ + $3) ON CONFLICT (project_id, user_id) DO UPDATE SET role = EXCLUDED.role \ RETURNING *", ) .bind(project_id) diff --git a/crates/common/src/repo/remote_builders.rs b/crates/common/src/repo/remote_builders.rs index 0ba6cc3..a276539 100644 --- a/crates/common/src/repo/remote_builders.rs +++ b/crates/common/src/repo/remote_builders.rs @@ -152,9 +152,9 @@ pub async fn upsert( sqlx::query_as::<_, RemoteBuilder>( "INSERT INTO remote_builders (name, ssh_uri, systems, max_jobs, \ speed_factor, supported_features, mandatory_features, enabled, \ - public_host_key, ssh_key_file) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, \ - $10) ON CONFLICT (name) DO UPDATE SET ssh_uri = EXCLUDED.ssh_uri, systems = \ - EXCLUDED.systems, max_jobs = EXCLUDED.max_jobs, speed_factor = \ + public_host_key, ssh_key_file) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, \ + $9, $10) ON CONFLICT (name) DO UPDATE SET ssh_uri = EXCLUDED.ssh_uri, \ + systems = EXCLUDED.systems, max_jobs = EXCLUDED.max_jobs, speed_factor = \ EXCLUDED.speed_factor, supported_features = EXCLUDED.supported_features, \ mandatory_features = EXCLUDED.mandatory_features, enabled = \ EXCLUDED.enabled, public_host_key = COALESCE(EXCLUDED.public_host_key, \ diff --git a/crates/common/src/repo/search.rs b/crates/common/src/repo/search.rs index 9dda0e2..a3f7f25 100644 --- a/crates/common/src/repo/search.rs +++ b/crates/common/src/repo/search.rs @@ -364,7 +364,8 @@ async fn search_evaluations( } } - // Get count - simple count (full filter support would require building query differently) + // Get count - simple count (full filter support would require building query + // differently) let (total,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM evaluations") .fetch_one(pool) .await?; diff --git a/crates/common/src/repo/webhook_configs.rs b/crates/common/src/repo/webhook_configs.rs index dadaa48..5d5ef01 100644 --- a/crates/common/src/repo/webhook_configs.rs +++ b/crates/common/src/repo/webhook_configs.rs @@ -94,10 +94,10 @@ pub async fn upsert( enabled: bool, ) -> Result { sqlx::query_as::<_, WebhookConfig>( - "INSERT INTO webhook_configs (project_id, forge_type, secret_hash, enabled) \ - VALUES ($1, $2, $3, $4) ON CONFLICT (project_id, forge_type) DO UPDATE SET \ - secret_hash = COALESCE(EXCLUDED.secret_hash, webhook_configs.secret_hash), \ - enabled = EXCLUDED.enabled RETURNING *", + "INSERT INTO webhook_configs (project_id, forge_type, secret_hash, \ + enabled) VALUES ($1, $2, $3, $4) ON CONFLICT (project_id, forge_type) DO \ + UPDATE SET secret_hash = COALESCE(EXCLUDED.secret_hash, \ + webhook_configs.secret_hash), enabled = EXCLUDED.enabled RETURNING *", ) .bind(project_id) .bind(forge_type) @@ -117,7 +117,8 @@ pub async fn sync_for_project( resolve_secret: impl Fn(&DeclarativeWebhook) -> Option, ) -> Result<()> { // Get forge types from declarative config - let types: Vec<&str> = webhooks.iter().map(|w| w.forge_type.as_str()).collect(); + let types: Vec<&str> = + webhooks.iter().map(|w| w.forge_type.as_str()).collect(); // Delete webhook configs not in declarative config sqlx::query( diff --git a/crates/common/src/roles.rs b/crates/common/src/roles.rs index 28c279e..d8afafb 100644 --- a/crates/common/src/roles.rs +++ b/crates/common/src/roles.rs @@ -49,19 +49,19 @@ pub const VALID_PROJECT_ROLES: &[&str] = &[ ]; /// Check if a global role is valid -#[must_use] +#[must_use] pub fn is_valid_role(role: &str) -> bool { VALID_ROLES.contains(&role) } /// Check if a project role is valid -#[must_use] +#[must_use] pub fn is_valid_project_role(role: &str) -> bool { VALID_PROJECT_ROLES.contains(&role) } /// Get the highest project role (for permission checks) -#[must_use] +#[must_use] pub fn project_role_level(role: &str) -> i32 { match role { PROJECT_ROLE_ADMIN => 3, @@ -73,7 +73,7 @@ pub fn project_role_level(role: &str) -> i32 { /// Check if user has required project permission /// Higher level roles automatically have lower level permissions -#[must_use] +#[must_use] pub fn has_project_permission(user_role: &str, required: &str) -> bool { let user_level = project_role_level(user_role); let required_level = project_role_level(required); diff --git a/crates/common/src/validate.rs b/crates/common/src/validate.rs index 060d2d1..95511bc 100644 --- a/crates/common/src/validate.rs +++ b/crates/common/src/validate.rs @@ -6,14 +6,14 @@ use regex::Regex; /// Validate that a path is a valid nix store path. /// Rejects path traversal, overly long paths, and non-store paths. -#[must_use] +#[must_use] pub fn is_valid_store_path(path: &str) -> bool { path.starts_with("/nix/store/") && !path.contains("..") && path.len() < 512 } /// Validate that a string is a valid nix store hash (32 lowercase alphanumeric /// chars). -#[must_use] +#[must_use] pub fn is_valid_nix_hash(hash: &str) -> bool { hash.len() == 32 && hash @@ -62,7 +62,8 @@ fn validate_repository_url(url: &str) -> Result<(), String> { } if !VALID_REPO_PREFIXES.iter().any(|p| url.starts_with(p)) { return Err( - "repository_url must start with https://, http://, git://, ssh://, or file://" + "repository_url must start with https://, http://, git://, ssh://, or \ + file://" .to_string(), ); } @@ -146,7 +147,19 @@ fn validate_forge_type(forge_type: &str) -> Result<(), String> { // --- Implementations --- -use crate::models::{CreateProject, UpdateProject, CreateJobset, UpdateJobset, CreateEvaluation, CreateBuild, CreateChannel, UpdateChannel, CreateRemoteBuilder, UpdateRemoteBuilder, CreateWebhookConfig}; +use crate::models::{ + CreateBuild, + CreateChannel, + CreateEvaluation, + CreateJobset, + CreateProject, + CreateRemoteBuilder, + CreateWebhookConfig, + UpdateChannel, + UpdateJobset, + UpdateProject, + UpdateRemoteBuilder, +}; impl Validate for CreateProject { fn validate(&self) -> Result<(), String> { diff --git a/crates/queue-runner/src/builder.rs b/crates/queue-runner/src/builder.rs index 86b3084..f9cdac9 100644 --- a/crates/queue-runner/src/builder.rs +++ b/crates/queue-runner/src/builder.rs @@ -146,7 +146,7 @@ pub struct SubStep { /// Parse a single nix internal JSON log line (`@nix {...}`). /// Returns `Some(action, drv_path)` if the line contains a derivation action. -#[must_use] +#[must_use] pub fn parse_nix_log_line(line: &str) -> Option<(&'static str, String)> { let json_str = line.strip_prefix("@nix ")?.trim(); let parsed: serde_json::Value = serde_json::from_str(json_str).ok()?; diff --git a/crates/queue-runner/src/worker.rs b/crates/queue-runner/src/worker.rs index 781a9fe..847b2d8 100644 --- a/crates/queue-runner/src/worker.rs +++ b/crates/queue-runner/src/worker.rs @@ -31,7 +31,7 @@ pub struct WorkerPool { impl WorkerPool { #[allow(clippy::too_many_arguments)] - #[must_use] + #[must_use] pub fn new( db_pool: PgPool, workers: usize, @@ -476,8 +476,10 @@ async fn run_build( push_to_cache(&build_result.output_paths, store_uri).await; } - let primary_output = - build_result.output_paths.first().map(std::string::String::as_str); + let primary_output = build_result + .output_paths + .first() + .map(std::string::String::as_str); repo::builds::complete( pool, diff --git a/crates/server/src/auth_middleware.rs b/crates/server/src/auth_middleware.rs index a43b025..1f9774c 100644 --- a/crates/server/src/auth_middleware.rs +++ b/crates/server/src/auth_middleware.rs @@ -76,9 +76,7 @@ pub async fn require_api_key( && let Some(session) = state.sessions.get(&session_id) { // Check session expiry (24 hours) - if session.created_at.elapsed() - < std::time::Duration::from_hours(24) - { + if session.created_at.elapsed() < std::time::Duration::from_hours(24) { // Insert both user and session data if let Some(ref user) = session.user { request.extensions_mut().insert(user.clone()); @@ -98,9 +96,7 @@ pub async fn require_api_key( && let Some(session) = state.sessions.get(&session_id) { // Check session expiry (24 hours) - if session.created_at.elapsed() - < std::time::Duration::from_hours(24) - { + if session.created_at.elapsed() < std::time::Duration::from_hours(24) { if let Some(ref api_key) = session.api_key { request.extensions_mut().insert(api_key.clone()); } @@ -222,9 +218,7 @@ pub async fn extract_session( && let Some(session) = state.sessions.get(&session_id) { // Check session expiry - if session.created_at.elapsed() - < std::time::Duration::from_hours(24) - { + if session.created_at.elapsed() < std::time::Duration::from_hours(24) { if let Some(ref user) = session.user { request.extensions_mut().insert(user.clone()); } @@ -242,9 +236,7 @@ pub async fn extract_session( && let Some(session) = state.sessions.get(&session_id) { // Check session expiry - if session.created_at.elapsed() - < std::time::Duration::from_hours(24) - { + if session.created_at.elapsed() < std::time::Duration::from_hours(24) { if let Some(ref api_key) = session.api_key { request.extensions_mut().insert(api_key.clone()); } diff --git a/crates/server/src/error.rs b/crates/server/src/error.rs index 9c3d23e..72c5a9c 100644 --- a/crates/server/src/error.rs +++ b/crates/server/src/error.rs @@ -90,8 +90,8 @@ impl IntoResponse for ApiError { StatusCode::INSUFFICIENT_STORAGE, "DISK_FULL", format!( - "Database error: {e}\n\nDISK SPACE ISSUE:\nThe server is running \ - low on disk space." + "Database error: {e}\n\nDISK SPACE ISSUE:\nThe server is \ + running low on disk space." ), ) } else { @@ -130,8 +130,8 @@ impl IntoResponse for ApiError { StatusCode::INSUFFICIENT_STORAGE, "DISK_FULL", format!( - "IO error: {msg}\n\nDISK SPACE ISSUE DETECTED:\nThe server has run \ - out of disk space. Please free up space:\n- Run \ + "IO error: {msg}\n\nDISK SPACE ISSUE DETECTED:\nThe server has \ + run out of disk space. Please free up space:\n- Run \ `nix-collect-garbage -d` to clean the Nix store\n- Clear the \ evaluator work directory: `rm -rf /tmp/fc-evaluator/*`\n- \ Clear build logs if configured" diff --git a/crates/server/src/routes/auth.rs b/crates/server/src/routes/auth.rs index 67f0388..6a34e10 100644 --- a/crates/server/src/routes/auth.rs +++ b/crates/server/src/routes/auth.rs @@ -29,7 +29,7 @@ pub struct ApiKeyInfo { pub last_used_at: Option>, } -#[must_use] +#[must_use] pub fn hash_api_key(key: &str) -> String { let mut hasher = Sha256::new(); hasher.update(key.as_bytes()); diff --git a/crates/server/src/routes/cache.rs b/crates/server/src/routes/cache.rs index 4a697fd..9d7292f 100644 --- a/crates/server/src/routes/cache.rs +++ b/crates/server/src/routes/cache.rs @@ -68,7 +68,10 @@ async fn narinfo( }; let nar_hash = entry.get("narHash").and_then(|v| v.as_str()).unwrap_or(""); - let nar_size = entry.get("narSize").and_then(serde_json::Value::as_u64).unwrap_or(0); + let nar_size = entry + .get("narSize") + .and_then(serde_json::Value::as_u64) + .unwrap_or(0); let store_path = entry .get("path") .and_then(|v| v.as_str()) diff --git a/crates/server/src/routes/dashboard.rs b/crates/server/src/routes/dashboard.rs index 0c073b0..12e83fc 100644 --- a/crates/server/src/routes/dashboard.rs +++ b/crates/server/src/routes/dashboard.rs @@ -7,7 +7,19 @@ use axum::{ response::{Html, IntoResponse, Redirect, Response}, routing::get, }; -use fc_common::models::{Build, Evaluation, BuildStatus, EvaluationStatus, ApiKey, Project, Jobset, BuildStep, BuildProduct, Channel, SystemStatus}; +use fc_common::models::{ + ApiKey, + Build, + BuildProduct, + BuildStatus, + BuildStep, + Channel, + Evaluation, + EvaluationStatus, + Jobset, + Project, + SystemStatus, +}; use sha2::{Digest, Sha256}; use uuid::Uuid; @@ -699,7 +711,8 @@ async fn evaluations_page( Ok(js) => { let pname = fc_common::repo::projects::get(&state.pool, js.project_id) - .await.map_or_else(|_| "-".to_string(), |p| p.name); + .await + .map_or_else(|_| "-".to_string(), |p| p.name); (js.name, pname) }, Err(_) => ("-".to_string(), "-".to_string()), @@ -983,13 +996,17 @@ async fn queue_page(State(state): State) -> Html { } else { String::new() }; - let builder_name = b.builder_id.and_then(|id| builder_map.get(&id).cloned()); + let builder_name = + b.builder_id.and_then(|id| builder_map.get(&id).cloned()); QueueBuildView { id: b.id, job_name: b.job_name.clone(), system: b.system.clone().unwrap_or_else(|| "unknown".to_string()), created_at: b.created_at.format("%Y-%m-%d %H:%M").to_string(), - started_at: b.started_at.map(|t| t.format("%H:%M:%S").to_string()).unwrap_or_default(), + started_at: b + .started_at + .map(|t| t.format("%H:%M:%S").to_string()) + .unwrap_or_default(), elapsed, priority: b.priority, builder_name, @@ -1002,16 +1019,18 @@ async fn queue_page(State(state): State) -> Html { let pending_builds: Vec = pending .iter() .enumerate() - .map(|(idx, b)| QueueBuildView { - id: b.id, - job_name: b.job_name.clone(), - system: b.system.clone().unwrap_or_else(|| "unknown".to_string()), - created_at: b.created_at.format("%Y-%m-%d %H:%M").to_string(), - started_at: String::new(), - elapsed: String::new(), - priority: b.priority, - builder_name: None, - queue_pos: (idx + 1) as i64, + .map(|(idx, b)| { + QueueBuildView { + id: b.id, + job_name: b.job_name.clone(), + system: b.system.clone().unwrap_or_else(|| "unknown".to_string()), + created_at: b.created_at.format("%Y-%m-%d %H:%M").to_string(), + started_at: String::new(), + elapsed: String::new(), + priority: b.priority, + builder_name: None, + queue_pos: (idx + 1) as i64, + } }) .collect(); @@ -1153,8 +1172,10 @@ async fn admin_page( name: k.name, role: k.role, created_at: k.created_at.format("%Y-%m-%d %H:%M").to_string(), - last_used_at: k - .last_used_at.map_or_else(|| "Never".to_string(), |t| t.format("%Y-%m-%d %H:%M").to_string()), + last_used_at: k.last_used_at.map_or_else( + || "Never".to_string(), + |t| t.format("%Y-%m-%d %H:%M").to_string(), + ), } }) .collect(); @@ -1218,7 +1239,9 @@ async fn login_action( password: password.clone(), }; - if let Ok(user) = fc_common::repo::users::authenticate(&state.pool, &creds).await { + if let Ok(user) = + fc_common::repo::users::authenticate(&state.pool, &creds).await + { let session_id = Uuid::new_v4().to_string(); state .sessions @@ -1269,7 +1292,9 @@ async fn login_action( hasher.update(token.as_bytes()); let key_hash = hex::encode(hasher.finalize()); - if let Ok(Some(api_key)) = fc_common::repo::api_keys::get_by_hash(&state.pool, &key_hash).await { + if let Ok(Some(api_key)) = + fc_common::repo::api_keys::get_by_hash(&state.pool, &key_hash).await + { let session_id = Uuid::new_v4().to_string(); state .sessions @@ -1280,7 +1305,8 @@ async fn login_action( }); let cookie = format!( - "fc_session={session_id}; HttpOnly; SameSite=Strict; Path=/; Max-Age=86400" + "fc_session={session_id}; HttpOnly; SameSite=Strict; Path=/; \ + Max-Age=86400" ); ( [(axum::http::header::SET_COOKIE, cookie)], @@ -1408,8 +1434,10 @@ async fn users_page( role: u.role, user_type: user_type.to_string(), enabled: u.enabled, - last_login_at: u - .last_login_at.map_or_else(|| "Never".to_string(), |t| t.format("%Y-%m-%d %H:%M").to_string()), + last_login_at: u.last_login_at.map_or_else( + || "Never".to_string(), + |t| t.format("%Y-%m-%d %H:%M").to_string(), + ), } }) .collect(); @@ -1455,12 +1483,14 @@ async fn starred_page( // Get project name let project_name = fc_common::repo::projects::get(&state.pool, s.project_id) - .await.map_or_else(|_| "-".to_string(), |p| p.name); + .await + .map_or_else(|_| "-".to_string(), |p| p.name); // Get jobset name let jobset_name = if let Some(js_id) = s.jobset_id { fc_common::repo::jobsets::get(&state.pool, js_id) - .await.map_or_else(|_| "-".to_string(), |j| j.name) + .await + .map_or_else(|_| "-".to_string(), |j| j.name) } else { "-".to_string() }; diff --git a/crates/server/src/routes/webhooks.rs b/crates/server/src/routes/webhooks.rs index 14ed304..d117e61 100644 --- a/crates/server/src/routes/webhooks.rs +++ b/crates/server/src/routes/webhooks.rs @@ -403,7 +403,9 @@ async fn handle_gitea_push( .map_err(ApiError)?; // Fall back to the other type if not found - let webhook_config = if let Some(c) = webhook_config { c } else { + let webhook_config = if let Some(c) = webhook_config { + c + } else { let alt = if forge_type == "gitea" { "forgejo" } else { diff --git a/crates/server/src/state.rs b/crates/server/src/state.rs index c341c82..b47de28 100644 --- a/crates/server/src/state.rs +++ b/crates/server/src/state.rs @@ -17,7 +17,7 @@ pub struct SessionData { impl SessionData { /// Check if the session has admin role - #[must_use] + #[must_use] pub fn is_admin(&self) -> bool { if let Some(ref user) = self.user { user.role == "admin" @@ -29,7 +29,7 @@ impl SessionData { } /// Check if the session has a specific role - #[must_use] + #[must_use] pub fn has_role(&self, role: &str) -> bool { if self.is_admin() { return true; @@ -44,7 +44,7 @@ impl SessionData { } /// Get the display name for the session (username or api key name) - #[must_use] + #[must_use] pub fn display_name(&self) -> String { if let Some(ref user) = self.user { user.username.clone() @@ -56,7 +56,7 @@ impl SessionData { } /// Check if this is a user session (not just API key) - #[must_use] + #[must_use] pub const fn is_user_session(&self) -> bool { self.user.is_some() }