1
0
Fork 0
forked from 2sys/shoutdotdev
shoutdotdev/src/v0_router.rs

113 lines
3.8 KiB
Rust
Raw Normal View History

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},
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> {
// 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 {
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
}