//! Mailing DTOs (Data Transfer Objects)
//!
//! Generated by metaphor-schema. Do not edit manually.
//!
//! DTOs provide a clean separation between domain entities and API
//! representations, with validation and OpenAPI documentation support.
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[cfg(feature = "openapi")]
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
#[cfg(feature = "validation")]
use validator::Validate;
use crate::domain::entity::Mailing;
use crate::domain::entity::AuditMetadata;
use crate::domain::entity::MailingScheduleType;
use crate::domain::entity::MailingState;
use crate::domain::entity::MailingTargetModel;
use crate::domain::entity::MailingType;
// =============================================================================
// Create DTO
// =============================================================================
/// Request DTO for creating a new Mailing
///
/// Used in POST requests to create new entities.
/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "validation", derive(Validate))]
#[serde(rename_all = "camelCase")]
pub struct CreateMailingDto {
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub subject: String,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub preview: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "body_html")]
pub body_html: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "body_plaintext")]
pub body_plaintext: Option<String>,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "email_from")]
pub email_from: String,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[serde(default, skip_serializing_if = "Option::is_none", alias = "reply_to")]
pub reply_to: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "keep_archives")]
pub keep_archives: bool,
pub state: MailingState,
#[serde(alias = "schedule_type")]
pub schedule_type: MailingScheduleType,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "schedule_date")]
pub schedule_date: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "sent_date")]
pub sent_date: Option<DateTime<Utc>>,
#[serde(alias = "mailing_type")]
pub mailing_type: MailingType,
#[serde(alias = "target_model")]
pub target_model: MailingTargetModel,
#[serde(alias = "mailing_domain")]
pub mailing_domain: serde_json::Value,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "use_exclusion_list")]
pub use_exclusion_list: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "campaign_id")]
pub campaign_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "medium_id")]
pub medium_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "source_id")]
pub source_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "user_id")]
pub user_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "ab_testing_enabled")]
pub ab_testing_enabled: bool,
#[cfg_attr(feature = "openapi", schema(example = 42))]
#[serde(alias = "ab_testing_pc")]
pub ab_testing_pc: i32,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "ab_test_id")]
pub ab_test_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "kpi_mail_required")]
pub kpi_mail_required: bool,
}
// =============================================================================
// Update DTO
// =============================================================================
/// Request DTO for full update of a Mailing
///
/// Used in PUT requests for full entity replacement.
/// All fields are required (except auto-generated ones).
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "validation", derive(Validate))]
#[serde(rename_all = "camelCase")]
pub struct UpdateMailingDto {
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub subject: String,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub preview: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "body_html")]
pub body_html: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "body_plaintext")]
pub body_plaintext: Option<String>,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "email_from")]
pub email_from: String,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[serde(default, skip_serializing_if = "Option::is_none", alias = "reply_to")]
pub reply_to: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "keep_archives")]
pub keep_archives: bool,
pub state: MailingState,
#[serde(alias = "schedule_type")]
pub schedule_type: MailingScheduleType,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "schedule_date")]
pub schedule_date: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "sent_date")]
pub sent_date: Option<DateTime<Utc>>,
#[serde(alias = "mailing_type")]
pub mailing_type: MailingType,
#[serde(alias = "target_model")]
pub target_model: MailingTargetModel,
#[serde(alias = "mailing_domain")]
pub mailing_domain: serde_json::Value,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "use_exclusion_list")]
pub use_exclusion_list: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "campaign_id")]
pub campaign_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "medium_id")]
pub medium_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "source_id")]
pub source_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "user_id")]
pub user_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "ab_testing_enabled")]
pub ab_testing_enabled: bool,
#[cfg_attr(feature = "openapi", schema(example = 42))]
#[serde(alias = "ab_testing_pc")]
pub ab_testing_pc: i32,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "ab_test_id")]
pub ab_test_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "kpi_mail_required")]
pub kpi_mail_required: bool,
}
// =============================================================================
// Patch DTO
// =============================================================================
/// Request DTO for partial update of a Mailing
///
/// Used in PATCH requests for partial updates.
/// All fields are optional - only provided fields will be updated.
#[derive(Debug, Clone, Default, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "validation", derive(Validate))]
#[serde(rename_all = "camelCase")]
pub struct PatchMailingDto {
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "body_html")]
pub body_html: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "body_plaintext")]
pub body_plaintext: Option<String>,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "email_from")]
pub email_from: Option<String>,
#[cfg_attr(feature = "validation", validate(length(max = 255)))]
#[serde(skip_serializing_if = "Option::is_none", alias = "reply_to")]
pub reply_to: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "keep_archives")]
pub keep_archives: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<MailingState>,
#[serde(skip_serializing_if = "Option::is_none", alias = "schedule_type")]
pub schedule_type: Option<MailingScheduleType>,
#[serde(skip_serializing_if = "Option::is_none", alias = "schedule_date")]
pub schedule_date: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "sent_date")]
pub sent_date: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "mailing_type")]
pub mailing_type: Option<MailingType>,
#[serde(skip_serializing_if = "Option::is_none", alias = "target_model")]
pub target_model: Option<MailingTargetModel>,
#[serde(skip_serializing_if = "Option::is_none", alias = "mailing_domain")]
pub mailing_domain: Option<serde_json::Value>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "use_exclusion_list")]
pub use_exclusion_list: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", alias = "campaign_id")]
pub campaign_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "medium_id")]
pub medium_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "source_id")]
pub source_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "user_id")]
pub user_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "ab_testing_enabled")]
pub ab_testing_enabled: Option<bool>,
#[cfg_attr(feature = "openapi", schema(example = 42))]
#[serde(skip_serializing_if = "Option::is_none", alias = "ab_testing_pc")]
pub ab_testing_pc: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none", alias = "ab_test_id")]
pub ab_test_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "kpi_mail_required")]
pub kpi_mail_required: Option<bool>,
}
impl PatchMailingDto {
/// Check if any field is set
pub fn has_changes(&self) -> bool {
self.subject.is_some() || self.preview.is_some() || self.body_html.is_some() || self.body_plaintext.is_some() || self.email_from.is_some() || self.reply_to.is_some() || self.keep_archives.is_some() || self.state.is_some() || self.schedule_type.is_some() || self.schedule_date.is_some() || self.sent_date.is_some() || self.mailing_type.is_some() || self.target_model.is_some() || self.mailing_domain.is_some() || self.use_exclusion_list.is_some() || self.campaign_id.is_some() || self.medium_id.is_some() || self.source_id.is_some() || self.user_id.is_some() || self.ab_testing_enabled.is_some() || self.ab_testing_pc.is_some() || self.ab_test_id.is_some() || self.kpi_mail_required.is_some()
}
}
// =============================================================================
// Response DTO
// =============================================================================
/// Response DTO for Mailing entity
///
/// Used in API responses for single entity.
/// Includes all fields including metadata.
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct MailingResponseDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub subject: String,
pub preview: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub body_html: String,
pub body_plaintext: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub email_from: String,
pub reply_to: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub keep_archives: bool,
pub state: MailingState,
pub schedule_type: MailingScheduleType,
pub schedule_date: Option<DateTime<Utc>>,
pub sent_date: Option<DateTime<Utc>>,
pub mailing_type: MailingType,
pub target_model: MailingTargetModel,
pub mailing_domain: serde_json::Value,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub use_exclusion_list: bool,
pub campaign_id: Option<Uuid>,
pub medium_id: Option<Uuid>,
pub source_id: Option<Uuid>,
pub user_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub ab_testing_enabled: bool,
#[cfg_attr(feature = "openapi", schema(example = 42))]
pub ab_testing_pc: i32,
pub ab_test_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub kpi_mail_required: bool,
pub metadata: AuditMetadata,
}
// =============================================================================
// List Response DTO
// =============================================================================
/// Paginated list response for Mailing entities
///
/// Used in API responses for list endpoints.
/// Includes pagination metadata.
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct MailingListResponseDto {
/// List of items
pub items: Vec<MailingResponseDto>,
/// Total number of items (across all pages)
pub total: u64,
/// Current page number (1-indexed)
pub page: u32,
/// Number of items per page
pub per_page: u32,
/// Total number of pages
pub total_pages: u32,
/// Whether there is a next page
pub has_next: bool,
/// Whether there is a previous page
pub has_prev: bool,
}
impl MailingListResponseDto {
/// Create a new list response from items and pagination info
pub fn new(items: Vec<MailingResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
let total_pages = if per_page > 0 {
((total as f64) / (per_page as f64)).ceil() as u32
} else {
0
};
Self {
items,
total,
page,
per_page,
total_pages,
has_next: page < total_pages,
has_prev: page > 1,
}
}
}
/// Summary DTO for list views (compact version)
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct MailingSummaryDto {
pub id: Uuid,
pub subject: String,
pub preview: Option<String>,
pub body_html: String,
pub created_at: Option<DateTime<Utc>>,
}
// =============================================================================
// Conversions
// =============================================================================
impl From<Mailing> for MailingResponseDto {
fn from(entity: Mailing) -> Self {
Self {
id: entity.id,
subject: entity.subject,
preview: entity.preview,
body_html: entity.body_html,
body_plaintext: entity.body_plaintext,
email_from: entity.email_from,
reply_to: entity.reply_to,
keep_archives: entity.keep_archives,
state: entity.state,
schedule_type: entity.schedule_type,
schedule_date: entity.schedule_date,
sent_date: entity.sent_date,
mailing_type: entity.mailing_type,
target_model: entity.target_model,
mailing_domain: entity.mailing_domain,
use_exclusion_list: entity.use_exclusion_list,
campaign_id: entity.campaign_id,
medium_id: entity.medium_id,
source_id: entity.source_id,
user_id: entity.user_id,
ab_testing_enabled: entity.ab_testing_enabled,
ab_testing_pc: entity.ab_testing_pc,
ab_test_id: entity.ab_test_id,
kpi_mail_required: entity.kpi_mail_required,
metadata: entity.metadata,
}
}
}
impl From<Mailing> for MailingSummaryDto {
fn from(entity: Mailing) -> Self {
let created_at = backbone_core::PersistentEntity::created_at(&entity);
Self {
id: entity.id,
subject: entity.subject,
preview: entity.preview,
body_html: entity.body_html,
created_at,
}
}
}
impl From<CreateMailingDto> for Mailing {
fn from(dto: CreateMailingDto) -> Self {
Self {
id: Uuid::new_v4(),
subject: dto.subject,
preview: dto.preview,
body_html: dto.body_html,
body_plaintext: dto.body_plaintext,
email_from: dto.email_from,
reply_to: dto.reply_to,
keep_archives: dto.keep_archives,
state: dto.state,
schedule_type: dto.schedule_type,
schedule_date: dto.schedule_date,
sent_date: dto.sent_date,
mailing_type: dto.mailing_type,
target_model: dto.target_model,
mailing_domain: dto.mailing_domain,
use_exclusion_list: dto.use_exclusion_list,
campaign_id: dto.campaign_id,
medium_id: dto.medium_id,
source_id: dto.source_id,
user_id: dto.user_id,
ab_testing_enabled: dto.ab_testing_enabled,
ab_testing_pc: dto.ab_testing_pc,
ab_test_id: dto.ab_test_id,
kpi_mail_required: dto.kpi_mail_required,
metadata: AuditMetadata::default(),
}
}
}
impl From<&Mailing> for MailingResponseDto {
fn from(entity: &Mailing) -> Self {
Self {
id: entity.id.clone(),
subject: entity.subject.clone(),
preview: entity.preview.clone(),
body_html: entity.body_html.clone(),
body_plaintext: entity.body_plaintext.clone(),
email_from: entity.email_from.clone(),
reply_to: entity.reply_to.clone(),
keep_archives: entity.keep_archives.clone(),
state: entity.state.clone(),
schedule_type: entity.schedule_type.clone(),
schedule_date: entity.schedule_date.clone(),
sent_date: entity.sent_date.clone(),
mailing_type: entity.mailing_type.clone(),
target_model: entity.target_model.clone(),
mailing_domain: entity.mailing_domain.clone(),
use_exclusion_list: entity.use_exclusion_list.clone(),
campaign_id: entity.campaign_id.clone(),
medium_id: entity.medium_id.clone(),
source_id: entity.source_id.clone(),
user_id: entity.user_id.clone(),
ab_testing_enabled: entity.ab_testing_enabled.clone(),
ab_testing_pc: entity.ab_testing_pc.clone(),
ab_test_id: entity.ab_test_id.clone(),
kpi_mail_required: entity.kpi_mail_required.clone(),
metadata: entity.metadata.clone(),
}
}
}
impl backbone_core::FromCreateDto<CreateMailingDto> for Mailing {
fn from_create_dto(dto: CreateMailingDto) -> backbone_core::ServiceResult<Self> {
Ok(Mailing::from(dto))
}
}
impl backbone_core::ApplyUpdateDto<UpdateMailingDto> for Mailing {
fn apply_update(mut self, dto: UpdateMailingDto) -> backbone_core::ServiceResult<Self> {
self.subject = dto.subject;
self.preview = dto.preview;
self.body_html = dto.body_html;
self.body_plaintext = dto.body_plaintext;
self.email_from = dto.email_from;
self.reply_to = dto.reply_to;
self.keep_archives = dto.keep_archives;
self.state = dto.state;
self.schedule_type = dto.schedule_type;
self.schedule_date = dto.schedule_date;
self.sent_date = dto.sent_date;
self.mailing_type = dto.mailing_type;
self.target_model = dto.target_model;
self.mailing_domain = dto.mailing_domain;
self.use_exclusion_list = dto.use_exclusion_list;
self.campaign_id = dto.campaign_id;
self.medium_id = dto.medium_id;
self.source_id = dto.source_id;
self.user_id = dto.user_id;
self.ab_testing_enabled = dto.ab_testing_enabled;
self.ab_testing_pc = dto.ab_testing_pc;
self.ab_test_id = dto.ab_test_id;
self.kpi_mail_required = dto.kpi_mail_required;
Ok(self)
}
}
// =============================================================================
// Custom DTOs
// =============================================================================
// <<< CUSTOM DTOs
// Add custom DTOs specific to Mailing here.
// This section will be preserved during regeneration.
// >>> END CUSTOM DTOs