1use sfr_core as sc;
4use sfr_types as st;
5
6use crate::extract::SlackSlashCommand;
7use crate::handler::home::HomeHandler;
8use crate::handler::oauth::OauthHandler;
9use crate::handler::slash_command::SlashCommandHandler;
10use crate::log::try_init_logger;
11use crate::{Config, HomeHandlerTrait, OauthHandlerTrait, ResponseError, SlashCommandHandlerTrait};
12use axum::extract::Extension;
13use axum::extract::Query;
14use axum::http::{StatusCode, Uri};
15use axum::response::IntoResponse;
16use axum::routing::{get, get_service, post};
17use axum::Router;
18use sc::{OauthRedirectQuery, Slack};
19use std::sync::Arc;
20use tower_http::services::ServeDir;
21use tower_http::trace::TraceLayer;
22
23pub struct Server<HH, SH, OH>
25where
26 HH: HomeHandlerTrait,
27 SH: SlashCommandHandlerTrait,
28 OH: OauthHandlerTrait,
29{
30 config: Config,
32
33 slack: Slack,
35
36 client: reqwest::Client,
38
39 home_handler: HH,
41
42 sc_handler: SH,
44
45 oauth_handler: OH,
47}
48
49impl<HH, SH, OH> Server<HH, SH, OH>
50where
51 HH: HomeHandlerTrait + 'static,
52 SH: SlashCommandHandlerTrait + 'static,
53 OH: OauthHandlerTrait + 'static,
54{
55 pub fn new(
57 config: Config,
58 slack: Slack,
59 client: reqwest::Client,
60 home_handler: HH,
61 sc_handler: SH,
62 oauth_handler: OH,
63 ) -> Self {
64 Self {
65 config,
66 slack,
67 client,
68 home_handler,
69 sc_handler,
70 oauth_handler,
71 }
72 }
73
74 pub async fn serve(self) -> Result<(), st::Error> {
76 if let Some(log) = self.config.log {
77 let _ = try_init_logger(log.as_str())
78 .inspect_err(|e| tracing::warn!("logger already initialized: {e:?}"));
79 }
80
81 let app = Router::new()
82 .route(&self.config.home_path, get(home_handler_fn::<HH>))
83 .route(
84 &self.config.slash_command_path,
85 post(slash_command_handler_fn::<SH, OH>),
86 )
87 .route(
88 &self.config.oauth_path,
89 get(handler_oauth_redirect_fn::<OH>),
90 )
91 .nest_service(
92 &self.config.static_path.http,
93 get_service(ServeDir::new(&self.config.static_path.local))
94 .fallback(handle_static_file_error),
95 )
96 .layer(Extension(self.slack))
97 .layer(Extension(self.client))
98 .layer(Extension(Arc::new(HomeHandler::new(self.home_handler))))
99 .layer(Extension(Arc::new(SlashCommandHandler::new(
100 self.sc_handler,
101 ))))
102 .layer(Extension(Arc::new(OauthHandler::new(self.oauth_handler))))
103 .layer(TraceLayer::new_for_http());
104
105 tracing::info!("listening on {}", self.config.sock);
106 let listener = tokio::net::TcpListener::bind(&self.config.sock)
107 .await
108 .map_err(st::Error::failed_to_bind_socket)?;
109 axum::serve(listener, app)
110 .await
111 .map_err(st::Error::failed_to_serve)
112 }
113}
114
115async fn home_handler_fn<HH>(
117 Extension(hh): Extension<Arc<HomeHandler<HH>>>,
118) -> Result<impl IntoResponse, ResponseError>
119where
120 HH: HomeHandlerTrait + 'static,
121{
122 hh.handle().await
123}
124
125async fn slash_command_handler_fn<SH, OH>(
127 Extension(client): Extension<reqwest::Client>,
128 Extension(sc): Extension<Arc<SlashCommandHandler<SH>>>,
129 Extension(oauth): Extension<Arc<OauthHandler<OH>>>,
130 SlackSlashCommand(body): SlackSlashCommand,
131) -> Result<impl IntoResponse, ResponseError>
132where
133 SH: SlashCommandHandlerTrait,
134 OH: OauthHandlerTrait,
135{
136 sc.handle(client, &oauth, body).await
137}
138
139async fn handler_oauth_redirect_fn<OH>(
141 Extension(client): Extension<reqwest::Client>,
142 Extension(slack): Extension<Slack>,
143 Extension(oauth): Extension<Arc<OauthHandler<OH>>>,
144 Query(query): Query<OauthRedirectQuery>,
145) -> Result<impl IntoResponse, ResponseError>
146where
147 OH: OauthHandlerTrait,
148{
149 oauth.handle(client, slack, query).await
150}
151
152async fn handle_static_file_error(_uri: Uri) -> impl IntoResponse {
154 (StatusCode::INTERNAL_SERVER_ERROR, "error")
155}