use anyhow::Context; use axum::{ extract::{Query, State}, response::{IntoResponse, Json}, routing::get, Router, }; use diesel::{dsl::insert_into, prelude::*, update}; use serde::Deserialize; use serde_json::json; use uuid::Uuid; use crate::{ api_keys::ApiKey, app_error::AppError, app_state::{AppState, DbConn}, channels::{Channel, EmailBackendConfig}, email::{MailSender as _, Mailer}, projects::Project, schema::{api_keys, projects}, settings::Settings, }; pub fn new_router(state: AppState) -> Router { Router::new().route("/say", get(say_get)).with_state(state) } #[derive(Deserialize)] struct SayQuery { key: Uuid, project: String, message: String, } async fn say_get( State(Settings { email: email_settings, .. }): State, State(mailer): State, DbConn(db_conn): DbConn, Query(query): Query, ) -> Result { // TODO: do some validation of message contents let api_key = { let query_key = query.key.clone(); db_conn .interact::<_, Result>(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, 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()? }; for channel in selected_channels { if let Ok(config) = TryInto::::try_into(channel.backend_config) { if config.verified { let recipient: lettre::message::Mailbox = config.recipient.parse()?; let email = crate::email::Message { from: email_settings.message_from.clone().into(), to: recipient.into(), subject: "Shout".to_string(), text_body: query.message.clone(), }; tracing::info!("Sending email to recipient for channel {}", channel.id); mailer.send_batch(vec![email]).await?; } else { tracing::info!("Email recipient for channel {} is not verified", channel.id); } } } Ok(Json(json!({ "ok": true }))) }