use serde::{Deserialize, Serialize}; use uuid::Uuid; // ─── Issue ────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Issue { pub id: String, pub title: String, pub description: String, pub severity: String, pub status: String, pub category: String, pub source: String, pub created_at: String, pub updated_at: String, pub resolved_at: Option, pub assigned_to: String, pub tags: String, } impl Issue { pub fn new(title: String, description: String, severity: String, category: String) -> Self { let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(); Issue { id: Uuid::now_v7().to_string(), title, description, severity, status: "open".to_string(), category, source: "manual".to_string(), created_at: now.clone(), updated_at: now, resolved_at: None, assigned_to: String::new(), tags: "[]".to_string(), } } } /// Full detail view returned by get_issue. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IssueDetail { pub issue: Issue, pub log_files: Vec, pub image_attachments: Vec, pub resolution_steps: Vec, pub conversations: Vec, pub timeline_events: Vec, } /// Lightweight row returned by list/search commands. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IssueSummary { pub id: String, pub title: String, pub severity: String, pub status: String, pub category: String, pub created_at: String, pub updated_at: String, pub log_count: i64, pub step_count: i64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IssueListItem { pub id: String, pub title: String, pub domain: String, pub status: String, pub severity: String, pub created_at: i64, pub updated_at: i64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct IssueFilter { pub status: Option, pub severity: Option, pub category: Option, pub domain: Option, pub search: Option, pub limit: Option, pub offset: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct NewIssue { pub title: String, pub domain: String, pub description: Option, pub severity: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct IssueUpdate { pub title: Option, pub description: Option, pub severity: Option, pub status: Option, pub category: Option, pub domain: Option, pub assigned_to: Option, pub tags: Option, } // ─── 5-Whys ───────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FiveWhyEntry { pub id: String, pub why_number: i32, pub question: String, pub answer: Option, pub created_at: i64, } // ─── Timeline ──────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimelineEvent { pub id: String, pub issue_id: String, pub event_type: String, pub description: String, pub metadata: String, pub created_at: String, } impl TimelineEvent { pub fn new( issue_id: String, event_type: String, description: String, metadata: String, ) -> Self { TimelineEvent { id: Uuid::now_v7().to_string(), issue_id, event_type, description, metadata, created_at: chrono::Utc::now() .format("%Y-%m-%d %H:%M:%S UTC") .to_string(), } } } // ─── Log File ─────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogFile { pub id: String, pub issue_id: String, pub file_name: String, pub file_path: String, pub file_size: i64, pub mime_type: String, pub content_hash: String, pub uploaded_at: String, pub redacted: bool, } impl LogFile { pub fn new(issue_id: String, file_name: String, file_path: String, file_size: i64) -> Self { LogFile { id: Uuid::now_v7().to_string(), issue_id, file_name, file_path, file_size, mime_type: "text/plain".to_string(), content_hash: String::new(), uploaded_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), redacted: false, } } } // ─── PII ──────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PiiSpanRecord { pub id: String, pub log_file_id: String, pub pii_type: String, pub start_offset: i64, pub end_offset: i64, pub original_value: String, pub replacement: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PiiDetectionResult { pub log_file_id: String, pub detections: Vec, pub total_pii_found: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RedactedLogFile { pub id: String, pub original_file_id: String, pub file_name: String, pub file_hash: String, pub redaction_count: usize, } // ─── AI Conversation ───────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AiConversation { pub id: String, pub issue_id: String, pub provider: String, pub model: String, pub created_at: String, pub title: String, } impl AiConversation { pub fn new(issue_id: String, provider: String, model: String) -> Self { AiConversation { id: Uuid::now_v7().to_string(), issue_id, provider, model, created_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), title: "Untitled".to_string(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AiMessage { pub id: String, pub conversation_id: String, pub role: String, pub content: String, pub token_count: i64, pub created_at: String, } impl AiMessage { pub fn new(conversation_id: String, role: String, content: String) -> Self { AiMessage { id: Uuid::now_v7().to_string(), conversation_id, role, content, token_count: 0, created_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), } } } // ─── Resolution Step ───────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResolutionStep { pub id: String, pub issue_id: String, pub step_order: i64, pub why_question: String, pub answer: String, pub evidence: String, pub created_at: String, } impl ResolutionStep { pub fn new( issue_id: String, step_order: i64, why_question: String, answer: String, evidence: String, ) -> Self { ResolutionStep { id: Uuid::now_v7().to_string(), issue_id, step_order, why_question, answer, evidence, created_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), } } } // ─── Document ──────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Document { pub id: String, pub issue_id: String, pub doc_type: String, pub title: String, pub content_md: String, pub created_at: i64, pub updated_at: i64, } impl Document { pub fn new(issue_id: String, doc_type: String, title: String, content_md: String) -> Self { let now = chrono::Utc::now().timestamp_millis(); Document { id: Uuid::now_v7().to_string(), issue_id, doc_type, title, content_md, created_at: now, updated_at: now, } } } // ─── Audit ────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuditEntry { pub id: String, pub timestamp: String, pub action: String, pub entity_type: String, pub entity_id: String, pub user_id: String, pub details: String, } impl AuditEntry { pub fn new(action: String, entity_type: String, entity_id: String, details: String) -> Self { AuditEntry { id: Uuid::now_v7().to_string(), timestamp: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), action, entity_type, entity_id, user_id: "local".to_string(), details, } } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AuditFilter { pub action: Option, pub entity_type: Option, pub entity_id: Option, pub from_date: Option, pub to_date: Option, pub limit: Option, pub offset: Option, } // ─── Settings ─────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SettingRecord { pub key: String, pub value: String, pub updated_at: String, } // ─── Integrations ─────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Credential { pub id: String, pub service: String, pub token_hash: String, pub encrypted_token: String, pub created_at: String, pub expires_at: Option, } impl Credential { pub fn new(service: String, token_hash: String, encrypted_token: String) -> Self { Credential { id: Uuid::now_v7().to_string(), service, token_hash, encrypted_token, created_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), expires_at: None, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IntegrationConfig { pub id: String, pub service: String, pub base_url: String, pub username: Option, pub project_name: Option, pub space_key: Option, pub auto_create_enabled: bool, pub updated_at: String, } impl IntegrationConfig { pub fn new(service: String, base_url: String) -> Self { IntegrationConfig { id: Uuid::now_v7().to_string(), service, base_url, username: None, project_name: None, space_key: None, auto_create_enabled: false, updated_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), } } } // ─── Image Attachment ──────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImageAttachment { pub id: String, pub issue_id: String, pub file_name: String, pub file_path: String, pub file_size: i64, pub mime_type: String, pub upload_hash: String, pub uploaded_at: String, pub pii_warning_acknowledged: bool, pub is_paste: bool, } // ─── Attachment Summaries (cross-incident list views) ─────────────────────── /// Lightweight log-file row joined with the parent issue title. /// Returned by `list_all_log_files` — never contains the compressed content blob. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogFileSummary { pub id: String, pub issue_id: String, pub issue_title: String, pub file_name: String, pub file_path: String, pub file_size: i64, pub mime_type: String, pub content_hash: String, pub uploaded_at: String, pub redacted: bool, } /// Lightweight image-attachment row joined with the parent issue title. /// Returned by `list_all_image_attachments` — never contains the raw image bytes. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImageAttachmentSummary { pub id: String, pub issue_id: String, pub issue_title: String, pub file_name: String, pub file_path: String, pub file_size: i64, pub mime_type: String, pub upload_hash: String, pub uploaded_at: String, pub pii_warning_acknowledged: bool, pub is_paste: bool, } // ─── Kubernetes Cluster ───────────────────────────────────────────────────── /// Represents a Kubernetes cluster configuration stored in the database. /// The kubeconfig is referenced by kubeconfig_id (foreign key to kubeconfig_files table). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Cluster { pub id: String, pub name: String, pub context: String, pub server_url: Option, pub kubeconfig_id: String, pub created_at: i64, pub updated_at: i64, } impl Cluster { pub fn new( name: String, context: String, server_url: Option, kubeconfig_id: String, ) -> Self { let now = chrono::Utc::now().timestamp(); Cluster { id: Uuid::now_v7().to_string(), name, context, server_url, kubeconfig_id, created_at: now, updated_at: now, } } } /// Lightweight summary for cluster list views. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClusterSummary { pub id: String, pub name: String, pub context: String, pub server_url: String, pub created_at: i64, pub updated_at: i64, pub port_forward_count: i64, } // ─── Port Forward ─────────────────────────────────────────────────────────── /// Represents a port forwarding session for a Kubernetes cluster. /// The ports and local_ports are stored as JSON arrays of u16. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PortForward { pub id: String, pub cluster_id: String, pub namespace: String, pub pod: String, pub container: Option, pub ports: Vec, pub local_ports: Vec, pub status: String, pub error_message: Option, pub created_at: i64, pub updated_at: i64, } impl PortForward { pub fn new( cluster_id: String, namespace: String, pod: String, container: Option, ports: Vec, local_ports: Vec, ) -> Self { let now = chrono::Utc::now().timestamp(); PortForward { id: Uuid::now_v7().to_string(), cluster_id, namespace, pod, container, ports, local_ports, status: "Active".to_string(), error_message: None, created_at: now, updated_at: now, } } } /// Lightweight summary for port forward list views. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PortForwardSummary { pub id: String, pub cluster_id: String, pub cluster_name: String, pub namespace: String, pub pod: String, pub container: Option, pub ports: Vec, pub local_ports: Vec, pub status: String, pub created_at: i64, pub updated_at: i64, } /// Filter for listing clusters. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ClusterFilter { pub name: Option, pub context: Option, pub limit: Option, pub offset: Option, } /// Filter for listing port forwards. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct PortForwardFilter { pub cluster_id: Option, pub status: Option, pub namespace: Option, pub limit: Option, pub offset: Option, } /// New cluster data for creation. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewCluster { pub name: String, pub context: String, pub server_url: String, pub kubeconfig_content: String, } /// Update for existing cluster. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ClusterUpdate { pub name: Option, pub context: Option, pub server_url: Option, pub kubeconfig_content: Option, } /// New port forward data for creation. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewPortForward { pub cluster_id: String, pub namespace: String, pub pod: String, pub container: Option, pub ports: Vec, pub local_ports: Vec, } /// Update for existing port forward. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct PortForwardUpdate { pub status: Option, pub error_message: Option, } impl ImageAttachment { #[allow(clippy::too_many_arguments)] pub fn new( issue_id: String, file_name: String, file_path: String, file_size: i64, mime_type: String, upload_hash: String, pii_warning_acknowledged: bool, is_paste: bool, ) -> Self { ImageAttachment { id: Uuid::now_v7().to_string(), issue_id, file_name, file_path, file_size, mime_type, upload_hash, uploaded_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), pii_warning_acknowledged, is_paste, } } }