use chrono::{DateTime, NaiveDate, Utc}; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; use crate::utils::datetime::DateTimeWithTimezone; use crate::utils::sanitize::{SanitizeOutput, sanitize_string, sanitize_option_string}; pub mod media; #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct User { pub id: Uuid, pub username: String, // NOT NULL pub email: Option, // nullable pub name: Option, // nullable pub avatar_url: Option, // nullable pub role: Option, // nullable (has default) pub verified: Option, // nullable (has default) pub created_at: Option>, // nullable (has default) pub updated_at: Option>, // nullable (has default) } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Member { pub id: Uuid, pub first_name: String, pub last_name: String, pub email: Option, pub phone: Option, pub address: Option, pub date_of_birth: Option, pub membership_status: Option, pub join_date: Option, pub baptism_date: Option, pub notes: Option, pub emergency_contact_name: Option, pub emergency_contact_phone: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Deserialize)] pub struct CreateMemberRequest { pub first_name: String, pub last_name: String, pub email: Option, pub phone: Option, pub address: Option, pub date_of_birth: Option, pub membership_status: Option, pub join_date: Option, pub baptism_date: Option, pub notes: Option, pub emergency_contact_name: Option, pub emergency_contact_phone: Option, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Bulletin { pub id: Uuid, pub title: String, pub date: NaiveDate, pub url: Option, pub pdf_url: Option, pub is_active: Option, pub pdf_file: Option, pub sabbath_school: Option, pub divine_worship: Option, pub scripture_reading: Option, pub sunset: Option, pub cover_image: Option, pub pdf_path: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Event { pub id: Uuid, pub title: String, pub description: String, pub start_time: DateTime, pub end_time: DateTime, pub location: String, pub location_url: Option, pub image: Option, pub thumbnail: Option, pub category: String, pub is_featured: Option, pub recurring_type: Option, pub approved_from: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct PendingEvent { pub id: Uuid, pub title: String, // NOT NULL pub description: String, // NOT NULL pub start_time: DateTime, // NOT NULL pub end_time: DateTime, // NOT NULL pub location: String, // NOT NULL pub location_url: Option, // nullable pub image: Option, // nullable pub thumbnail: Option, // nullable pub category: String, // NOT NULL pub is_featured: Option, // nullable (has default) pub recurring_type: Option, // nullable pub approval_status: Option, // nullable (has default) pub submitted_at: Option>, // nullable (has default) pub bulletin_week: String, // NOT NULL pub admin_notes: Option, // nullable pub submitter_email: Option, // nullable pub email_sent: Option, // nullable (has default) pub pending_email_sent: Option, // nullable (has default) pub rejection_email_sent: Option, // nullable (has default) pub approval_email_sent: Option, // nullable (has default) pub created_at: Option>, // nullable (has default) pub updated_at: Option>, // nullable (has default) } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ChurchConfig { pub id: Uuid, pub church_name: String, pub contact_email: String, pub contact_phone: Option, pub church_address: String, pub po_box: Option, pub google_maps_url: Option, pub about_text: String, pub api_keys: Option, pub jellyfin_server_url: Option, pub brand_color: String, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Serialize)] pub struct ApiResponse { pub success: bool, pub data: Option, pub message: Option, } #[derive(Debug, Deserialize)] pub struct LoginRequest { pub username: String, pub password: String, } #[derive(Debug, Serialize)] pub struct LoginResponse { pub token: String, pub user: User, } #[derive(Debug, Deserialize)] pub struct CreateBulletinRequest { pub title: String, pub date: NaiveDate, pub url: Option, pub cover_image: Option, pub sabbath_school: Option, pub divine_worship: Option, pub scripture_reading: Option, pub sunset: Option, pub is_active: Option, } #[derive(Debug, Clone, Deserialize)] pub struct CreateEventRequest { pub title: String, pub description: String, pub start_time: DateTime, pub end_time: DateTime, pub location: String, pub location_url: Option, pub category: String, pub is_featured: Option, pub recurring_type: Option, } #[derive(Debug, Deserialize)] pub struct SubmitEventRequest { pub title: String, pub description: String, pub start_time: DateTime, pub end_time: DateTime, pub location: String, pub location_url: Option, pub category: String, pub is_featured: Option, pub recurring_type: Option, pub bulletin_week: String, pub submitter_email: Option, pub image: Option, pub thumbnail: Option, } #[derive(Debug, Serialize)] pub struct PaginatedResponse { pub items: Vec, pub total: i64, pub page: i32, pub per_page: i32, pub has_more: bool, } // Hymnal-specific paginated response with "results" field #[derive(Debug, Serialize)] pub struct HymnalPaginatedResponse { pub results: Vec, pub total: i64, pub page: i32, pub per_page: i32, pub has_more: bool, } #[derive(Debug, Deserialize)] pub struct PaginationParams { pub page: Option, pub per_page: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct BibleVerse { pub id: Uuid, pub reference: String, pub text: String, pub is_active: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Serialize, Deserialize)] pub struct Personnel { pub ss_leader: String, pub ss_teacher: String, pub mission_story: String, pub song_leader: String, pub announcements: String, pub offering: String, pub special_music: String, pub speaker: String, } #[derive(Debug, Serialize, Deserialize)] pub struct ScheduleData { pub date: String, pub personnel: Personnel, } #[derive(Debug, Serialize, Deserialize)] pub struct ConferenceData { pub date: String, pub offering_focus: String, pub sunset_tonight: String, pub sunset_next_friday: String, } #[derive(Debug, Deserialize)] pub struct DateQuery { pub date: Option, } // Database model for schedule #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Schedule { pub id: Uuid, pub date: NaiveDate, pub song_leader: Option, pub ss_teacher: Option, pub ss_leader: Option, pub mission_story: Option, pub special_program: Option, pub sermon_speaker: Option, pub scripture: Option, pub offering: Option, pub deacons: Option, pub special_music: Option, pub childrens_story: Option, pub afternoon_program: Option, pub created_at: Option>, pub updated_at: Option>, } // Contact form models #[derive(Debug, Serialize, Deserialize)] pub struct ContactRequest { pub first_name: String, pub last_name: String, pub email: String, pub phone: Option, pub message: String, pub subject: Option, } #[derive(Debug)] pub struct Contact { pub first_name: String, pub last_name: String, pub email: String, pub phone: Option, pub message: String, } #[derive(Debug)] pub struct ContactEmail { pub first_name: String, pub last_name: String, pub email: String, pub phone: Option, pub message: String, pub subject: Option, } // V2 Contact form models - simplified with just 'name' #[derive(Debug, Serialize, Deserialize)] pub struct ContactRequestV2 { pub name: String, pub email: String, pub phone: Option, pub message: String, pub subject: Option, } // V2 API Models with enhanced timezone handling #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EventV2 { pub id: Uuid, pub title: String, pub description: String, pub start_time: DateTimeWithTimezone, pub end_time: DateTimeWithTimezone, pub location: String, pub location_url: Option, pub image: Option, pub thumbnail: Option, pub category: String, pub is_featured: Option, pub recurring_type: Option, pub timezone: String, pub approved_from: Option, pub created_at: Option, pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PendingEventV2 { pub id: Uuid, pub title: String, pub description: String, pub start_time: DateTimeWithTimezone, pub end_time: DateTimeWithTimezone, pub location: String, pub location_url: Option, pub image: Option, pub thumbnail: Option, pub category: String, pub is_featured: Option, pub recurring_type: Option, pub timezone: String, pub approval_status: Option, pub submitted_at: Option, pub bulletin_week: String, pub admin_notes: Option, pub submitter_email: Option, pub email_sent: Option, pub pending_email_sent: Option, pub rejection_email_sent: Option, pub approval_email_sent: Option, pub created_at: Option, pub updated_at: Option, } #[derive(Debug, Deserialize)] pub struct CreateEventRequestV2 { pub title: String, pub description: String, pub start_time: String, pub end_time: String, pub location: String, pub location_url: Option, pub category: String, pub is_featured: Option, pub recurring_type: Option, pub timezone: Option, } #[derive(Debug, Deserialize)] pub struct SubmitEventRequestV2 { pub title: String, pub description: String, pub start_time: String, pub end_time: String, pub location: String, pub location_url: Option, pub category: String, pub is_featured: Option, pub recurring_type: Option, pub bulletin_week: String, pub submitter_email: Option, pub timezone: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BulletinV2 { pub id: Uuid, pub title: String, pub date: NaiveDate, pub url: Option, pub cover_image: Option, pub cover_image_url: Option, pub pdf_url: Option, pub is_active: Option, pub pdf_file: Option, pub sabbath_school: Option, pub divine_worship: Option, pub scripture_reading: Option, pub sunset: Option, pub pdf_path: Option, pub created_at: Option, pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScheduleV2 { pub id: Uuid, pub date: NaiveDate, pub song_leader: Option, pub ss_teacher: Option, pub ss_leader: Option, pub mission_story: Option, pub special_program: Option, pub sermon_speaker: Option, pub scripture: Option, pub offering: Option, pub deacons: Option, pub special_music: Option, pub childrens_story: Option, pub afternoon_program: Option, pub created_at: Option, pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BibleVerseV2 { pub id: Uuid, pub reference: String, pub text: String, pub is_active: Option, pub created_at: Option, pub updated_at: Option, } // Hymnal Models #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Hymnal { pub id: Uuid, pub name: String, pub code: String, pub description: Option, pub year: Option, pub language: Option, pub is_active: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Hymn { pub id: Uuid, pub hymnal_id: Uuid, pub number: i32, pub title: String, pub content: String, pub is_favorite: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct HymnWithHymnal { pub id: Uuid, pub hymnal_id: Uuid, pub hymnal_name: String, pub hymnal_code: String, pub hymnal_year: Option, pub number: i32, pub title: String, pub content: String, pub is_favorite: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ThematicList { pub id: Uuid, pub hymnal_id: Uuid, pub name: String, pub sort_order: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ThematicAmbit { pub id: Uuid, pub thematic_list_id: Uuid, pub name: String, pub start_number: i32, pub end_number: i32, pub sort_order: Option, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ThematicListWithAmbits { pub id: Uuid, pub hymnal_id: Uuid, pub name: String, pub sort_order: Option, pub ambits: Vec, pub created_at: Option>, pub updated_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ResponsiveReading { pub id: Uuid, pub number: i32, pub title: String, pub content: String, pub is_favorite: Option, pub created_at: Option>, pub updated_at: Option>, } // Hymnal search result structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SearchResult { pub hymn: HymnWithHymnal, pub score: f64, pub match_type: String, } // Hymnal request/query types #[derive(Debug, Deserialize)] pub struct HymnSearchQuery { pub q: Option, pub hymnal: Option, pub number: Option, pub theme: Option, } #[derive(Debug, Deserialize)] pub struct ResponsiveReadingQuery { pub q: Option, pub number: Option, } // SanitizeOutput trait implementations impl SanitizeOutput for Bulletin { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.sabbath_school = sanitize_option_string(self.sabbath_school); self.divine_worship = sanitize_option_string(self.divine_worship); self.scripture_reading = sanitize_option_string(self.scripture_reading); self.sunset = sanitize_option_string(self.sunset); self } } impl SanitizeOutput for BulletinV2 { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.sabbath_school = sanitize_option_string(self.sabbath_school); self.divine_worship = sanitize_option_string(self.divine_worship); self.scripture_reading = sanitize_option_string(self.scripture_reading); self.sunset = sanitize_option_string(self.sunset); self } } impl SanitizeOutput for Event { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.description = sanitize_string(self.description); self.location = sanitize_string(self.location); self.category = sanitize_string(self.category); self.location_url = sanitize_option_string(self.location_url); self.approved_from = sanitize_option_string(self.approved_from); self } } impl SanitizeOutput for EventV2 { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.description = sanitize_string(self.description); self.location = sanitize_string(self.location); self.category = sanitize_string(self.category); self.location_url = sanitize_option_string(self.location_url); self.approved_from = sanitize_option_string(self.approved_from); self } } impl SanitizeOutput for PendingEvent { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.description = sanitize_string(self.description); self.location = sanitize_string(self.location); self.category = sanitize_string(self.category); self.location_url = sanitize_option_string(self.location_url); self.admin_notes = sanitize_option_string(self.admin_notes); self.submitter_email = sanitize_option_string(self.submitter_email); self.bulletin_week = sanitize_string(self.bulletin_week); self } } impl SanitizeOutput for PendingEventV2 { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.description = sanitize_string(self.description); self.location = sanitize_string(self.location); self.category = sanitize_string(self.category); self.location_url = sanitize_option_string(self.location_url); self.admin_notes = sanitize_option_string(self.admin_notes); self.submitter_email = sanitize_option_string(self.submitter_email); self.bulletin_week = sanitize_string(self.bulletin_week); self } } impl SanitizeOutput for BibleVerse { fn sanitize_output(mut self) -> Self { self.reference = sanitize_string(self.reference); self.text = sanitize_string(self.text); self } } impl SanitizeOutput for BibleVerseV2 { fn sanitize_output(mut self) -> Self { self.reference = sanitize_string(self.reference); self.text = sanitize_string(self.text); self } } impl SanitizeOutput for Member { fn sanitize_output(mut self) -> Self { self.first_name = sanitize_string(self.first_name); self.last_name = sanitize_string(self.last_name); self.address = sanitize_option_string(self.address); self.notes = sanitize_option_string(self.notes); self.emergency_contact_name = sanitize_option_string(self.emergency_contact_name); self.membership_status = sanitize_option_string(self.membership_status); self } } impl SanitizeOutput for Schedule { fn sanitize_output(mut self) -> Self { self.song_leader = sanitize_option_string(self.song_leader); self.ss_teacher = sanitize_option_string(self.ss_teacher); self.ss_leader = sanitize_option_string(self.ss_leader); self.mission_story = sanitize_option_string(self.mission_story); self.special_program = sanitize_option_string(self.special_program); self.sermon_speaker = sanitize_option_string(self.sermon_speaker); self.scripture = sanitize_option_string(self.scripture); self.offering = sanitize_option_string(self.offering); self.deacons = sanitize_option_string(self.deacons); self.special_music = sanitize_option_string(self.special_music); self.childrens_story = sanitize_option_string(self.childrens_story); self.afternoon_program = sanitize_option_string(self.afternoon_program); self } } impl SanitizeOutput for ScheduleV2 { fn sanitize_output(mut self) -> Self { self.song_leader = sanitize_option_string(self.song_leader); self.ss_teacher = sanitize_option_string(self.ss_teacher); self.ss_leader = sanitize_option_string(self.ss_leader); self.mission_story = sanitize_option_string(self.mission_story); self.special_program = sanitize_option_string(self.special_program); self.sermon_speaker = sanitize_option_string(self.sermon_speaker); self.scripture = sanitize_option_string(self.scripture); self.offering = sanitize_option_string(self.offering); self.deacons = sanitize_option_string(self.deacons); self.special_music = sanitize_option_string(self.special_music); self.childrens_story = sanitize_option_string(self.childrens_story); self.afternoon_program = sanitize_option_string(self.afternoon_program); self } } // Implement for collections impl SanitizeOutput for Vec { fn sanitize_output(self) -> Self { self.into_iter().map(|item| item.sanitize_output()).collect() } } impl SanitizeOutput for Option { fn sanitize_output(self) -> Self { self.map(|item| item.sanitize_output()) } } impl SanitizeOutput for PaginatedResponse { fn sanitize_output(mut self) -> Self { self.items = self.items.sanitize_output(); self } } impl SanitizeOutput for HymnalPaginatedResponse { fn sanitize_output(mut self) -> Self { self.results = self.results.sanitize_output(); self } } impl SanitizeOutput for Personnel { fn sanitize_output(mut self) -> Self { self.ss_leader = sanitize_string(self.ss_leader); self.ss_teacher = sanitize_string(self.ss_teacher); self.mission_story = sanitize_string(self.mission_story); self.song_leader = sanitize_string(self.song_leader); self.announcements = sanitize_string(self.announcements); self.offering = sanitize_string(self.offering); self.special_music = sanitize_string(self.special_music); self.speaker = sanitize_string(self.speaker); self } } impl SanitizeOutput for ScheduleData { fn sanitize_output(mut self) -> Self { self.date = sanitize_string(self.date); self.personnel = self.personnel.sanitize_output(); self } } impl SanitizeOutput for ConferenceData { fn sanitize_output(mut self) -> Self { self.date = sanitize_string(self.date); self.offering_focus = sanitize_string(self.offering_focus); self.sunset_tonight = sanitize_string(self.sunset_tonight); self.sunset_next_friday = sanitize_string(self.sunset_next_friday); self } } impl SanitizeOutput for ChurchConfig { fn sanitize_output(mut self) -> Self { self.church_name = sanitize_string(self.church_name); self.contact_email = sanitize_string(self.contact_email); self.contact_phone = sanitize_option_string(self.contact_phone); self.church_address = sanitize_string(self.church_address); self.po_box = sanitize_option_string(self.po_box); self.google_maps_url = sanitize_option_string(self.google_maps_url); self.about_text = sanitize_string(self.about_text); self.jellyfin_server_url = sanitize_option_string(self.jellyfin_server_url); self.api_keys = self.api_keys.sanitize_output(); self } } impl SanitizeOutput for User { fn sanitize_output(mut self) -> Self { self.username = sanitize_string(self.username); self.email = sanitize_option_string(self.email); self.name = sanitize_option_string(self.name); self.avatar_url = sanitize_option_string(self.avatar_url); self.role = sanitize_option_string(self.role); self } } impl SanitizeOutput for Hymnal { fn sanitize_output(mut self) -> Self { self.name = sanitize_string(self.name); self.code = sanitize_string(self.code); self.description = sanitize_option_string(self.description); self.language = sanitize_option_string(self.language); self } } impl SanitizeOutput for Hymn { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.content = sanitize_string(self.content); self } } impl SanitizeOutput for HymnWithHymnal { fn sanitize_output(mut self) -> Self { self.hymnal_name = sanitize_string(self.hymnal_name); self.hymnal_code = sanitize_string(self.hymnal_code); self.title = sanitize_string(self.title); self.content = sanitize_string(self.content); self } } impl SanitizeOutput for ThematicList { fn sanitize_output(mut self) -> Self { self.name = sanitize_string(self.name); self } } impl SanitizeOutput for ThematicAmbit { fn sanitize_output(mut self) -> Self { self.name = sanitize_string(self.name); self } } impl SanitizeOutput for ThematicListWithAmbits { fn sanitize_output(mut self) -> Self { self.name = sanitize_string(self.name); self.ambits = self.ambits.sanitize_output(); self } } impl SanitizeOutput for ResponsiveReading { fn sanitize_output(mut self) -> Self { self.title = sanitize_string(self.title); self.content = sanitize_string(self.content); self } } impl SanitizeOutput for SearchResult { fn sanitize_output(mut self) -> Self { self.hymn = self.hymn.sanitize_output(); self.match_type = sanitize_string(self.match_type); self } }