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("/trash", axum::routing::get(handlers::get_trash))
35 .route("/details", axum::routing::get(handlers::get_paper_details))
36 .route("/profile", axum::routing::get(handlers::profile))
37 .route("/edit", axum::routing::post(handlers::edit))
38 .route("/delete", axum::routing::post(handlers::delete))
39 .route("/harddelete", axum::routing::post(handlers::hard_delete))
40 .route("/similar", axum::routing::get(handlers::similar))
41 .route_layer(axum::middleware::from_fn_with_state(
42 state.clone(),
43 middleware::verify_jwt_middleware,
44 ))
45 .route("/oauth", axum::routing::post(handlers::oauth))
46 .route("/healthcheck", axum::routing::get(handlers::healthcheck))
47 .route("/search", axum::routing::get(handlers::search))
48 .layer(DefaultBodyLimit::max(2 << 20)) .route("/upload", axum::routing::post(handlers::upload))
50 .layer(DefaultBodyLimit::max(50 << 20)) .with_state(state)
52 .layer(
53 TraceLayer::new_for_http()
54 .make_span_with(trace::DefaultMakeSpan::new().level(tracing::Level::INFO))
55 .on_response(trace::DefaultOnResponse::new().level(tracing::Level::INFO)),
56 )
57 .layer(
58 CorsLayer::new()
59 .allow_headers(Any)
60 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS])
61 .allow_origin(
62 env_vars
63 .cors_allowed_origins
64 .split(',')
65 .map(|origin| {
66 origin
67 .trim()
68 .parse::<HeaderValue>()
69 .expect("CORS Allowed Origins Invalid")
70 })
71 .collect::<Vec<HeaderValue>>(),
72 ),
73 )
74}
75
76#[derive(Clone)]
77struct RouterState {
79 pub db: db::Database,
80 pub env_vars: EnvVars,
81}
82
83#[derive(Clone, Copy)]
84enum Status {
86 Success,
87 Error,
88}
89
90impl From<Status> for String {
91 fn from(value: Status) -> Self {
92 match value {
93 Status::Success => "success".into(),
94 Status::Error => "error".into(),
95 }
96 }
97}
98
99impl Serialize for Status {
100 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101 where
102 S: serde::Serializer,
103 {
104 serializer.serialize_str(&String::from(*self))
105 }
106}
107
108#[derive(serde::Serialize)]
110struct BackendResponse<T: Serialize> {
111 pub status: Status,
113 pub message: String,
115 pub data: Option<T>,
117}
118
119impl<T: serde::Serialize> BackendResponse<T> {
120 pub fn ok(message: String, data: T) -> (StatusCode, Self) {
122 (
123 StatusCode::OK,
124 Self {
125 status: Status::Success,
126 message,
127 data: Some(data),
128 },
129 )
130 }
131
132 pub fn error(message: String, status_code: StatusCode) -> (StatusCode, Self) {
134 (
135 status_code,
136 Self {
137 status: Status::Error,
138 message,
139 data: None,
140 },
141 )
142 }
143}
144
145impl<T: Serialize> IntoResponse for BackendResponse<T> {
146 fn into_response(self) -> axum::response::Response {
147 Json(self).into_response()
148 }
149}
150
151pub(super) struct AppError(color_eyre::eyre::Error);
153impl IntoResponse for AppError {
154 fn into_response(self) -> axum::response::Response {
155 tracing::error!("An error occured: {}", self.0);
156
157 BackendResponse::<()>::error(
158 "An internal server error occured. Please try again later.".into(),
159 StatusCode::INTERNAL_SERVER_ERROR,
160 )
161 .into_response()
162 }
163}
164
165impl<E> From<E> for AppError
166where
167 E: Into<color_eyre::eyre::Error>,
168{
169 fn from(err: E) -> Self {
170 Self(err.into())
171 }
172}