use crate::db::entity; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use validator::Validate; #[derive(Debug, Serialize, Deserialize)] pub struct ApiResponse { pub success: bool, pub data: Option, pub error: Option, pub message: Option, } impl ApiResponse { pub fn success(data: T) -> Self { Self { success: true, data: Some(data), error: None, message: None, } } pub fn error(error: String) -> Self { Self { success: false, data: None, error: Some(error), message: None, } } pub fn message(message: String) -> Self { Self { success: true, data: None, error: None, message: Some(message), } } } #[derive(Debug, Serialize, Deserialize)] pub struct PaginatedResponse { pub items: Vec, pub total: u64, pub page: u32, pub per_page: u32, pub total_pages: u32, } #[derive(Debug, Serialize, Deserialize)] pub struct PaginationParams { pub page: Option, pub per_page: Option, } impl Default for PaginationParams { fn default() -> Self { Self { page: Some(1), per_page: Some(20), } } } #[derive(Debug, Serialize, Deserialize, Validate)] #[validate(schema(function = "validate_contact_info", skip_on_field_errors = false))] pub struct CreateNodeRequest { #[validate(length(min = 1, max = 100))] pub name: String, #[validate(length(min = 1, max = 255))] pub host: String, #[validate(range(min = 1, max = 65535))] pub port: i32, #[validate(length(min = 1, max = 20))] pub protocol: String, #[validate(length(max = 500))] pub description: Option, #[validate(range(min = 1, max = 10000))] pub max_connections: i32, pub allow_relay: bool, #[validate(length(min = 1, max = 100))] pub network_name: String, #[validate(length(max = 100))] pub network_secret: Option, // 联系方式字段 #[validate(length(max = 20))] pub qq_number: Option, #[validate(length(max = 50))] pub wechat: Option, #[validate(email)] pub mail: Option, } // 自定义验证函数:确保至少填写一种联系方式 fn validate_contact_info(request: &CreateNodeRequest) -> Result<(), validator::ValidationError> { let has_qq = request .qq_number .as_ref() .is_some_and(|s| !s.trim().is_empty()); let has_wechat = request .wechat .as_ref() .is_some_and(|s| !s.trim().is_empty()); let has_mail = request.mail.as_ref().is_some_and(|s| !s.trim().is_empty()); if !has_qq && !has_wechat && !has_mail { return Err(validator::ValidationError::new("contact_required")); } Ok(()) } #[derive(Debug, Serialize, Deserialize, Validate)] pub struct UpdateNodeRequest { #[validate(length(min = 1, max = 100))] pub name: Option, #[validate(length(min = 1, max = 255))] pub host: Option, #[validate(range(min = 1, max = 65535))] pub port: Option, #[validate(length(min = 1, max = 20))] pub protocol: Option, #[validate(length(max = 500))] pub description: Option, #[validate(range(min = 1, max = 10000))] pub max_connections: Option, pub is_active: Option, pub allow_relay: Option, #[validate(length(min = 1, max = 100))] pub network_name: Option, #[validate(length(max = 100))] pub network_secret: Option, // 联系方式字段 #[validate(length(max = 20))] pub qq_number: Option, #[validate(length(max = 50))] pub wechat: Option, #[validate(email)] pub mail: Option, // 标签字段(仅管理员可用) pub tags: Option>, } #[derive(Debug, Serialize, Deserialize)] pub struct NodeResponse { pub id: i32, pub name: String, pub host: String, pub port: i32, pub protocol: String, pub version: Option, pub description: Option, pub max_connections: i32, pub current_connections: i32, pub is_active: bool, pub is_approved: bool, pub allow_relay: bool, pub network_name: Option, pub network_secret: Option, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub address: String, pub usage_percentage: f64, // 健康状态相关字段 pub current_health_status: Option, pub last_check_time: Option>, pub last_response_time: Option, pub health_percentage_24h: Option, pub health_record_total_counter_ring: Vec, pub health_record_healthy_counter_ring: Vec, pub ring_granularity: u32, // 联系方式字段 pub qq_number: Option, pub wechat: Option, pub mail: Option, pub tags: Vec, } impl From for NodeResponse { fn from(node: entity::shared_nodes::Model) -> Self { Self { id: node.id, name: node.name.clone(), host: node.host.clone(), port: node.port, protocol: node.protocol.clone(), version: Some(node.version.clone()), description: Some(node.description.clone()), max_connections: node.max_connections, current_connections: node.current_connections, is_active: node.is_active, is_approved: node.is_approved, allow_relay: node.allow_relay, network_name: Some(node.network_name.clone()), network_secret: Some(node.network_secret.clone()), created_at: node.created_at.into(), updated_at: node.updated_at.into(), address: format!("{}://{}:{}", node.protocol, node.host, node.port), usage_percentage: node.current_connections as f64 / node.max_connections as f64 * 100.0, // 健康状态字段初始化为 None,将在 handlers 中填充 current_health_status: None, last_check_time: None, last_response_time: None, health_percentage_24h: None, health_record_healthy_counter_ring: Vec::new(), health_record_total_counter_ring: Vec::new(), ring_granularity: 0, // 联系方式字段 qq_number: if node.qq_number.is_empty() { None } else { Some(node.qq_number) }, wechat: if node.wechat.is_empty() { None } else { Some(node.wechat) }, mail: if node.mail.is_empty() { None } else { Some(node.mail) }, tags: Vec::new(), } } } #[derive(Debug, Serialize, Deserialize)] pub struct HealthRecordResponse { pub id: i32, pub node_id: i32, pub status: String, pub response_time: Option, pub error_message: Option, pub checked_at: chrono::DateTime, } impl From for HealthRecordResponse { fn from(record: entity::health_records::Model) -> Self { Self { id: record.id, node_id: record.node_id, status: record.status.to_string(), response_time: Some(record.response_time), error_message: Some(record.error_message), checked_at: record.checked_at.into(), } } } pub type HealthStatsResponse = crate::db::HealthStats; #[derive(Debug, Serialize, Deserialize)] pub struct NodeFilterParams { pub is_active: Option, pub protocol: Option, pub search: Option, #[serde(default)] pub tags: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct HealthFilterParams { pub status: Option, pub since: Option>, } // 管理员相关模型 #[derive(Debug, Serialize, Deserialize, Validate)] pub struct AdminLoginRequest { #[validate(length(min = 1))] pub password: String, } #[derive(Debug, Serialize, Deserialize)] pub struct AdminLoginResponse { pub token: String, pub expires_at: DateTime, } #[derive(Debug, Serialize, Deserialize)] pub struct ApproveNodeRequest { pub approved: bool, } #[derive(Debug, Serialize, Deserialize)] pub struct AdminNodeFilterParams { pub is_active: Option, pub is_approved: Option, pub protocol: Option, pub search: Option, pub tag: Option, pub tags: Option>, }