use sfr_core as sc;
use sfr_types as st;
use crate::extract::SlackSlashCommand;
use crate::handler::home::HomeHandler;
use crate::handler::oauth::OauthHandler;
use crate::handler::slash_command::SlashCommandHandler;
use crate::log::try_init_logger;
use crate::{Config, HomeHandlerTrait, OauthHandlerTrait, ResponseError, SlashCommandHandlerTrait};
use axum::extract::Extension;
use axum::extract::Query;
use axum::http::{StatusCode, Uri};
use axum::response::IntoResponse;
use axum::routing::{get, get_service, post};
use axum::Router;
use sc::{OauthRedirectQuery, Slack};
use std::sync::Arc;
use tower_http::services::ServeDir;
use tower_http::trace::TraceLayer;
pub struct Server<HH, SH, OH>
where
HH: HomeHandlerTrait,
SH: SlashCommandHandlerTrait,
OH: OauthHandlerTrait,
{
config: Config,
slack: Slack,
client: reqwest::Client,
home_handler: HH,
sc_handler: SH,
oauth_handler: OH,
}
impl<HH, SH, OH> Server<HH, SH, OH>
where
HH: HomeHandlerTrait + 'static,
SH: SlashCommandHandlerTrait + 'static,
OH: OauthHandlerTrait + 'static,
{
pub fn new(
config: Config,
slack: Slack,
client: reqwest::Client,
home_handler: HH,
sc_handler: SH,
oauth_handler: OH,
) -> Self {
Self {
config,
slack,
client,
home_handler,
sc_handler,
oauth_handler,
}
}
pub async fn serve(self) -> Result<(), st::Error> {
if let Some(log) = self.config.log {
let _ = try_init_logger(log.as_str())
.inspect_err(|e| tracing::warn!("logger already initialized: {e:?}"));
}
let app = Router::new()
.route(&self.config.home_path, get(home_handler_fn::<HH>))
.route(
&self.config.slash_command_path,
post(slash_command_handler_fn::<SH, OH>),
)
.route(
&self.config.oauth_path,
get(handler_oauth_redirect_fn::<OH>),
)
.nest_service(
&self.config.static_path.http,
get_service(ServeDir::new(&self.config.static_path.local))
.fallback(handle_static_file_error),
)
.layer(Extension(self.slack))
.layer(Extension(self.client))
.layer(Extension(Arc::new(HomeHandler::new(self.home_handler))))
.layer(Extension(Arc::new(SlashCommandHandler::new(
self.sc_handler,
))))
.layer(Extension(Arc::new(OauthHandler::new(self.oauth_handler))))
.layer(TraceLayer::new_for_http());
tracing::info!("listening on {}", self.config.sock);
let listener = tokio::net::TcpListener::bind(&self.config.sock)
.await
.map_err(st::Error::failed_to_bind_socket)?;
axum::serve(listener, app)
.await
.map_err(st::Error::failed_to_serve)
}
}
async fn home_handler_fn<HH>(
Extension(hh): Extension<Arc<HomeHandler<HH>>>,
) -> Result<impl IntoResponse, ResponseError>
where
HH: HomeHandlerTrait + 'static,
{
hh.handle().await
}
async fn slash_command_handler_fn<SH, OH>(
Extension(client): Extension<reqwest::Client>,
Extension(sc): Extension<Arc<SlashCommandHandler<SH>>>,
Extension(oauth): Extension<Arc<OauthHandler<OH>>>,
SlackSlashCommand(body): SlackSlashCommand,
) -> Result<impl IntoResponse, ResponseError>
where
SH: SlashCommandHandlerTrait,
OH: OauthHandlerTrait,
{
sc.handle(client, &oauth, body).await
}
async fn handler_oauth_redirect_fn<OH>(
Extension(client): Extension<reqwest::Client>,
Extension(slack): Extension<Slack>,
Extension(oauth): Extension<Arc<OauthHandler<OH>>>,
Query(query): Query<OauthRedirectQuery>,
) -> Result<impl IntoResponse, ResponseError>
where
OH: OauthHandlerTrait,
{
oauth.handle(client, slack, query).await
}
async fn handle_static_file_error(_uri: Uri) -> impl IntoResponse {
(StatusCode::INTERNAL_SERVER_ERROR, "error")
}