use axum::body::Bytes;
use axum::extract::{Path, State};
use axum::http::{HeaderMap, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::routing::post;
use axum::{Json, Router};
use uuid::Uuid;
use crate::application::service::{
InboundMessage, InboundOutcome, MailInboundError, MailInboundService,
};
use crate::presentation::http::thread_routes::{db_err, json_err, ApiState};
fn bearer_token(headers: &HeaderMap) -> Option<String> {
let value = headers.get(axum::http::header::AUTHORIZATION)?.to_str().ok()?;
let token = value.strip_prefix("Bearer ").or_else(|| value.strip_prefix("bearer "))?;
if token.is_empty() {
return None;
}
Some(token.to_string())
}
async fn mail_inbound(
State(app): State<ApiState>,
Path(server_id): Path<Uuid>,
headers: HeaderMap,
body: Bytes,
) -> Response {
let msg: InboundMessage = match serde_json::from_slice(&body) {
Ok(m) => m,
Err(e) => {
return (
StatusCode::BAD_REQUEST,
Json(json_err(&format!("unparseable body: {e}"))),
)
.into_response()
}
};
let Some(token) = bearer_token(&headers) else {
return (StatusCode::UNAUTHORIZED, Json(json_err("unauthorized"))).into_response();
};
let service = MailInboundService::new(app.db_pool());
match service.process_inbound(server_id, &token, &msg).await {
Ok(outcome) => {
let label = match &outcome {
InboundOutcome::Routed { .. } => "routed",
InboundOutcome::Duplicate { .. } => "duplicate",
InboundOutcome::Rejected => "rejected",
InboundOutcome::Dropped => "dropped",
};
(StatusCode::OK, Json(serde_json::json!({ "outcome": label }))).into_response()
}
Err(e) => match e {
MailInboundError::Auth => {
(StatusCode::UNAUTHORIZED, Json(json_err("unauthorized"))).into_response()
}
MailInboundError::Invalid(m) => {
(StatusCode::UNPROCESSABLE_ENTITY, Json(json_err(&m))).into_response()
}
MailInboundError::Db(err) => db_err(err),
},
}
}
pub fn composer() -> Router<ApiState> {
use axum::middleware as axum_mw;
Router::<ApiState>::new()
.route("/mail/inbound/:server_id", post(mail_inbound))
.route_layer(axum_mw::from_fn_with_state(
backbone_rate_limit::middleware(120, 60),
backbone_rate_limit::rate_limit_middleware,
))
}