diff --git a/README.md b/README.md index 0b34bc6..1c61521 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# actix-web-sample-app \ No newline at end of file +# Actix-Admin + diff --git a/example/src/main.rs b/example/src/main.rs index fe6e0aa..3ac1598 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -7,6 +7,7 @@ use azure_auth::{AppDataTrait as AzureAuthAppDataTrait, AzureAuth, UserInfo}; use oauth2::basic::BasicClient; use oauth2::RedirectUrl; use sea_orm::{ConnectOptions, DatabaseConnection}; +use actix_web::http::header::ContentType; use std::env; use std::time::Duration; use tera::{Context, Tera}; @@ -37,6 +38,19 @@ impl AzureAuthAppDataTrait for AppState { } } +async fn custom_handler< + T: ActixAdminAppDataTrait, + E: ActixAdminViewModelTrait, +>( + _session: Session, + _data: web::Data, + _text: String +) -> HttpResponse { + HttpResponse::Ok() + .content_type(ContentType::plaintext()) + .body("data") +} + async fn index(session: Session, data: web::Data) -> HttpResponse { let login = session.get::("user_info").unwrap(); let web_auth_link = if login.is_some() { @@ -62,12 +76,16 @@ fn create_actix_admin_builder() -> ActixAdminBuilder { user_info.is_some() }), login_link: Some("/azure-auth/login".to_string()), - logout_link: Some("/azure-auth/logout".to_string() + logout_link: Some("/azure-auth/logout".to_string()) }; let mut admin_builder = ActixAdminBuilder::new(configuration); admin_builder.add_entity::(&post_view_model); admin_builder.add_entity::(&comment_view_model); + admin_builder.add_custom_handler_for_entity::( + "/custom_handler", + web::get().to(custom_handler::) + ); admin_builder } diff --git a/src/builder.rs b/src/builder.rs index 75d8871..073f928 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,4 +1,4 @@ -use actix_web::web; +use actix_web::{ web, Route }; use std::collections::HashMap; use crate::prelude::*; @@ -6,7 +6,7 @@ use crate::prelude::*; use crate::routes::{create_get, create_post, delete, delete_many, edit_get, edit_post, index, list}; pub struct ActixAdminBuilder { - pub scopes: Vec, + pub scopes: HashMap, pub actix_admin: ActixAdmin, } @@ -16,6 +16,11 @@ pub trait ActixAdminBuilderTrait { &mut self, view_model: &ActixAdminViewModel, ); + fn add_custom_handler_for_entity( + &mut self, + path: &str, + route: Route + ); fn get_scope(self) -> actix_web::Scope; fn get_actix_admin(&self) -> ActixAdmin; } @@ -28,7 +33,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { view_models: HashMap::new(), configuration: configuration }, - scopes: Vec::new(), + scopes: HashMap::new(), } } @@ -36,7 +41,8 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { &mut self, view_model: &ActixAdminViewModel, ) { - self.scopes.push( + self.scopes.insert( + E::get_entity_name(), web::scope(&format!("/{}", E::get_entity_name())) .route("/list", web::get().to(list::)) .route("/create", web::get().to(create_get::)) @@ -52,13 +58,38 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { self.actix_admin.view_models.insert(key, view_model.clone()); } - fn get_scope(self) -> actix_web::Scope { - let mut scope = web::scope("/admin").route("/", web::get().to(index::)); - for entity_scope in self.scopes { - scope = scope.service(entity_scope); + fn add_custom_handler_for_entity( + &mut self, + path: &str, + route: Route + ) { + let existing_scope = self.scopes.remove(&E::get_entity_name()); + match existing_scope { + Some(scope) => { + let existing_scope = scope.route(path, route); + self.scopes.insert(E::get_entity_name(), existing_scope); + }, + _ => { + let new_scope = + web::scope(&format!("/{}", E::get_entity_name())) + .route(path, route); + self.scopes.insert(E::get_entity_name(), new_scope); + } + } + + if !self.actix_admin.entity_names.contains(&E::get_entity_name()) { + self.actix_admin.entity_names.push(E::get_entity_name()); + } + } + + fn get_scope(mut self) -> actix_web::Scope { + let mut admin_scope = web::scope("/admin").route("/", web::get().to(index::)); + for entity_name in self.actix_admin.entity_names { + let scope = self.scopes.remove(&entity_name).unwrap(); + admin_scope = admin_scope.service(scope); } - scope + admin_scope } fn get_actix_admin(&self) -> ActixAdmin { diff --git a/src/lib.rs b/src/lib.rs index f766471..0fba17c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ pub mod prelude { pub use actix_admin_macros::{ DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel, DeriveActixAdminEnumSelectList, DeriveActixAdminModelSelectList }; pub use crate::{ ActixAdminAppDataTrait, ActixAdmin, ActixAdminConfiguration }; pub use crate::{ hashmap, ActixAdminSelectListTrait }; + pub use crate::routes::{ create_or_edit_post }; } use crate::prelude::*; diff --git a/src/routes/create_or_edit_post.rs b/src/routes/create_or_edit_post.rs index cab6bfe..5007c7e 100644 --- a/src/routes/create_or_edit_post.rs +++ b/src/routes/create_or_edit_post.rs @@ -1,30 +1,38 @@ -use actix_web::http::header; -use actix_web::{web, error, Error, HttpResponse}; -use tera::{Context}; -use actix_session::{Session}; -use crate::TERA; -use actix_multipart::Multipart; -use super::{ user_can_access_page, render_unauthorized}; +use super::{render_unauthorized, user_can_access_page}; use crate::prelude::*; +use crate::TERA; +use actix_session::Session; +use actix_web::http::header; +use actix_web::{error, web, Error, HttpResponse}; +use tera::Context; +use actix_multipart::Multipart; +use std::collections::HashMap; pub async fn create_post( session: Session, data: web::Data, payload: Multipart, ) -> Result { - create_or_edit_post::(&session, &data, payload, None).await + let model = ActixAdminModel::create_from_payload(payload).await.unwrap(); + create_or_edit_post::(&session, &data, model, None).await } pub async fn edit_post( session: Session, data: web::Data, payload: Multipart, - id: web::Path + id: web::Path, ) -> Result { - create_or_edit_post::(&session, &data, payload, Some(id.into_inner())).await + let model = ActixAdminModel::create_from_payload(payload).await.unwrap(); + create_or_edit_post::(&session, &data, model, Some(id.into_inner())).await } -async fn create_or_edit_post(session: &Session, data: &web::Data, payload: Multipart, id: Option) -> Result { +pub async fn create_or_edit_post( + session: &Session, + data: &web::Data, + mut model: ActixAdminModel, + id: Option, +) -> Result { let actix_admin = data.get_actix_admin(); let entity_name = E::get_entity_name(); @@ -35,15 +43,16 @@ async fn create_or_edit_post E::edit_entity(db, id, model).await, - None => E::create_entity(db, model).await + None => E::create_entity(db, model).await, }; Ok(HttpResponse::SeeOther() @@ -65,5 +73,29 @@ async fn create_or_edit_post for ActixAdminModel { + fn from(string: String) -> Self { + let mut hashmap = HashMap::new(); + let key_values: Vec<&str> = string.split('&').collect(); + for key_value in key_values { + if !key_value.is_empty() { + let mut iter = key_value.splitn(2, '='); + hashmap.insert( + iter.next().unwrap().to_string(), + iter.next().unwrap().to_string(), + ); + } } + + ActixAdminModel { + primary_key: None, + values: hashmap, + errors: HashMap::new(), + custom_errors: HashMap::new(), + } + } } \ No newline at end of file diff --git a/src/routes/mod.rs b/src/routes/mod.rs index ed9e49e..a1b5bf6 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -2,7 +2,7 @@ mod create_or_edit_get; pub use create_or_edit_get::{create_get, edit_get}; mod create_or_edit_post; -pub use create_or_edit_post::{ create_post, edit_post }; +pub use create_or_edit_post::{ create_post, edit_post, create_or_edit_post }; mod index; pub use index::index; diff --git a/tests/integration_test_get_resp_is_success.rs b/tests/integration_test_get_resp_is_success.rs index 9817349..791d779 100644 --- a/tests/integration_test_get_resp_is_success.rs +++ b/tests/integration_test_get_resp_is_success.rs @@ -11,27 +11,27 @@ mod tests { use actix_admin::prelude::*; #[actix_web::test] - async fn test_admin_index_get() { + async fn admin_index_get() { test_get_is_success("/admin/").await } #[actix_web::test] - async fn test_post_list() { + async fn post_list_get() { test_get_is_success("/admin/post/list").await } #[actix_web::test] - async fn test_comment_list() { + async fn comment_list_get() { test_get_is_success("/admin/comment/list").await } #[actix_web::test] - async fn test_post_create() { + async fn post_create_get() { test_get_is_success("/admin/post/create").await } #[actix_web::test] - async fn test_comment_create() { + async fn comment_create_get() { test_get_is_success("/admin/comment/create").await } diff --git a/tests/integration_test_post_resp_is_success.rs b/tests/integration_test_post_resp_is_success.rs new file mode 100644 index 0000000..35d2e77 --- /dev/null +++ b/tests/integration_test_post_resp_is_success.rs @@ -0,0 +1,60 @@ +mod test_setup; +use test_setup::helper::{create_actix_admin_builder, create_tables_and_get_connection, AppState}; + +#[cfg(test)] +mod tests { + extern crate serde_derive; + + use actix_admin::prelude::*; + use actix_web::http::header::ContentType; + use actix_web::test; + use actix_web::{middleware, web, App}; + + #[actix_web::test] + async fn comment_create_post() { + test_post_is_success("/admin/comment/create_post_from_plaintext").await + } + + #[actix_web::test] + async fn post_create_post() { + test_post_is_success("/admin/post/create_post_from_plaintext").await + } + + #[actix_web::test] + async fn post_edit_post() { + test_post_is_success("/admin/post/edit_post_from_plaintext").await + } + + #[actix_web::test] + async fn comment_edit_post() { + test_post_is_success("/admin/comment/edit_post_from_plaintext").await + } + + async fn test_post_is_success(url: &str) { + let conn = super::create_tables_and_get_connection().await; + + let actix_admin_builder = super::create_actix_admin_builder(); + let actix_admin = actix_admin_builder.get_actix_admin(); + + let app_state = super::AppState { + db: conn, + actix_admin: actix_admin, + }; + + let app = test::init_service( + App::new() + .app_data(web::Data::new(app_state.clone())) + .service(actix_admin_builder.get_scope::()) + .wrap(middleware::Logger::default()), + ) + .await; + + let req = test::TestRequest::post() + .insert_header(ContentType::form_url_encoded()) + .uri(url) + .to_request(); + let resp = test::call_service(&app, req).await; + + assert!(resp.status().is_success()); + } +} diff --git a/tests/test_setup/helper.rs b/tests/test_setup/helper.rs index 6f6441e..422fc6c 100644 --- a/tests/test_setup/helper.rs +++ b/tests/test_setup/helper.rs @@ -1,5 +1,9 @@ use sea_orm::{ConnectOptions, DatabaseConnection}; use actix_admin::prelude::*; +use actix_web::Error; +use actix_session::Session; +use actix_web::HttpResponse; +use actix_web::{web}; use super::{Post, Comment, create_tables}; @@ -43,5 +47,47 @@ pub fn create_actix_admin_builder() -> ActixAdminBuilder { admin_builder.add_entity::(&post_view_model); admin_builder.add_entity::(&comment_view_model); + admin_builder.add_custom_handler_for_entity::( + "/create_comment_from_plaintext", + web::post().to(create_post_from_plaintext::)); + + admin_builder.add_custom_handler_for_entity::( + "/create_post_from_plaintext", + web::post().to(create_post_from_plaintext::)); + + admin_builder.add_custom_handler_for_entity::( + "/edit_post_from_plaintext/{id}", + web::post().to(edit_post_from_plaintext::)); + + admin_builder.add_custom_handler_for_entity::( + "/edit_comment_from_plaintext/{id}", + web::post().to(edit_post_from_plaintext::)); + admin_builder } + +async fn create_post_from_plaintext< + T: ActixAdminAppDataTrait, + E: ActixAdminViewModelTrait, +>( + session: Session, + data: web::Data, + text: String, +) -> Result { + let model = ActixAdminModel::from(text); + create_or_edit_post::(&session, &data, model, None).await +} + +async fn edit_post_from_plaintext< + T: ActixAdminAppDataTrait, + E: ActixAdminViewModelTrait, +>( + session: Session, + data: web::Data, + text: String, + id: web::Path, +) -> Result { + println!("ok"); + let model = ActixAdminModel::from(text); + create_or_edit_post::(&session, &data, model, Some(id.into_inner())).await +} \ No newline at end of file