iqps_backend/routing/
mod.rs1use std::sync::Arc;
4
5use axum::{
6 extract::{DefaultBodyLimit, Json, State},
7 http::StatusCode,
8 response::IntoResponse,
9};
10use http::{HeaderValue, Method};
11use serde::Serialize;
12use tower_http::{
13 cors::{Any, CorsLayer},
14 trace::{self, TraceLayer},
15};
16
17use crate::{
18 db::{self, Database},
19 env::EnvVars,
20};
21
22mod handlers;
23mod middleware;
24
25pub use handlers::{EditReq, FileDetails};
26
27pub fn get_router(env_vars: EnvVars, db: Database) -> axum::Router {
29 let cors_origins = env_vars
30 .cors_allowed_origins
31 .split(',')
32 .map(|origin| {
33 origin
34 .trim()
35 .parse::<HeaderValue>()
36 .expect("CORS Allowed Origins Invalid")
37 })
38 .collect::<Vec<HeaderValue>>();
39
40 let state = Arc::new(RouterState { db, env_vars });
41
42 axum::Router::new()
43 .route("/unapproved", axum::routing::get(handlers::get_unapproved))
44 .route("/trash", axum::routing::get(handlers::get_trash))
45 .route("/details", axum::routing::get(handlers::get_paper_details))
46 .route("/profile", axum::routing::get(handlers::profile))
47 .route("/edit", axum::routing::post(handlers::edit))
48 .route("/delete", axum::routing::post(handlers::delete))
49 .route("/harddelete", axum::routing::post(handlers::hard_delete))
50 .route("/similar", axum::routing::get(handlers::similar))
51 .route_layer(axum::middleware::from_fn_with_state(
52 state.clone(),
53 middleware::verify_jwt_middleware,
54 ))
55 .route("/oauth", axum::routing::post(handlers::oauth))
56 .route("/healthcheck", axum::routing::get(handlers::healthcheck))
57 .route("/search", axum::routing::get(handlers::search))
58 .layer(DefaultBodyLimit::max(2 << 20)) .route("/upload", axum::routing::post(handlers::upload))
60 .layer(DefaultBodyLimit::max(50 << 20)) .with_state(state)
62 .layer(
63 TraceLayer::new_for_http()
64 .make_span_with(trace::DefaultMakeSpan::new().level(tracing::Level::INFO))
65 .on_response(trace::DefaultOnResponse::new().level(tracing::Level::INFO)),
66 )
67 .layer(
68 CorsLayer::new()
69 .allow_headers(Any)
70 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS])
71 .allow_origin(cors_origins),
72 )
73}
74
75struct RouterState {
77 pub db: db::Database,
78 pub env_vars: EnvVars,
79}
80type HandlerState = State<Arc<RouterState>>;
81
82#[derive(serde::Serialize)]
84struct BackendResponse<T: Serialize> {
85 pub status: &'static str,
87 pub message: String,
89 pub data: Option<T>,
91}
92
93impl<T: serde::Serialize> BackendResponse<T> {
94 pub fn ok(message: String, data: T) -> (StatusCode, Self) {
96 (
97 StatusCode::OK,
98 Self {
99 status: "success",
100 message,
101 data: Some(data),
102 },
103 )
104 }
105
106 pub fn error(message: String, status_code: StatusCode) -> (StatusCode, Self) {
108 (
109 status_code,
110 Self {
111 status: "error",
112 message,
113 data: None,
114 },
115 )
116 }
117}
118
119impl<T: Serialize> IntoResponse for BackendResponse<T> {
120 fn into_response(self) -> axum::response::Response {
121 Json(self).into_response()
122 }
123}
124
125pub(super) struct AppError(color_eyre::eyre::Error);
127impl IntoResponse for AppError {
128 fn into_response(self) -> axum::response::Response {
129 tracing::error!("An error occured: {}", self.0);
130
131 BackendResponse::<()>::error(
132 "An internal server error occured. Please try again later.".into(),
133 StatusCode::INTERNAL_SERVER_ERROR,
134 )
135 .into_response()
136 }
137}
138
139impl<E> From<E> for AppError
140where
141 E: Into<color_eyre::eyre::Error>,
142{
143 fn from(err: E) -> Self {
144 Self(err.into())
145 }
146}