Compare commits
	
		
			3 Commits
		
	
	
		
			1d28ec616b
			...
			68100e9b54
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 68100e9b54 | |||
| 1cf634bd19 | |||
| 562d658ea1 | 
							
								
								
									
										2
									
								
								backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -429,6 +429,7 @@ dependencies = [ | |||||||
|  "openidconnect", |  "openidconnect", | ||||||
|  "serde", |  "serde", | ||||||
|  "sqlx", |  "sqlx", | ||||||
|  |  "utoipa", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -1669,6 +1670,7 @@ dependencies = [ | |||||||
|  "log", |  "log", | ||||||
|  "serde", |  "serde", | ||||||
|  "sqlx", |  "sqlx", | ||||||
|  |  "utoipa", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
|  | |||||||
| @ -11,3 +11,4 @@ log.workspace = true | |||||||
| openidconnect.workspace = true | openidconnect.workspace = true | ||||||
| serde.workspace = true | serde.workspace = true | ||||||
| sqlx.workspace = true | sqlx.workspace = true | ||||||
|  | utoipa.workspace = true | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| use serde::Deserialize; | use serde::Deserialize; | ||||||
|  | use utoipa::IntoParams; | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize)] | #[derive(Deserialize, IntoParams)] | ||||||
| pub struct OidcCallbackQueryDto { | pub struct OidcCallbackQueryDto { | ||||||
|     pub code: String, |     pub code: String, | ||||||
|     pub state: String, |     pub state: String, | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  | use utoipa::ToSchema; | ||||||
| 
 | 
 | ||||||
| use crate::domain::entity::user::User; | use crate::domain::entity::user::User; | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize, ToSchema)] | ||||||
| pub struct UserResponseDto { | pub struct UserResponseDto { | ||||||
|     pub id: i32, |     pub id: i32, | ||||||
|     pub displayed_name: String, |     pub displayed_name: String, | ||||||
|  | |||||||
| @ -1,4 +1,9 @@ | |||||||
|  | pub mod auth_api_doc; | ||||||
| pub mod auth_middleware; | pub mod auth_middleware; | ||||||
| pub mod auth_web_routes; | pub mod auth_web_routes; | ||||||
|  | pub mod get_logged_in_user_handler; | ||||||
|  | pub mod oidc_callback_handler; | ||||||
|  | pub mod oidc_login_handler; | ||||||
|  | pub mod oidc_logout_handler; | ||||||
| 
 | 
 | ||||||
| mod constants; | mod constants; | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								backend/feature/auth/src/framework/web/auth_api_doc.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								backend/feature/auth/src/framework/web/auth_api_doc.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | use crate::framework::web::{ | ||||||
|  |     get_logged_in_user_handler, oidc_callback_handler, oidc_login_handler, oidc_logout_handler, | ||||||
|  | }; | ||||||
|  | use utoipa::{OpenApi, openapi}; | ||||||
|  | 
 | ||||||
|  | #[derive(OpenApi)] | ||||||
|  | #[openapi(paths(
 | ||||||
|  |     get_logged_in_user_handler::get_logged_in_user_handler, | ||||||
|  |     oidc_callback_handler::oidc_callback_handler, | ||||||
|  |     oidc_login_handler::oidc_login_handler, | ||||||
|  |     oidc_logout_handler::oidc_logout_handler | ||||||
|  | ))] | ||||||
|  | struct ApiDoc; | ||||||
|  | 
 | ||||||
