circus/crates/common/src/models.rs
NotAShelf f8f9703faa
fc-common: add build_metrics table and repository
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: If6842311f49bfcba9e1b11fa8bc9748c6a6a6964
2026-02-14 18:08:16 +03:00

581 lines
16 KiB
Rust

//! 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<String>,
pub repository_url: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[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<String>,
pub scheduling_shares: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub state: JobsetState,
pub last_checked_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Evaluation {
pub id: Uuid,
pub jobset_id: Uuid,
pub commit_hash: String,
pub evaluation_time: DateTime<Utc>,
pub status: EvaluationStatus,
pub error_message: Option<String>,
pub inputs_hash: Option<String>,
pub pr_number: Option<i32>,
pub pr_head_branch: Option<String>,
pub pr_base_branch: Option<String>,
pub pr_action: Option<String>,
}
#[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<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub log_path: Option<String>,
pub build_output_path: Option<String>,
pub error_message: Option<String>,
pub system: Option<String>,
pub priority: i32,
pub retry_count: i32,
pub max_retries: i32,
pub notification_pending_since: Option<DateTime<Utc>>,
pub log_url: Option<String>,
pub created_at: DateTime<Utc>,
pub outputs: Option<serde_json::Value>,
pub is_aggregate: bool,
pub constituents: Option<serde_json::Value>,
pub builder_id: Option<Uuid>,
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<String>,
pub file_size: Option<i64>,
pub content_type: Option<String>,
pub is_directory: bool,
pub gc_root_path: Option<String>,
pub created_at: DateTime<Utc>,
}
#[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<String>,
pub error_output: Option<String>,
pub started_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub exit_code: Option<i32>,
}
#[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<Utc>,
}
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<String>,
pub scheduling_shares: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub state: JobsetState,
pub last_checked_at: Option<DateTime<Utc>>,
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<i64>,
pub completed_builds: Option<i64>,
pub failed_builds: Option<i64>,
pub running_builds: Option<i64>,
pub pending_builds: Option<i64>,
pub avg_duration_seconds: Option<f64>,
}
/// 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<Uuid>,
pub created_at: DateTime<Utc>,
pub last_used_at: Option<DateTime<Utc>>,
}
/// 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<String>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
}
/// 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<Utc>,
}
/// 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<String>,
pub created_at: DateTime<Utc>,
}
/// 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<Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// 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<String>,
pub max_jobs: i32,
pub speed_factor: i32,
pub supported_features: Vec<String>,
pub mandatory_features: Vec<String>,
pub enabled: bool,
pub public_host_key: Option<String>,
pub ssh_key_file: Option<String>,
pub created_at: DateTime<Utc>,
}
// --- 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<String>,
pub password_hash: Option<String>,
pub user_type: UserType,
pub role: String,
pub enabled: bool,
pub email_verified: bool,
pub public_dashboard: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub last_login_at: Option<DateTime<Utc>>,
}
#[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<Uuid>,
pub job_name: String,
pub created_at: DateTime<Utc>,
}
/// 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<Utc>,
}
/// 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<Utc>,
pub created_at: DateTime<Utc>,
pub last_used_at: Option<DateTime<Utc>>,
}
// --- Pagination ---
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationParams {
pub limit: Option<i64>,
pub offset: Option<i64>,
}
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<T> {
pub items: Vec<T>,
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<String>,
pub repository_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateProject {
pub name: Option<String>,
pub description: Option<String>,
pub repository_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateJobset {
pub project_id: Uuid,
pub name: String,
pub nix_expression: String,
pub enabled: Option<bool>,
pub flake_mode: Option<bool>,
pub check_interval: Option<i32>,
pub branch: Option<String>,
pub scheduling_shares: Option<i32>,
pub state: Option<JobsetState>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateJobset {
pub name: Option<String>,
pub nix_expression: Option<String>,
pub enabled: Option<bool>,
pub flake_mode: Option<bool>,
pub check_interval: Option<i32>,
pub branch: Option<String>,
pub scheduling_shares: Option<i32>,
pub state: Option<JobsetState>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateEvaluation {
pub jobset_id: Uuid,
pub commit_hash: String,
pub pr_number: Option<i32>,
pub pr_head_branch: Option<String>,
pub pr_base_branch: Option<String>,
pub pr_action: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateBuild {
pub evaluation_id: Uuid,
pub job_name: String,
pub drv_path: String,
pub system: Option<String>,
pub outputs: Option<serde_json::Value>,
pub is_aggregate: Option<bool>,
pub constituents: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateBuildProduct {
pub build_id: Uuid,
pub name: String,
pub path: String,
pub sha256_hash: Option<String>,
pub file_size: Option<i64>,
pub content_type: Option<String>,
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<String>,
}
#[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<String>,
pub jobset_id: Option<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateRemoteBuilder {
pub name: String,
pub ssh_uri: String,
pub systems: Vec<String>,
pub max_jobs: Option<i32>,
pub speed_factor: Option<i32>,
pub supported_features: Option<Vec<String>>,
pub mandatory_features: Option<Vec<String>>,
pub public_host_key: Option<String>,
pub ssh_key_file: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateRemoteBuilder {
pub name: Option<String>,
pub ssh_uri: Option<String>,
pub systems: Option<Vec<String>>,
pub max_jobs: Option<i32>,
pub speed_factor: Option<i32>,
pub supported_features: Option<Vec<String>>,
pub mandatory_features: Option<Vec<String>>,
pub enabled: Option<bool>,
pub public_host_key: Option<String>,
pub ssh_key_file: Option<String>,
}
/// 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<String>,
pub password: String,
pub role: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateUser {
pub email: Option<String>,
pub full_name: Option<String>,
pub password: Option<String>,
pub role: Option<String>,
pub enabled: Option<bool>,
pub public_dashboard: Option<bool>,
}
#[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<Uuid>,
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<String>,
}