The smokesignal.events web application
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}