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 let recipient_did = input.recipient_did.trim();
41 let content = input.content.trim();
42 if recipient_did.is_empty() {
43 return (
44 StatusCode::BAD_REQUEST,
45 Json(json!({"error": "InvalidRequest", "message": "recipientDid is required"})),
46 )
47 .into_response();
48 }
49 if content.is_empty() {
50 return (
51 StatusCode::BAD_REQUEST,
52 Json(json!({"error": "InvalidRequest", "message": "content is required"})),
53 )
54 .into_response();
55 }
56 let user = sqlx::query!(
57 "SELECT id, email, handle FROM users WHERE did = $1",
58 recipient_did
59 )
60 .fetch_optional(&state.db)
61 .await;
62 let (user_id, email, handle) = match user {
63 Ok(Some(row)) => {
64 let email = match row.email {
65 Some(e) => e,
66 None => {
67 return (
68 StatusCode::BAD_REQUEST,
69 Json(json!({"error": "NoEmail", "message": "Recipient has no email address"})),
70 )
71 .into_response();
72 }
73 };
74 (row.id, email, row.handle)
75 }
76 Ok(None) => {
77 return (
78 StatusCode::NOT_FOUND,
79 Json(json!({"error": "AccountNotFound", "message": "Recipient account not found"})),
80 )
81 .into_response();
82 }
83 Err(e) => {
84 error!("DB error in send_email: {:?}", e);
85 return (
86 StatusCode::INTERNAL_SERVER_ERROR,
87 Json(json!({"error": "InternalError"})),
88 )
89 .into_response();
90 }
91 };
92 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
93 let subject = input
94 .subject
95 .clone()
96 .unwrap_or_else(|| format!("Message from {}", hostname));
97 let notification = crate::notifications::NewNotification::email(
98 user_id,
99 crate::notifications::NotificationType::AdminEmail,
100 email,
101 subject,
102 content.to_string(),
103 );
104 let result = crate::notifications::enqueue_notification(&state.db, notification).await;
105 match result {
106 Ok(_) => {
107 tracing::info!(
108 "Admin email queued for {} ({})",
109 handle,
110 recipient_did
111 );
112 (StatusCode::OK, Json(SendEmailOutput { sent: true })).into_response()
113 }
114 Err(e) => {
115 warn!("Failed to enqueue admin email: {:?}", e);
116 (StatusCode::OK, Json(SendEmailOutput { sent: false })).into_response()
117 }
118 }
119}