this repo has no description
1use crate::state::AppState;
2use axum::{
3 Json,
4 extract::State,
5 http::StatusCode,
6 response::{IntoResponse, Response},
7};
8use serde::{Deserialize, Serialize};
9use serde_json::json;
10use tracing::{error, warn};
11
12#[derive(Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct SendEmailInput {
15 pub recipient_did: String,
16 pub sender_did: String,
17 pub content: String,
18 pub subject: Option<String>,
19 pub comment: Option<String>,
20}
21
22#[derive(Serialize)]
23pub struct SendEmailOutput {
24 pub sent: bool,
25}
26
27pub async fn send_email(
28 State(state): State<AppState>,
29 headers: axum::http::HeaderMap,
30 Json(input): Json<SendEmailInput>,
31) -> Response {
32 let auth_header = headers.get("Authorization");
33 if auth_header.is_none() {
34 return (
35 StatusCode::UNAUTHORIZED,
36 Json(json!({"error": "AuthenticationRequired"})),
37 )
38 .into_response();
39 }
40
41 let recipient_did = input.recipient_did.trim();
42 let content = input.content.trim();
43
44 if recipient_did.is_empty() {
45 return (
46 StatusCode::BAD_REQUEST,
47 Json(json!({"error": "InvalidRequest", "message": "recipientDid is required"})),
48 )
49 .into_response();
50 }
51
52 if content.is_empty() {
53 return (
54 StatusCode::BAD_REQUEST,
55 Json(json!({"error": "InvalidRequest", "message": "content is required"})),
56 )
57 .into_response();
58 }
59
60 let user = sqlx::query!(
61 "SELECT id, email, handle FROM users WHERE did = $1",
62 recipient_did
63 )
64 .fetch_optional(&state.db)
65 .await;
66
67 let (user_id, email, handle) = match user {
68 Ok(Some(row)) => {
69 let email = match row.email {
70 Some(e) => e,
71 None => {
72 return (
73 StatusCode::BAD_REQUEST,
74 Json(json!({"error": "NoEmail", "message": "Recipient has no email address"})),
75 )
76 .into_response();
77 }
78 };
79 (row.id, email, row.handle)
80 }
81 Ok(None) => {
82 return (
83 StatusCode::NOT_FOUND,
84 Json(json!({"error": "AccountNotFound", "message": "Recipient account not found"})),
85 )
86 .into_response();
87 }
88 Err(e) => {
89 error!("DB error in send_email: {:?}", e);
90 return (
91 StatusCode::INTERNAL_SERVER_ERROR,
92 Json(json!({"error": "InternalError"})),
93 )
94 .into_response();
95 }
96 };
97
98 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
99 let subject = input
100 .subject
101 .clone()
102 .unwrap_or_else(|| format!("Message from {}", hostname));
103
104 let notification = crate::notifications::NewNotification::email(
105 user_id,
106 crate::notifications::NotificationType::AdminEmail,
107 email,
108 subject,
109 content.to_string(),
110 );
111
112 let result = crate::notifications::enqueue_notification(&state.db, notification).await;
113
114 match result {
115 Ok(_) => {
116 tracing::info!(
117 "Admin email queued for {} ({})",
118 handle,
119 recipient_did
120 );
121 (StatusCode::OK, Json(SendEmailOutput { sent: true })).into_response()
122 }
123 Err(e) => {
124 warn!("Failed to enqueue admin email: {:?}", e);
125 (StatusCode::OK, Json(SendEmailOutput { sent: false })).into_response()
126 }
127 }
128}