use chrono::{DateTime, Utc}; use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; use surrealdb::sql::Thing; use utoipa::ToSchema; use validator::{Validate, ValidationError}; // Custom URL validator that ensures valid HTTP/HTTPS URLs pub fn validate_url(url: &str) -> Result<(), ValidationError> { lazy_static! { static ref VALID_URL_REGEX: Regex = Regex::new(r"^https?://[^\s$.?#].[^\s]*$").unwrap(); } if VALID_URL_REGEX.is_match(url) { Ok(()) } else { Err(ValidationError::new("invalid_url")) } } // Custom validator for future dates pub fn validate_future_date(end_date: &DateTime) -> Result<(), ValidationError> { let now = Utc::now(); if end_date > &now { Ok(()) } else { Err(ValidationError::new("future_date")) } } // Custom validator for event date ranges (for combined validation) pub fn validate_date_range(start_date: &DateTime, end_date: &DateTime) -> Result<(), ValidationError> { if start_date <= end_date { Ok(()) } else { Err(ValidationError::new("date_range")) } } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct EventsCreateRequestDto { #[validate(length(min = 1, max = 100, message = "Name must be between 1 and 100 characters"))] pub name: String, #[validate(length(min = 1, max = 1000, message = "Description must be between 1 and 1000 characters"))] pub description: String, #[validate(custom( function = "validate_url", message = "Detail link must be a valid HTTP/HTTPS URL" ))] pub detail_link: String, #[validate(range(min = 0.0, max = 1_000_000.0, message = "Price must be between 0 and 1,000,000"))] pub price: f64, #[schema(example = "2025-09-20T13:00:00Z", value_type = String)] #[validate(custom( function = "validate_future_date", message = "End date must be in the future" ))] pub end_date: DateTime, #[schema(example = "2025-09-20T13:00:00Z", value_type = String)] pub start_date: DateTime, #[validate(length(max = 200, message = "Location name cannot exceed 200 characters"))] pub location: Option, pub is_online: bool, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct EventsUpdateRequestDto { #[validate(length(min = 1, message = "Name is required"))] pub name: String, #[schema(example = "2025-09-20T13:00:00Z", value_type = String)] pub end_date: DateTime, #[schema(example = "2025-09-20T13:00:00Z", value_type = String)] pub start_date: DateTime, #[validate(range(min = 0.0, message = "Price cannot be negative"))] pub price: f64, pub is_online: bool, pub description: String, #[validate(url(message = "Detail link must be a valid URL"))] pub detail_link: String, pub location: Option, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct EventsListItemDto { pub id: String, pub name: String, pub description: String, pub detail_link: String, pub price: f64, pub is_online: bool, pub start_date: String, pub end_date: String, pub created_at: String, pub location: Option, pub is_deleted: bool, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct EventsDetailItemDto { pub id: String, pub name: String, pub description: String, pub detail_link: String, pub price: f64, pub is_online: bool, pub start_date: String, pub end_date: String, pub created_at: String, pub updated_at: String, pub location: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EventsQueryDto { pub id: Thing, pub name: String, pub description: String, pub detail_link: String, pub price: f64, pub is_online: bool, pub is_deleted: bool, pub start_date: String, pub end_date: String, pub created_at: String, pub updated_at: String, pub location: Option, } impl EventsQueryDto { pub fn from(self) -> EventsListItemDto { EventsListItemDto { id: self.id.id.to_raw(), name: self.name, description: self.description, detail_link: self.detail_link, price: self.price, location: self.location, is_online: self.is_online, start_date: self.start_date, end_date: self.end_date, created_at: self.created_at, is_deleted: self.is_deleted, } } }