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