2025-02-26 13:10:47 -08:00
|
|
|
use anyhow::Context;
|
|
|
|
use axum::{
|
2025-02-26 13:10:45 -08:00
|
|
|
extract::{Query, State},
|
2025-02-26 13:10:47 -08:00
|
|
|
response::{IntoResponse, Json},
|
|
|
|
routing::get,
|
|
|
|
Router,
|
|
|
|
};
|
|
|
|
use diesel::{dsl::insert_into, prelude::*, update};
|
2025-02-26 13:10:45 -08:00
|
|
|
use serde::Deserialize;
|
|
|
|
use serde_json::json;
|
2025-02-26 13:10:47 -08:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
api_keys::ApiKey,
|
|
|
|
app_error::AppError,
|
2025-02-26 13:10:43 -08:00
|
|
|
app_state::{AppState, DbConn},
|
2025-02-26 13:10:27 -08:00
|
|
|
channels::{Channel, EmailBackendConfig},
|
2025-02-26 13:10:43 -08:00
|
|
|
email::{MailSender as _, Mailer},
|
2025-02-26 13:10:47 -08:00
|
|
|
projects::Project,
|
2025-02-26 13:10:45 -08:00
|
|
|
schema::{api_keys, projects},
|
|
|
|
settings::Settings,
|
2025-02-26 13:10:47 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
pub fn new_router(state: AppState) -> Router<AppState> {
|
|
|
|
Router::new().route("/say", get(say_get)).with_state(state)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct SayQuery {
|
|
|
|
key: Uuid,
|
|
|
|
project: String,
|
|
|
|
message: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn say_get(
|
2025-02-26 13:10:45 -08:00
|
|
|
State(Settings {
|
|
|
|
email: email_settings,
|
|
|
|
..
|
|
|
|
}): State<Settings>,
|
2025-02-26 13:10:43 -08:00
|
|
|
State(mailer): State<Mailer>,
|
2025-02-26 13:10:47 -08:00
|
|
|
DbConn(db_conn): DbConn,
|
|
|
|
Query(query): Query<SayQuery>,
|
|
|
|
) -> Result<impl IntoResponse, AppError> {
|
2025-02-26 13:10:27 -08:00
|
|
|
// TODO: do some validation of message contents
|
|
|
|
let api_key = {
|
|
|
|
let query_key = query.key.clone();
|
|
|
|
db_conn
|
|
|
|
.interact::<_, Result<ApiKey, AppError>>(move |conn| {
|
|
|
|
update(api_keys::table.filter(ApiKey::with_id(query_key)))
|
|
|
|
.set(api_keys::last_used_at.eq(diesel::dsl::now))
|
|
|
|
.returning(ApiKey::as_returning())
|
|
|
|
.get_result(conn)
|
|
|
|
.optional()
|
|
|
|
.context("failed to get API key")?
|
|
|
|
.ok_or(AppError::ForbiddenError("Key not accepted.".to_string()))
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
.unwrap()?
|
|
|
|
};
|
|
|
|
|
|
|
|
let selected_channels = {
|
|
|
|
let project_name = query.project.to_lowercase();
|
|
|
|
db_conn
|
|
|
|
.interact::<_, Result<Vec<Channel>, AppError>>(move |conn| {
|
|
|
|
conn.transaction(move |conn| {
|
|
|
|
let project = match Project::all()
|
|
|
|
.filter(Project::with_team(api_key.team_id))
|
|
|
|
.filter(Project::with_name(project_name.clone()))
|
|
|
|
.first(conn)
|
|
|
|
.optional()
|
|
|
|
.context("failed to load project")?
|
|
|
|
{
|
|
|
|
Some(project) => project,
|
|
|
|
None => insert_into(projects::table)
|
|
|
|
.values((
|
|
|
|
projects::id.eq(Uuid::now_v7()),
|
|
|
|
projects::team_id.eq(api_key.team_id),
|
|
|
|
projects::name.eq(project_name),
|
|
|
|
))
|
|
|
|
.get_result(conn)
|
|
|
|
.context("failed to insert project")?,
|
|
|
|
};
|
|
|
|
Ok(project
|
|
|
|
.selected_channels()
|
|
|
|
.load(conn)
|
|
|
|
.context("failed to load selected channels")?)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
.unwrap()?
|
2025-02-26 13:10:47 -08:00
|
|
|
};
|
2025-02-26 13:10:45 -08:00
|
|
|
|
|
|
|
for channel in selected_channels {
|
2025-02-26 13:10:27 -08:00
|
|
|
if let Ok(config) = TryInto::<EmailBackendConfig>::try_into(channel.backend_config) {
|
|
|
|
if config.verified {
|
|
|
|
let recipient: lettre::message::Mailbox = config.recipient.parse()?;
|
2025-02-26 13:10:43 -08:00
|
|
|
let email = crate::email::Message {
|
|
|
|
from: email_settings.message_from.clone().into(),
|
|
|
|
to: recipient.into(),
|
|
|
|
subject: "Shout".to_string(),
|
|
|
|
text_body: query.message.clone(),
|
|
|
|
};
|
2025-02-26 13:10:45 -08:00
|
|
|
tracing::info!("Sending email to recipient for channel {}", channel.id);
|
2025-02-26 13:10:43 -08:00
|
|
|
mailer.send_batch(vec![email]).await?;
|
2025-02-26 13:10:45 -08:00
|
|
|
} else {
|
|
|
|
tracing::info!("Email recipient for channel {} is not verified", channel.id);
|
|
|
|
}
|
|
|
|
}
|
2025-02-26 13:10:47 -08:00
|
|
|
}
|
2025-02-26 13:10:45 -08:00
|
|
|
|
|
|
|
Ok(Json(json!({ "ok": true })))
|
2025-02-26 13:10:47 -08:00
|
|
|
}
|