The smokesignal.events web application
at main 131 lines 3.9 kB view raw
1use anyhow::{Result, anyhow}; 2 3use crate::{ 4 http::{ 5 context::WebContext, 6 errors::{CommonError, WebError}, 7 utils::url_from_aturi, 8 }, 9 storage::{ 10 event::{Event, event_get}, 11 identity_profile::handle_for_did, 12 }, 13}; 14 15/// Verify that the current user is the organizer of the specified event. 16/// Returns the Event if authorized, or an error if not found or not authorized. 17pub(crate) async fn verify_event_organizer_authorization( 18 web_context: &WebContext, 19 event_aturi: &str, 20 organizer_did: &str, 21) -> Result<Event, WebError> { 22 // Get the event from storage 23 let event = event_get(&web_context.pool, event_aturi) 24 .await 25 .map_err(|e| anyhow!("Failed to get event: {}", e))?; 26 27 // Verify the current user is the event organizer 28 if event.did != organizer_did { 29 return Err(CommonError::NotAuthorized.into()); 30 } 31 32 Ok(event) 33} 34 35/// Send an email notification to the subject about their RSVP acceptance. 36/// This function never fails - errors are logged but the function always returns successfully. 37pub async fn send_acceptance_email_notification( 38 web_context: &WebContext, 39 subject_did: &str, 40 event_name: &str, 41 event_aturi: &str, 42) { 43 // Get the subject's profile for email notification 44 let subject_profile = match handle_for_did(&web_context.pool, subject_did).await { 45 Ok(profile) => profile, 46 Err(e) => { 47 tracing::warn!( 48 "Failed to get profile for DID {} to send acceptance notification: {:?}", 49 subject_did, 50 e 51 ); 52 return; 53 } 54 }; 55 56 // Generate event URL 57 let event_url = match url_from_aturi(&web_context.config.external_base, event_aturi) { 58 Ok(url) => url, 59 Err(e) => { 60 tracing::error!( 61 "Failed to generate event URL from AT-URI {}: {:?}", 62 event_aturi, 63 e 64 ); 65 return; 66 } 67 }; 68 69 // Send email notification if email is available 70 if let Some(email) = &subject_profile.email 71 && let Some(emailer) = &web_context.emailer 72 { 73 if let Err(e) = emailer 74 .notify_rsvp_accepted(email, subject_did, event_name, &event_url) 75 .await 76 { 77 tracing::error!("Failed to send RSVP accepted email to {}: {:?}", email, e); 78 } else { 79 tracing::info!( 80 "Sent RSVP acceptance notification to {} for event {}", 81 email, 82 event_name 83 ); 84 } 85 } 86} 87 88/// Format an error message as an HTML notification for HTMX responses. 89pub fn format_error_html(title: &str, message: &str, details: Option<&str>) -> String { 90 if let Some(details) = details { 91 format!( 92 r#"<div class="notification is-danger"> 93 <p><strong>Error!</strong> {}</p> 94 <p>{}</p> 95 <p class="is-size-7 mt-2">Details: {}</p> 96 </div>"#, 97 title, message, details 98 ) 99 } else { 100 format!( 101 r#"<div class="notification is-danger"> 102 <p><strong>Error!</strong> {}</p> 103 <p>{}</p> 104 </div>"#, 105 title, message 106 ) 107 } 108} 109 110/// Format a success message as an HTML notification for HTMX responses. 111pub fn format_success_html( 112 title: &str, 113 message: &str, 114 additional_info: Option<Vec<String>>, 115) -> String { 116 let mut html = format!( 117 r#"<div class="notification is-success"> 118 <p><strong>Success!</strong> {}</p> 119 <p>{}</p>"#, 120 title, message 121 ); 122 123 if let Some(info) = additional_info { 124 for line in info { 125 html.push_str(&format!("\n <p>{}</p>", line)); 126 } 127 } 128 129 html.push_str("\n </div>"); 130 html 131}