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