|  | pub fn openapi() -> openapi::OpenApi { | ||||||
|  |     ApiDoc::openapi() | ||||||
|  | } | ||||||
| @ -1,15 +1,9 @@ | |||||||
| use actix_session::Session; | use actix_web::web; | ||||||
| use actix_web::{HttpResponse, Responder, http::header, web}; |  | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::framework::web::{ | ||||||
|     adapter::delivery::{ |     get_logged_in_user_handler::get_logged_in_user_handler, | ||||||
|         auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto, |     oidc_callback_handler::oidc_callback_handler, oidc_login_handler::oidc_login_handler, | ||||||
|     }, |     oidc_logout_handler::oidc_logout_handler, | ||||||
|     application::error::auth_error::AuthError, |  | ||||||
|     framework::web::{ |  | ||||||
|         auth_middleware::UserId, |  | ||||||
|         constants::{SESSION_KEY_AUTH_NONCE, SESSION_KEY_AUTH_STATE, SESSION_KEY_USER_ID}, |  | ||||||
|     }, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) { | pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) { | ||||||
| @ -17,101 +11,8 @@ pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) { | |||||||
|         web::scope("/auth") |         web::scope("/auth") | ||||||
|             .route("/login", web::get().to(oidc_login_handler)) |             .route("/login", web::get().to(oidc_login_handler)) | ||||||
|             .route("/callback", web::get().to(oidc_callback_handler)) |             .route("/callback", web::get().to(oidc_callback_handler)) | ||||||
|             .route("/logout", web::get().to(logout_handler)), |             .route("/logout", web::get().to(oidc_logout_handler)), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     cfg.service(web::resource("/me").route(web::get().to(get_logged_in_user_handler))); |     cfg.service(web::resource("/me").route(web::get().to(get_logged_in_user_handler))); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| async fn oidc_login_handler( |  | ||||||
|     auth_controller: web::Data<dyn AuthController>, |  | ||||||
|     session: Session, |  | ||||||
| ) -> impl Responder { |  | ||||||
|     let result = auth_controller.oidc_login(); |  | ||||||
| 
 |  | ||||||
|     match result { |  | ||||||
|         Ok(auth_url) => { |  | ||||||
|             if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_STATE, auth_url.state) { |  | ||||||
|                 log::error!("{e:?}"); |  | ||||||
|                 return HttpResponse::InternalServerError().finish(); |  | ||||||
|             } |  | ||||||
|             if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_NONCE, auth_url.nonce) { |  | ||||||
|                 log::error!("{e:?}"); |  | ||||||
|                 return HttpResponse::InternalServerError().finish(); |  | ||||||
|             } |  | ||||||
|             HttpResponse::Found() |  | ||||||
|                 .append_header((header::LOCATION, auth_url.url)) |  | ||||||
|                 .finish() |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             log::error!("{e:?}"); |  | ||||||
|             HttpResponse::InternalServerError().finish() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn oidc_callback_handler( |  | ||||||
|     auth_controller: web::Data<dyn AuthController>, |  | ||||||
|     query: web::Query<OidcCallbackQueryDto>, |  | ||||||
|     session: Session, |  | ||||||
| ) -> impl Responder { |  | ||||||
|     let expected_state = match session.get::<String>(SESSION_KEY_AUTH_STATE) { |  | ||||||
|         Ok(Some(state)) => state, |  | ||||||
|         _ => return HttpResponse::BadRequest().finish(), |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     let expected_nonce = match session.get::<String>(SESSION_KEY_AUTH_NONCE) { |  | ||||||
|         Ok(Some(nonce)) => nonce, |  | ||||||
|         _ => return HttpResponse::BadRequest().finish(), |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     let result = auth_controller |  | ||||||
|         .oidc_callback(query.into_inner(), &expected_state, &expected_nonce) |  | ||||||
|         .await; |  | ||||||
| 
 |  | ||||||
|     session.remove(SESSION_KEY_AUTH_STATE); |  | ||||||
|     session.remove(SESSION_KEY_AUTH_NONCE); |  | ||||||
|     match result { |  | ||||||
|         Ok(user) => { |  | ||||||
|             if let Err(e) = session.insert::<i32>(SESSION_KEY_USER_ID, user.id) { |  | ||||||
|                 log::error!("{e:?}"); |  | ||||||
|                 return HttpResponse::InternalServerError().finish(); |  | ||||||
|             } |  | ||||||
|             HttpResponse::Found() |  | ||||||
|                 .append_header((header::LOCATION, "/")) |  | ||||||
|                 .finish() |  | ||||||
|         } |  | ||||||
|         Err(e) => match e { |  | ||||||
|             AuthError::InvalidAuthCode |  | ||||||
|             | AuthError::InvalidIdToken |  | ||||||
|             | AuthError::InvalidNonce |  | ||||||
|             | AuthError::InvalidState => HttpResponse::BadRequest().finish(), |  | ||||||
|             _ => { |  | ||||||
|                 log::error!("{e:?}"); |  | ||||||
|                 HttpResponse::InternalServerError().finish() |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn logout_handler(session: Session) -> impl Responder { |  | ||||||
|     session.clear(); |  | ||||||
|     HttpResponse::Found() |  | ||||||
|         .append_header((header::LOCATION, "/")) |  | ||||||
|         .finish() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn get_logged_in_user_handler( |  | ||||||
|     auth_controller: web::Data<dyn AuthController>, |  | ||||||
|     user_id: UserId, |  | ||||||
| ) -> impl Responder { |  | ||||||
|     let result = auth_controller.get_user(user_id.get()).await; |  | ||||||
| 
 |  | ||||||
|     match result { |  | ||||||
|         Ok(user) => HttpResponse::Ok().json(user), |  | ||||||
|         Err(e) => { |  | ||||||
|             log::error!("{e:?}"); |  | ||||||
|             HttpResponse::InternalServerError().finish() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -0,0 +1,33 @@ | |||||||
|  | use actix_web::{HttpResponse, Responder, web}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     adapter::delivery::{auth_controller::AuthController, user_response_dto::UserResponseDto}, | ||||||
|  |     framework::web::auth_middleware::UserId, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     get, | ||||||
|  |     path = "/me", | ||||||
|  |     tag = "auth", | ||||||
|  |     summary = "Get logged-in user information", | ||||||
|  |     responses( | ||||||
|  |         (status = 200, body = UserResponseDto), | ||||||
|  |     ), | ||||||
|  |     security( | ||||||
|  |         ("oauth2" = []) | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn get_logged_in_user_handler( | ||||||
|  |     auth_controller: web::Data<dyn AuthController>, | ||||||
|  |     user_id: UserId, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let result = auth_controller.get_user(user_id.get()).await; | ||||||
|  | 
 | ||||||
|  |     match result { | ||||||
|  |         Ok(user) => HttpResponse::Ok().json(user), | ||||||
|  |         Err(e) => { | ||||||
|  |             log::error!("{e:?}"); | ||||||
|  |             HttpResponse::InternalServerError().finish() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,69 @@ | |||||||
|  | use actix_session::Session; | ||||||
|  | use actix_web::{HttpResponse, Responder, http::header, web}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     adapter::delivery::{ | ||||||
|  |         auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto, | ||||||
|  |     }, | ||||||
|  |     application::error::auth_error::AuthError, | ||||||
|  |     framework::web::constants::{ | ||||||
|  |         SESSION_KEY_AUTH_NONCE, SESSION_KEY_AUTH_STATE, SESSION_KEY_USER_ID, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     get, | ||||||
|  |     path = "/auth/callback", | ||||||
|  |     tag = "auth", | ||||||
|  |     summary = "Handle OIDC callback", | ||||||
|  |     params( | ||||||
|  |         OidcCallbackQueryDto | ||||||
|  |     ), | ||||||
|  |     responses( | ||||||
|  |         (status = 302, description = "Redirect to home page"), | ||||||
|  |         (status = 400, description = "Invalid state or nonce"), | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn oidc_callback_handler( | ||||||
|  |     auth_controller: web::Data<dyn AuthController>, | ||||||
|  |     query: web::Query<OidcCallbackQueryDto>, | ||||||
|  |     session: Session, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let expected_state = match session.get::<String>(SESSION_KEY_AUTH_STATE) { | ||||||
|  |         Ok(Some(state)) => state, | ||||||
|  |         _ => return HttpResponse::BadRequest().finish(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let expected_nonce = match session.get::<String>(SESSION_KEY_AUTH_NONCE) { | ||||||
|  |         Ok(Some(nonce)) => nonce, | ||||||
|  |         _ => return HttpResponse::BadRequest().finish(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let result = auth_controller | ||||||
|  |         .oidc_callback(query.into_inner(), &expected_state, &expected_nonce) | ||||||
|  |         .await; | ||||||
|  | 
 | ||||||
|  |     session.remove(SESSION_KEY_AUTH_STATE); | ||||||
|  |     session.remove(SESSION_KEY_AUTH_NONCE); | ||||||
|  |     match result { | ||||||
|  |         Ok(user) => { | ||||||
|  |             if let Err(e) = session.insert::<i32>(SESSION_KEY_USER_ID, user.id) { | ||||||
|  |                 log::error!("{e:?}"); | ||||||
|  |                 return HttpResponse::InternalServerError().finish(); | ||||||
|  |             } | ||||||
|  |             HttpResponse::Found() | ||||||
|  |                 .append_header((header::LOCATION, "/")) | ||||||
|  |                 .finish() | ||||||
|  |         } | ||||||
|  |         Err(e) => match e { | ||||||
|  |             AuthError::InvalidAuthCode | ||||||
|  |             | AuthError::InvalidIdToken | ||||||
|  |             | AuthError::InvalidNonce | ||||||
|  |             | AuthError::InvalidState => HttpResponse::BadRequest().finish(), | ||||||
|  |             _ => { | ||||||
|  |                 log::error!("{e:?}"); | ||||||
|  |                 HttpResponse::InternalServerError().finish() | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								backend/feature/auth/src/framework/web/oidc_login_handler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								backend/feature/auth/src/framework/web/oidc_login_handler.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | use actix_session::Session; | ||||||
|  | use actix_web::{HttpResponse, Responder, http::header, web}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     adapter::delivery::auth_controller::AuthController, | ||||||
|  |     framework::web::constants::{SESSION_KEY_AUTH_NONCE, SESSION_KEY_AUTH_STATE}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     get, | ||||||
|  |     path = "/auth/login", | ||||||
|  |     tag = "auth", | ||||||
|  |     summary = "Initiate OIDC login", | ||||||
|  |     responses( | ||||||
|  |         (status = 302, description = "Redirect to OIDC provider") | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn oidc_login_handler( | ||||||
|  |     auth_controller: web::Data<dyn AuthController>, | ||||||
|  |     session: Session, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let result = auth_controller.oidc_login(); | ||||||
|  | 
 | ||||||
|  |     match result { | ||||||
|  |         Ok(auth_url) => { | ||||||
|  |             if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_STATE, auth_url.state) { | ||||||
|  |                 log::error!("{e:?}"); | ||||||
|  |                 return HttpResponse::InternalServerError().finish(); | ||||||
|  |             } | ||||||
|  |             if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_NONCE, auth_url.nonce) { | ||||||
|  |                 log::error!("{e:?}"); | ||||||
|  |                 return HttpResponse::InternalServerError().finish(); | ||||||
|  |             } | ||||||
|  |             HttpResponse::Found() | ||||||
|  |                 .append_header((header::LOCATION, auth_url.url)) | ||||||
|  |                 .finish() | ||||||
|  |         } | ||||||
|  |         Err(e) => { | ||||||
|  |             log::error!("{e:?}"); | ||||||
|  |             HttpResponse::InternalServerError().finish() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,18 @@ | |||||||
|  | use actix_session::Session; | ||||||
|  | use actix_web::{HttpResponse, Responder, http::header}; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     get, | ||||||
|  |     path = "/auth/logout", | ||||||
|  |     tag = "auth", | ||||||
|  |     summary = "Logout user", | ||||||
|  |     responses( | ||||||
|  |         (status = 302, description = "Redirect to home page") | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn oidc_logout_handler(session: Session) -> impl Responder { | ||||||
|  |     session.clear(); | ||||||
|  |     HttpResponse::Found() | ||||||
|  |         .append_header((header::LOCATION, "/")) | ||||||
|  |         .finish() | ||||||
|  | } | ||||||
| @ -11,5 +11,6 @@ futures.workspace = true | |||||||
| log.workspace = true | log.workspace = true | ||||||
| serde.workspace = true | serde.workspace = true | ||||||
| sqlx.workspace = true | sqlx.workspace = true | ||||||
|  | utoipa.workspace = true | ||||||
| 
 | 
 | ||||||
| auth.workspace = true | auth.workspace = true | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  | use utoipa::ToSchema; | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize, ToSchema)] | ||||||
| pub struct ImageInfoResponseDto { | pub struct ImageInfoResponseDto { | ||||||
|     pub id: i32, |     pub id: i32, | ||||||
|     pub mime_type: String, |     pub mime_type: String, | ||||||
|  | |||||||
| @ -1 +1,5 @@ | |||||||
| pub mod image_web_routes; | pub mod image_api_doc; | ||||||
|  | pub mod image_web_routes; | ||||||
|  | 
 | ||||||
|  | mod get_image_by_id_handler; | ||||||
|  | mod upload_image_handler; | ||||||
|  | |||||||
| @ -0,0 +1,43 @@ | |||||||
|  | use actix_web::{HttpResponse, Responder, web}; | ||||||
|  | use utoipa::ToSchema; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     adapter::delivery::image_controller::ImageController, | ||||||
|  |     application::error::image_error::ImageError, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     get, | ||||||
|  |     path = "/image/{id}", | ||||||
|  |     tag = "image", | ||||||
|  |     summary = "Get image by ID", | ||||||
|  |     responses ( | ||||||
|  |         (status = 200, body = inline(ResponseBodySchema), content_type = "image/*"), | ||||||
|  |         (status = 404, description = "Image not found") | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn get_image_by_id_handler( | ||||||
|  |     image_controller: web::Data<dyn ImageController>, | ||||||
|  |     path: web::Path<i32>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let id = path.into_inner(); | ||||||
|  |     let result = image_controller.get_image_by_id(id).await; | ||||||
|  | 
 | ||||||
|  |     match result { | ||||||
|  |         Ok(image_response) => HttpResponse::Ok() | ||||||
|  |             .content_type(image_response.mime_type) | ||||||
|  |             .body(image_response.data), | ||||||
|  |         Err(e) => match e { | ||||||
|  |             ImageError::NotFound => HttpResponse::NotFound().finish(), | ||||||
|  |             _ => { | ||||||
|  |                 log::error!("{e:?}"); | ||||||
|  |                 HttpResponse::InternalServerError().finish() | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(ToSchema)] | ||||||
|  | #[schema(value_type = String, format = Binary)] | ||||||
|  | #[allow(dead_code)] | ||||||
|  | struct ResponseBodySchema(Vec<u8>); | ||||||
							
								
								
									
										13
									
								
								backend/feature/image/src/framework/web/image_api_doc.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/feature/image/src/framework/web/image_api_doc.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | use crate::framework::web::{get_image_by_id_handler, upload_image_handler}; | ||||||
|  | use utoipa::{OpenApi, openapi}; | ||||||
|  | 
 | ||||||
|  | #[derive(OpenApi)] | ||||||
|  | #[openapi(paths(
 | ||||||
|  |     get_image_by_id_handler::get_image_by_id_handler, | ||||||
|  |     upload_image_handler::upload_image_handler | ||||||
|  | ))] | ||||||
|  | struct ApiDoc; | ||||||
|  | 
 | ||||||
|  | pub fn openapi() -> openapi::OpenApi { | ||||||
|  |     ApiDoc::openapi() | ||||||
|  | } | ||||||
| @ -1,11 +1,7 @@ | |||||||
| use actix_multipart::Multipart; | use actix_web::web; | ||||||
| use actix_web::{HttpResponse, Responder, web}; |  | ||||||
| use auth::framework::web::auth_middleware::UserId; |  | ||||||
| use futures::StreamExt; |  | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::framework::web::{ | ||||||
|     adapter::delivery::{image_controller::ImageController, image_request_dto::ImageRequestDto}, |     get_image_by_id_handler::get_image_by_id_handler, upload_image_handler::upload_image_handler, | ||||||
|     application::error::image_error::ImageError, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub fn configure_image_routes(cfg: &mut web::ServiceConfig) { | pub fn configure_image_routes(cfg: &mut web::ServiceConfig) { | ||||||
| @ -15,77 +11,3 @@ pub fn configure_image_routes(cfg: &mut web::ServiceConfig) { | |||||||
|             .route("/{id}", web::get().to(get_image_by_id_handler)), |             .route("/{id}", web::get().to(get_image_by_id_handler)), | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| async fn upload_image_handler( |  | ||||||
|     image_controller: web::Data<dyn ImageController>, |  | ||||||
|     mut payload: Multipart, |  | ||||||
|     _: UserId, |  | ||||||
| ) -> impl Responder { |  | ||||||
|     let mut image_request_dto: Option<ImageRequestDto> = None; |  | ||||||
| 
 |  | ||||||
|     while let Some(item) = payload.next().await { |  | ||||||
|         let mut field = match item { |  | ||||||
|             Ok(field) => field, |  | ||||||
|             Err(_) => return HttpResponse::BadRequest().finish(), |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         if field.name() != Some("file") { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let mime_type = field |  | ||||||
|             .content_type() |  | ||||||
|             .cloned() |  | ||||||
|             .map(|mt| mt.to_string()) |  | ||||||
|             .unwrap_or_else(|| "application/octet-stream".to_string()); |  | ||||||
| 
 |  | ||||||
|         let mut data = Vec::new(); |  | ||||||
|         while let Some(chunk) = field.next().await { |  | ||||||
|             match chunk { |  | ||||||
|                 Ok(bytes) => data.extend_from_slice(&bytes), |  | ||||||
|                 Err(_) => return HttpResponse::InternalServerError().finish(), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         image_request_dto = Some(ImageRequestDto { mime_type, data }); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let image_request_dto = match image_request_dto { |  | ||||||
|         Some(dto) => dto, |  | ||||||
|         None => return HttpResponse::BadRequest().finish(), |  | ||||||
|     }; |  | ||||||
|     let result = image_controller.upload_image(image_request_dto).await; |  | ||||||
| 
 |  | ||||||
|     match result { |  | ||||||
|         Ok(image_info) => HttpResponse::Created().json(image_info), |  | ||||||
|         Err(e) => match e { |  | ||||||
|             ImageError::UnsupportedMimeType => HttpResponse::BadRequest().body(format!("{e:?}")), |  | ||||||
|             _ => { |  | ||||||
|                 log::error!("{e:?}"); |  | ||||||
|                 HttpResponse::InternalServerError().finish() |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn get_image_by_id_handler( |  | ||||||
|     image_controller: web::Data<dyn ImageController>, |  | ||||||
|     path: web::Path<i32>, |  | ||||||
| ) -> impl Responder { |  | ||||||
|     let id = path.into_inner(); |  | ||||||
|     let result = image_controller.get_image_by_id(id).await; |  | ||||||
| 
 |  | ||||||
|     match result { |  | ||||||
|         Ok(image_response) => HttpResponse::Ok() |  | ||||||
|             .content_type(image_response.mime_type) |  | ||||||
|             .body(image_response.data), |  | ||||||
|         Err(e) => match e { |  | ||||||
|             ImageError::NotFound => HttpResponse::NotFound().finish(), |  | ||||||
|             _ => { |  | ||||||
|                 log::error!("{e:?}"); |  | ||||||
|                 HttpResponse::InternalServerError().finish() |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -0,0 +1,90 @@ | |||||||
|  | use actix_multipart::Multipart; | ||||||
|  | use actix_web::{HttpResponse, Responder, web}; | ||||||
|  | use auth::framework::web::auth_middleware::UserId; | ||||||
|  | use futures::StreamExt; | ||||||
|  | use utoipa::ToSchema; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     adapter::delivery::{ | ||||||
|  |         image_controller::ImageController, image_info_response_dto::ImageInfoResponseDto, | ||||||
|  |         image_request_dto::ImageRequestDto, | ||||||
|  |     }, | ||||||
|  |     application::error::image_error::ImageError, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     post, | ||||||
|  |     path = "/image/upload", | ||||||
|  |     tag = "image", | ||||||
|  |     summary = "Upload an image", | ||||||
|  |     request_body ( | ||||||
|  |         content = RequestBodySchema, | ||||||
|  |         content_type = "multipart/form-data", | ||||||
|  |     ), | ||||||
|  |     responses ( | ||||||
|  |         (status = 201, body = ImageInfoResponseDto), | ||||||
|  |         (status = 400, description = "Unsupported MIME type or file field not found"), | ||||||
|  |     ), | ||||||
|  |     security( | ||||||
|  |         ("oauth2" = []) | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn upload_image_handler( | ||||||
|  |     image_controller: web::Data<dyn ImageController>, | ||||||
|  |     mut payload: Multipart, | ||||||
|  |     _: UserId, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let mut image_request_dto: Option<ImageRequestDto> = None; | ||||||
|  | 
 | ||||||
|  |     while let Some(item) = payload.next().await { | ||||||
|  |         let mut field = match item { | ||||||
|  |             Ok(field) => field, | ||||||
|  |             Err(_) => return HttpResponse::BadRequest().finish(), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if field.name() != Some("file") { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let mime_type = field | ||||||
|  |             .content_type() | ||||||
|  |             .cloned() | ||||||
|  |             .map(|mt| mt.to_string()) | ||||||
|  |             .unwrap_or_else(|| "application/octet-stream".to_string()); | ||||||
|  | 
 | ||||||
|  |         let mut data = Vec::new(); | ||||||
|  |         while let Some(chunk) = field.next().await { | ||||||
|  |             match chunk { | ||||||
|  |                 Ok(bytes) => data.extend_from_slice(&bytes), | ||||||
|  |                 Err(_) => return HttpResponse::InternalServerError().finish(), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         image_request_dto = Some(ImageRequestDto { mime_type, data }); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let image_request_dto = match image_request_dto { | ||||||
|  |         Some(dto) => dto, | ||||||
|  |         None => return HttpResponse::BadRequest().finish(), | ||||||
|  |     }; | ||||||
|  |     let result = image_controller.upload_image(image_request_dto).await; | ||||||
|  | 
 | ||||||
|  |     match result { | ||||||
|  |         Ok(image_info) => HttpResponse::Created().json(image_info), | ||||||
|  |         Err(e) => match e { | ||||||
|  |             ImageError::UnsupportedMimeType => HttpResponse::BadRequest().body(format!("{e:?}")), | ||||||
|  |             _ => { | ||||||
|  |                 log::error!("{e:?}"); | ||||||
|  |                 HttpResponse::InternalServerError().finish() | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(ToSchema)] | ||||||
|  | #[allow(dead_code)] | ||||||
|  | struct RequestBodySchema { | ||||||
|  |     #[schema(value_type = String, format = Binary)] | ||||||
|  |     file: Vec<u8>, | ||||||
|  | } | ||||||
| @ -10,3 +10,4 @@ chrono.workspace = true | |||||||
| log.workspace = true | log.workspace = true | ||||||
| serde.workspace = true | serde.workspace = true | ||||||
| sqlx.workspace = true | sqlx.workspace = true | ||||||
|  | utoipa.workspace = true | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  | use utoipa::ToSchema; | ||||||
| 
 | 
 | ||||||
| use crate::domain::entity::color::Color; | use crate::domain::entity::color::Color; | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize, ToSchema)] | ||||||
| pub struct ColorResponseDto { | pub struct ColorResponseDto { | ||||||
|     pub red: u8, |     pub red: u8, | ||||||
|     pub green: u8, |     pub green: u8, | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  | use utoipa::ToSchema; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     adapter::delivery::color_response_dto::ColorResponseDto, domain::entity::label::Label, |     adapter::delivery::color_response_dto::ColorResponseDto, domain::entity::label::Label, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize, ToSchema)] | ||||||
| pub struct LabelResponseDto { | pub struct LabelResponseDto { | ||||||
|     pub id: i32, |     pub id: i32, | ||||||
|     pub name: String, |     pub name: String, | ||||||
|  | |||||||
| @ -2,11 +2,14 @@ use std::sync::Arc; | |||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| 
 | 
 | ||||||
| use crate::application::{ | use crate::{ | ||||||
|     error::post_error::PostError, |     adapter::delivery::post_info_query_dto::PostQueryDto, | ||||||
|     use_case::{ |     application::{ | ||||||
|         get_all_post_info_use_case::GetAllPostInfoUseCase, |         error::post_error::PostError, | ||||||
|         get_full_post_use_case::GetFullPostUseCase, |         use_case::{ | ||||||
|  |             get_all_post_info_use_case::GetAllPostInfoUseCase, | ||||||
|  |             get_full_post_use_case::GetFullPostUseCase, | ||||||
|  |         }, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -16,10 +19,10 @@ use super::{post_info_response_dto::PostInfoResponseDto, post_response_dto::Post | |||||||
| pub trait PostController: Send + Sync { | pub trait PostController: Send + Sync { | ||||||
|     async fn get_all_post_info( |     async fn get_all_post_info( | ||||||
|         &self, |         &self, | ||||||
|         is_published_only: bool, |         query: PostQueryDto, | ||||||
|     ) -> Result<Vec<PostInfoResponseDto>, PostError>; |     ) -> Result<Vec<PostInfoResponseDto>, PostError>; | ||||||
| 
 | 
 | ||||||
|     async fn get_full_post(&self, id: i32) -> Result<PostResponseDto, PostError>; |     async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct PostControllerImpl { | pub struct PostControllerImpl { | ||||||
| @ -43,9 +46,12 @@ impl PostControllerImpl { | |||||||
| impl PostController for PostControllerImpl { | impl PostController for PostControllerImpl { | ||||||
|     async fn get_all_post_info( |     async fn get_all_post_info( | ||||||
|         &self, |         &self, | ||||||
|         is_published_only: bool, |         query: PostQueryDto, | ||||||
|     ) -> Result<Vec<PostInfoResponseDto>, PostError> { |     ) -> Result<Vec<PostInfoResponseDto>, PostError> { | ||||||
|         let result = self.get_all_post_info_use_case.execute(is_published_only).await; |         let result = self | ||||||
|  |             .get_all_post_info_use_case | ||||||
|  |             .execute(query.is_published_only.unwrap_or(true)) | ||||||
|  |             .await; | ||||||
| 
 | 
 | ||||||
|         result.map(|post_info_list| { |         result.map(|post_info_list| { | ||||||
|             let post_info_response_dto_list: Vec<PostInfoResponseDto> = post_info_list |             let post_info_response_dto_list: Vec<PostInfoResponseDto> = post_info_list | ||||||
| @ -57,7 +63,7 @@ impl PostController for PostControllerImpl { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn get_full_post(&self, id: i32) -> Result<PostResponseDto, PostError> { |     async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError> { | ||||||
|         let result = self.get_full_post_use_case.execute(id).await; |         let result = self.get_full_post_use_case.execute(id).await; | ||||||
| 
 | 
 | ||||||
|         result.map(PostResponseDto::from) |         result.map(PostResponseDto::from) | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| use serde::Deserialize; | use serde::Deserialize; | ||||||
|  | use utoipa::IntoParams; | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize)] | #[derive(Deserialize, IntoParams)] | ||||||
| pub struct PostQueryDto { | pub struct PostQueryDto { | ||||||
|  |     #[param(default = true)] | ||||||
|     pub is_published_only: Option<bool>, |     pub is_published_only: Option<bool>, | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  | use utoipa::ToSchema; | ||||||
| 
 | 
 | ||||||
| use crate::domain::entity::post_info::PostInfo; | use crate::domain::entity::post_info::PostInfo; | ||||||
| 
 | 
 | ||||||
| use super::label_response_dto::LabelResponseDto; | use super::label_response_dto::LabelResponseDto; | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize, ToSchema)] | ||||||
| pub struct PostInfoResponseDto { | pub struct PostInfoResponseDto { | ||||||
|     pub id: i32, |     pub id: i32, | ||||||
|     pub title: String, |     pub title: String, | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  | use utoipa::ToSchema; | ||||||
| 
 | 
 | ||||||
| use crate::domain::entity::post::Post; | use crate::domain::entity::post::Post; | ||||||
| 
 | 
 | ||||||
| use super::post_info_response_dto::PostInfoResponseDto; | use super::post_info_response_dto::PostInfoResponseDto; | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize, ToSchema)] | ||||||
| pub struct PostResponseDto { | pub struct PostResponseDto { | ||||||
|     pub id: i32, |     pub id: i32, | ||||||
|     pub info: PostInfoResponseDto, |     pub info: PostInfoResponseDto, | ||||||
|  | |||||||
| @ -1 +1,5 @@ | |||||||
|  | pub mod post_api_doc; | ||||||
| pub mod post_web_routes; | pub mod post_web_routes; | ||||||
|  | 
 | ||||||
|  | mod get_all_post_info_handler; | ||||||
|  | mod get_post_by_id_handler; | ||||||
|  | |||||||
| @ -0,0 +1,33 @@ | |||||||
|  | use actix_web::{HttpResponse, Responder, web}; | ||||||
|  | 
 | ||||||
|  | use crate::adapter::delivery::{ | ||||||
|  |     post_controller::PostController, post_info_query_dto::PostQueryDto, | ||||||
|  |     post_info_response_dto::PostInfoResponseDto, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     get, | ||||||
|  |     path = "/post/all", | ||||||
|  |     tag = "post", | ||||||
|  |     summary = "Get all post information", | ||||||
|  |     params( | ||||||
|  |         PostQueryDto | ||||||
|  |     ), | ||||||
|  |     responses ( | ||||||
|  |         (status = 200, body = [PostInfoResponseDto]) | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn get_all_post_info_handler( | ||||||
|  |     post_controller: web::Data<dyn PostController>, | ||||||
|  |     query: web::Query<PostQueryDto>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let result = post_controller.get_all_post_info(query.into_inner()).await; | ||||||
|  | 
 | ||||||
|  |     match result { | ||||||
|  |         Ok(post_info_list) => HttpResponse::Ok().json(post_info_list), | ||||||
|  |         Err(e) => { | ||||||
|  |             log::error!("{e:?}"); | ||||||
|  |             HttpResponse::InternalServerError().finish() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | use actix_web::{HttpResponse, Responder, web}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     adapter::delivery::{post_controller::PostController, post_response_dto::PostResponseDto}, | ||||||
|  |     application::error::post_error::PostError, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[utoipa::path(
 | ||||||
|  |     get, | ||||||
|  |     path = "/post/{id}", | ||||||
|  |     tag = "post", | ||||||
|  |     summary = "Get post by ID", | ||||||
|  |     responses ( | ||||||
|  |         (status = 200, body = PostResponseDto), | ||||||
|  |         (status = 404, description = "Post not found") | ||||||
|  |     ) | ||||||
|  | )] | ||||||
|  | pub async fn get_post_by_id_handler( | ||||||
|  |     post_controller: web::Data<dyn PostController>, | ||||||
|  |     path: web::Path<i32>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     let id = path.into_inner(); | ||||||
|  |     let result = post_controller.get_post_by_id(id).await; | ||||||
|  | 
 | ||||||
|  |     match result { | ||||||
|  |         Ok(post) => HttpResponse::Ok().json(post), | ||||||
|  |         Err(e) => { | ||||||
|  |             if e == PostError::NotFound { | ||||||
|  |                 HttpResponse::NotFound().finish() | ||||||
|  |             } else { | ||||||
|  |                 log::error!("{e:?}"); | ||||||
|  |                 HttpResponse::InternalServerError().finish() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								backend/feature/post/src/framework/web/post_api_doc.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/feature/post/src/framework/web/post_api_doc.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | use crate::framework::web::{get_all_post_info_handler, get_post_by_id_handler}; | ||||||
|  | use utoipa::{OpenApi, openapi}; | ||||||
|  | 
 | ||||||
|  | #[derive(OpenApi)] | ||||||
|  | #[openapi(paths(
 | ||||||
|  |     get_all_post_info_handler::get_all_post_info_handler, | ||||||
|  |     get_post_by_id_handler::get_post_by_id_handler | ||||||
|  | ))] | ||||||
|  | struct ApiDoc; | ||||||
|  | 
 | ||||||
|  | pub fn openapi() -> openapi::OpenApi { | ||||||
|  |     ApiDoc::openapi() | ||||||
|  | } | ||||||
| @ -1,50 +1,14 @@ | |||||||
| use actix_web::{HttpResponse, Responder, web}; | use actix_web::web; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::framework::web::{ | ||||||
|     adapter::delivery::{post_controller::PostController, post_info_query_dto::PostQueryDto}, |     get_all_post_info_handler::get_all_post_info_handler, | ||||||
|     application::error::post_error::PostError, |     get_post_by_id_handler::get_post_by_id_handler, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub fn configure_post_routes(cfg: &mut web::ServiceConfig) { | pub fn configure_post_routes(cfg: &mut web::ServiceConfig) { | ||||||
|     cfg.service( |     cfg.service( | ||||||
|         web::scope("/post") |         web::scope("/post") | ||||||
|             .route("/all", web::get().to(get_all_post_info_handler)) |             .route("/all", web::get().to(get_all_post_info_handler)) | ||||||
|             .route("/{id}", web::get().to(get_full_post_handler)), |             .route("/{id}", web::get().to(get_post_by_id_handler)), | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| async fn get_all_post_info_handler( |  | ||||||
|     post_controller: web::Data<dyn PostController>, |  | ||||||
|     query: web::Query<PostQueryDto>, |  | ||||||
| ) -> impl Responder { |  | ||||||
|     let is_published_only = query.is_published_only.unwrap_or_else(|| true); |  | ||||||
|     let result = post_controller.get_all_post_info(is_published_only).await; |  | ||||||
| 
 |  | ||||||
|     match result { |  | ||||||
|         Ok(post_info_list) => HttpResponse::Ok().json(post_info_list), |  | ||||||
|         Err(e) => { |  | ||||||
|             log::error!("{e:?}"); |  | ||||||
|             HttpResponse::InternalServerError().finish() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn get_full_post_handler( |  | ||||||
|     post_controller: web::Data<dyn PostController>, |  | ||||||
|     path: web::Path<i32>, |  | ||||||
| ) -> impl Responder { |  | ||||||
|     let id = path.into_inner(); |  | ||||||
|     let result = post_controller.get_full_post(id).await; |  | ||||||
| 
 |  | ||||||
|     match result { |  | ||||||
|         Ok(post) => HttpResponse::Ok().json(post), |  | ||||||
|         Err(e) => { |  | ||||||
|             if e == PostError::NotFound { |  | ||||||
|                 HttpResponse::NotFound().finish() |  | ||||||
|             } else { |  | ||||||
|                 log::error!("{e:?}"); |  | ||||||
|                 HttpResponse::InternalServerError().finish() |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										46
									
								
								backend/server/src/api_doc.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								backend/server/src/api_doc.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | use actix_web::web; | ||||||
|  | use auth::framework::web::auth_api_doc; | ||||||
|  | use image::framework::web::image_api_doc; | ||||||
|  | use post::framework::web::post_api_doc; | ||||||
|  | use utoipa::{ | ||||||
|  |     OpenApi, | ||||||
|  |     openapi::{ | ||||||
|  |         Components, InfoBuilder, OpenApiBuilder, | ||||||
|  |         security::{AuthorizationCode, Flow, OAuth2, Scopes, SecurityScheme}, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | use utoipa_redoc::{Redoc, Servable}; | ||||||
|  | 
 | ||||||
|  | pub struct ApiDoc; | ||||||
|  | 
 | ||||||
|  | impl utoipa::OpenApi for ApiDoc { | ||||||
|  |     fn openapi() -> utoipa::openapi::OpenApi { | ||||||
|  |         let mut components = Components::new(); | ||||||
|  | 
 | ||||||
|  |         components.add_security_scheme( | ||||||
|  |             "oauth2", | ||||||
|  |             SecurityScheme::OAuth2(OAuth2::new(vec![Flow::AuthorizationCode( | ||||||
|  |                 AuthorizationCode::new("/auth/login", "/auth/callback", Scopes::new()), | ||||||
|  |             )])), | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         OpenApiBuilder::new() | ||||||
|  |             .info( | ||||||
|  |                 InfoBuilder::new() | ||||||
|  |                     .title("SquidSpirit API") | ||||||
|  |                     .version(env!("CARGO_PKG_VERSION")) | ||||||
|  |                     .build(), | ||||||
|  |             ) | ||||||
|  |             .components(Some(components)) | ||||||
|  |             .build() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn configure_api_doc_routes(cfg: &mut web::ServiceConfig) { | ||||||
|  |     let openapi = ApiDoc::openapi() | ||||||
|  |         .merge_from(auth_api_doc::openapi()) | ||||||
|  |         .merge_from(image_api_doc::openapi()) | ||||||
|  |         .merge_from(post_api_doc::openapi()); | ||||||
|  | 
 | ||||||
|  |     cfg.service(Redoc::with_url("/redoc", openapi)); | ||||||
|  | } | ||||||
| @ -1,14 +0,0 @@ | |||||||
| use actix_web::web; |  | ||||||
| use utoipa::OpenApi; |  | ||||||
| use utoipa_redoc::{Redoc, Servable}; |  | ||||||
| 
 |  | ||||||
| #[derive(OpenApi)] |  | ||||||
| #[openapi(info(
 |  | ||||||
|     title = "SquidSpirit API", |  | ||||||
|     version = env!("CARGO_PKG_VERSION") |  | ||||||
| ))] |  | ||||||
| pub struct ApiDoc; |  | ||||||
| 
 |  | ||||||
| pub fn configure_api_doc_routes(cfg: &mut web::ServiceConfig) { |  | ||||||
|     cfg.service(Redoc::with_url("/redoc", ApiDoc::openapi())); |  | ||||||
| } |  | ||||||
| @ -1,3 +1,3 @@ | |||||||
| pub mod apidoc; | pub mod api_doc; | ||||||
| pub mod configuration; | pub mod configuration; | ||||||
| pub mod container; | pub mod container; | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ use auth::framework::web::auth_web_routes::configure_auth_routes; | |||||||
| use image::framework::web::image_web_routes::configure_image_routes; | use image::framework::web::image_web_routes::configure_image_routes; | ||||||
| use openidconnect::reqwest; | use openidconnect::reqwest; | ||||||
| use post::framework::web::post_web_routes::configure_post_routes; | use post::framework::web::post_web_routes::configure_post_routes; | ||||||
| use server::{apidoc::configure_api_doc_routes, configuration::Configuration, container::Container}; | use server::{api_doc::configure_api_doc_routes, configuration::Configuration, container::Container}; | ||||||
| use sqlx::{Pool, Postgres}; | use sqlx::{Pool, Postgres}; | ||||||
| 
 | 
 | ||||||
| #[actix_web::main] | #[actix_web::main] | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user