//! Data models for CI use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Project { pub id: Uuid, pub name: String, pub description: Option, pub repository_url: String, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Jobset { pub id: Uuid, pub project_id: Uuid, pub name: String, pub nix_expression: String, pub enabled: bool, pub flake_mode: bool, pub check_interval: i32, pub branch: Option, pub scheduling_shares: i32, pub created_at: DateTime, pub updated_at: DateTime, pub state: JobsetState, pub last_checked_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Evaluation { pub id: Uuid, pub jobset_id: Uuid, pub commit_hash: String, pub evaluation_time: DateTime, pub status: EvaluationStatus, pub error_message: Option, pub inputs_hash: Option, pub pr_number: Option, pub pr_head_branch: Option, pub pr_base_branch: Option, pub pr_action: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "text", rename_all = "lowercase")] pub enum EvaluationStatus { Pending, Running, Completed, Failed, } /// Jobset scheduling state (Hydra-compatible). /// /// - `Disabled`: Jobset will not be evaluated /// - `Enabled`: Normal operation, evaluated according to `check_interval` /// - `OneShot`: Evaluated once, then automatically set to Disabled /// - `OneAtATime`: Only one build can run at a time for this jobset #[derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type, Default, )] #[serde(rename_all = "snake_case")] #[sqlx(type_name = "varchar", rename_all = "snake_case")] pub enum JobsetState { Disabled, #[default] Enabled, OneShot, OneAtATime, } impl JobsetState { /// Returns true if this jobset state allows evaluation. #[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] pub const fn as_str(&self) -> &'static str { match self { Self::Disabled => "disabled", Self::Enabled => "enabled", Self::OneShot => "one_shot", Self::OneAtATime => "one_at_a_time", } } } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Build { pub id: Uuid, pub evaluation_id: Uuid, pub job_name: String, pub drv_path: String, pub status: BuildStatus, pub started_at: Option>, pub completed_at: Option>, pub log_path: Option, pub build_output_path: Option, pub error_message: Option, pub system: Option, pub priority: i32, pub retry_count: i32, pub max_retries: i32, pub notification_pending_since: Option>, pub log_url: Option, pub created_at: DateTime, pub outputs: Option, pub is_aggregate: bool, pub constituents: Option, pub builder_id: Option, pub signed: bool, } #[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type, PartialEq, Eq)] #[sqlx(type_name = "text", rename_all = "lowercase")] pub enum BuildStatus { Pending, Running, Completed, Failed, Cancelled, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct BuildProduct { pub id: Uuid, pub build_id: Uuid, pub name: String, pub path: String, pub sha256_hash: Option, pub file_size: Option, pub content_type: Option, pub is_directory: bool, pub gc_root_path: Option, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct BuildStep { pub id: Uuid, pub build_id: Uuid, pub step_number: i32, pub command: String, pub output: Option, pub error_output: Option, pub started_at: DateTime, pub completed_at: Option>, pub exit_code: Option, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct BuildDependency { pub id: Uuid, pub build_id: Uuid, pub dependency_build_id: Uuid, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct BuildMetric { pub id: Uuid, pub build_id: Uuid, pub metric_name: String, pub metric_value: f64, pub unit: String, pub collected_at: DateTime, } pub mod metric_names { pub const BUILD_DURATION_SECONDS: &str = "build_duration_seconds"; pub const OUTPUT_SIZE_BYTES: &str = "output_size_bytes"; } pub mod metric_units { pub const SECONDS: &str = "seconds"; pub const BYTES: &str = "bytes"; } /// Active jobset view — enabled jobsets joined with project info. #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ActiveJobset { pub id: Uuid, pub project_id: Uuid, pub name: String, pub nix_expression: String, pub enabled: bool, pub flake_mode: bool, pub check_interval: i32, pub branch: Option, pub scheduling_shares: i32, pub created_at: DateTime, pub updated_at: DateTime, pub state: JobsetState, pub last_checked_at: Option>, pub project_name: String, pub repository_url: String, } /// Build statistics from the `build_stats` view. #[derive(Debug, Clone, Serialize, Deserialize, FromRow, Default)] pub struct BuildStats { pub total_builds: Option, pub completed_builds: Option, pub failed_builds: Option, pub running_builds: Option, pub pending_builds: Option, pub avg_duration_seconds: Option, } /// API key for authentication. #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ApiKey { pub id: Uuid, pub name: String, pub key_hash: String, pub role: String, pub user_id: Option, pub created_at: DateTime, pub last_used_at: Option>, } /// Webhook configuration for a project. #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct WebhookConfig { pub id: Uuid, pub project_id: Uuid, pub forge_type: String, pub secret_hash: Option, pub enabled: bool, pub created_at: DateTime, } /// Notification configuration for a project. #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct NotificationConfig { pub id: Uuid, pub project_id: Uuid, pub notification_type: String, pub config: serde_json::Value, pub enabled: bool, pub created_at: DateTime, } /// Jobset input definition. #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct JobsetInput { pub id: Uuid, pub jobset_id: Uuid, pub name: String, pub input_type: String, pub value: String, pub revision: Option, pub created_at: DateTime, } /// Release channel — tracks the latest "good" evaluation for a jobset. #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Channel { pub id: Uuid, pub project_id: Uuid, pub name: String, pub jobset_id: Uuid, pub current_evaluation_id: Option, pub created_at: DateTime, pub updated_at: DateTime, } /// Remote builder for multi-machine / multi-arch builds. #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct RemoteBuilder { pub id: Uuid, pub name: String, pub ssh_uri: String, pub systems: Vec, pub max_jobs: i32, pub speed_factor: i32, pub supported_features: Vec, pub mandatory_features: Vec, pub enabled: bool, pub public_host_key: Option, pub ssh_key_file: Option, pub created_at: DateTime, } // --- User Management --- /// User account for authentication and personalization #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct User { pub id: Uuid, pub username: String, pub email: String, pub full_name: Option, pub password_hash: Option, pub user_type: UserType, pub role: String, pub enabled: bool, pub email_verified: bool, pub public_dashboard: bool, pub created_at: DateTime, pub updated_at: DateTime, pub last_login_at: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "varchar", rename_all = "lowercase")] pub enum UserType { Local, Github, Google, } /// Starred job for personalized dashboard #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct StarredJob { pub id: Uuid, pub user_id: Uuid, pub project_id: Uuid, pub jobset_id: Option, pub job_name: String, pub created_at: DateTime, } /// Project membership for per-project permissions #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ProjectMember { pub id: Uuid, pub project_id: Uuid, pub user_id: Uuid, pub role: String, pub created_at: DateTime, } /// User session for persistent authentication #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct UserSession { pub id: Uuid, pub user_id: Uuid, pub session_token_hash: String, pub expires_at: DateTime, pub created_at: DateTime, pub last_used_at: Option>, } // --- Pagination --- #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PaginationParams { pub limit: Option, pub offset: Option, } impl PaginationParams { #[must_use] pub fn limit(&self) -> i64 { self.limit.unwrap_or(50).clamp(1, 200) } #[must_use] pub fn offset(&self) -> i64 { self.offset.unwrap_or(0).max(0) } } impl Default for PaginationParams { fn default() -> Self { Self { limit: Some(50), offset: Some(0), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PaginatedResponse { pub items: Vec, pub total: i64, pub limit: i64, pub offset: i64, } // --- DTO structs for creation and updates --- #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateProject { pub name: String, pub description: Option, pub repository_url: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateProject { pub name: Option, pub description: Option, pub repository_url: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateJobset { pub project_id: Uuid, pub name: String, pub nix_expression: String, pub enabled: Option, pub flake_mode: Option, pub check_interval: Option, pub branch: Option, pub scheduling_shares: Option, pub state: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateJobset { pub name: Option, pub nix_expression: Option, pub enabled: Option, pub flake_mode: Option, pub check_interval: Option, pub branch: Option, pub scheduling_shares: Option, pub state: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateEvaluation { pub jobset_id: Uuid, pub commit_hash: String, pub pr_number: Option, pub pr_head_branch: Option, pub pr_base_branch: Option, pub pr_action: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateBuild { pub evaluation_id: Uuid, pub job_name: String, pub drv_path: String, pub system: Option, pub outputs: Option, pub is_aggregate: Option, pub constituents: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateBuildProduct { pub build_id: Uuid, pub name: String, pub path: String, pub sha256_hash: Option, pub file_size: Option, pub content_type: Option, pub is_directory: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateBuildStep { pub build_id: Uuid, pub step_number: i32, pub command: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateWebhookConfig { pub project_id: Uuid, pub forge_type: String, pub secret: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateNotificationConfig { pub project_id: Uuid, pub notification_type: String, pub config: serde_json::Value, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateChannel { pub project_id: Uuid, pub name: String, pub jobset_id: Uuid, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateChannel { pub name: Option, pub jobset_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateRemoteBuilder { pub name: String, pub ssh_uri: String, pub systems: Vec, pub max_jobs: Option, pub speed_factor: Option, pub supported_features: Option>, pub mandatory_features: Option>, pub public_host_key: Option, pub ssh_key_file: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateRemoteBuilder { pub name: Option, pub ssh_uri: Option, pub systems: Option>, pub max_jobs: Option, pub speed_factor: Option, pub supported_features: Option>, pub mandatory_features: Option>, pub enabled: Option, pub public_host_key: Option, pub ssh_key_file: Option, } /// Summary of system status for the admin API. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SystemStatus { pub projects_count: i64, pub jobsets_count: i64, pub evaluations_count: i64, pub builds_pending: i64, pub builds_running: i64, pub builds_completed: i64, pub builds_failed: i64, pub remote_builders: i64, pub channels_count: i64, } // --- User DTOs --- #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateUser { pub username: String, pub email: String, pub full_name: Option, pub password: String, pub role: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateUser { pub email: Option, pub full_name: Option, pub password: Option, pub role: Option, pub enabled: Option, pub public_dashboard: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoginCredentials { pub username: String, pub password: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateStarredJob { pub project_id: Uuid, pub jobset_id: Option, pub job_name: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateProjectMember { pub user_id: Uuid, pub role: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateProjectMember { pub role: Option, }