create actix_admin_builder function

This commit is contained in:
Manuel Gugger 2022-05-25 19:34:29 +02:00
parent 7c8cf72668
commit 90a17e64d3
3 changed files with 108 additions and 67 deletions

View File

@ -1,11 +1,11 @@
use actix_web::http::header;
use actix_web::{error, web, Error, HttpRequest, HttpResponse}; use actix_web::{error, web, Error, HttpRequest, HttpResponse};
use async_trait::async_trait;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use sea_orm::{ DatabaseConnection}; use sea_orm::DatabaseConnection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use actix_web::http::header;
use tera::{Context, Tera}; use tera::{Context, Tera};
use async_trait::async_trait;
pub use actix_admin_macros::DeriveActixAdminModel; pub use actix_admin_macros::DeriveActixAdminModel;
@ -48,19 +48,27 @@ pub trait AppDataTrait {
// ActixAdminModel // ActixAdminModel
#[async_trait] #[async_trait]
pub trait ActixAdminModelTrait { pub trait ActixAdminModelTrait {
async fn list_model(db: &DatabaseConnection, page: usize, posts_per_page: usize) -> Vec<ActixAdminModel>; async fn list_model(
db: &DatabaseConnection,
page: usize,
posts_per_page: usize,
) -> Vec<ActixAdminModel>;
fn get_fields() -> Vec<(String, ActixAdminField)>; fn get_fields() -> Vec<(String, ActixAdminField)>;
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct ActixAdminModel { pub struct ActixAdminModel {
pub values: HashMap<String, String> pub values: HashMap<String, String>,
} }
// ActixAdminViewModel // ActixAdminViewModel
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait ActixAdminViewModelTrait { pub trait ActixAdminViewModelTrait {
async fn list(db: &DatabaseConnection, page: usize, entities_per_page: usize) -> Vec<ActixAdminModel>; async fn list(
db: &DatabaseConnection,
page: usize,
entities_per_page: usize,
) -> Vec<ActixAdminModel>;
async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> ActixAdminModel; async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> ActixAdminModel;
fn get_entity_name() -> String; fn get_entity_name() -> String;
} }
@ -71,34 +79,64 @@ pub struct ActixAdminViewModel {
pub fields: Vec<(String, ActixAdminField)>, pub fields: Vec<(String, ActixAdminField)>,
} }
// ActixAdminController
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ActixAdmin { pub struct ActixAdmin {
pub entity_names: Vec<String>, pub entity_names: Vec<String>,
pub view_models: HashMap<String, ActixAdminViewModel> pub view_models: HashMap<String, ActixAdminViewModel>,
} }
impl ActixAdmin { pub struct ActixAdminBuilder {
pub fn new() -> Self { pub scopes: Vec<actix_web::Scope>,
let actix_admin = ActixAdmin { pub actix_admin: ActixAdmin,
entity_names: Vec::new(), }
view_models: HashMap::new()
}; pub trait ActixAdminBuilderTrait {
actix_admin fn new() -> Self;
fn add_entity<T: AppDataTrait + 'static, E: ActixAdminViewModelTrait + 'static>(&mut self, view_model: &ActixAdminViewModel);
fn get_scope<T: AppDataTrait + 'static>(self) -> actix_web::Scope;
fn get_actix_admin(&self) -> ActixAdmin;
}
impl ActixAdminBuilderTrait for ActixAdminBuilder {
fn new() -> Self {
ActixAdminBuilder {
actix_admin: ActixAdmin {
entity_names: Vec::new(),
view_models: HashMap::new(),
},
scopes: Vec::new(),
}
} }
pub fn add_entity<E: ActixAdminViewModelTrait>(mut self, view_model: &ActixAdminViewModel) -> Self { fn add_entity<T: AppDataTrait + 'static, E: ActixAdminViewModelTrait + 'static>(
self.entity_names.push(E::get_entity_name()); &mut self,
let view_model_cloned = view_model.clone(); view_model: &ActixAdminViewModel,
let key = view_model.entity_name.to_string(); ) {
self.view_models.insert(key, view_model_cloned); self.scopes.push(
self web::scope(&format!("/{}", E::get_entity_name()))
.route("/list", web::get().to(self::list::<T, E>))
.route("/create", web::get().to(self::create_get::<T, E>))
.route("/create", web::post().to(self::create_post::<T, E>)),
);
self.actix_admin.entity_names.push(E::get_entity_name());
//let view_model_cloned = view_model.clone();
let key = E::get_entity_name();
self.actix_admin.view_models.insert(key, view_model.clone());
} }
pub fn create_scope<T: AppDataTrait + 'static >(&self) -> actix_web::Scope { fn get_scope<T: AppDataTrait + 'static>(self) -> actix_web::Scope {
let scope = web::scope("/admin").route("/", web::get().to(index::<T>)); let mut scope = web::scope("/admin").route("/", web::get().to(index::<T>));
for entity_scope in self.scopes {
scope = scope.service(entity_scope);
}
scope scope
} }
fn get_actix_admin(&self) -> ActixAdmin {
self.actix_admin.clone()
}
} }
impl From<String> for ActixAdminModel { impl From<String> for ActixAdminModel {
@ -107,13 +145,13 @@ impl From<String> for ActixAdminModel {
let key_values: Vec<&str> = string.split('&').collect(); let key_values: Vec<&str> = string.split('&').collect();
for key_value in key_values { for key_value in key_values {
let mut iter = key_value.splitn(2, '='); let mut iter = key_value.splitn(2, '=');
hashmap.insert(iter.next().unwrap().to_string(), iter.next().unwrap().to_string()); hashmap.insert(
iter.next().unwrap().to_string(),
iter.next().unwrap().to_string(),
);
} }
ActixAdminModel { ActixAdminModel { values: hashmap }
// TODO: create dynamically
values: hashmap
}
} }
} }
@ -123,7 +161,7 @@ impl ActixAdminModel {
let value = self.values.get(key).unwrap().to_string().parse::<T>(); let value = self.values.get(key).unwrap().to_string().parse::<T>();
match value { match value {
Ok(val) => Some(val), Ok(val) => Some(val),
Err(_) => None //panic!("key {} could not be parsed", key) Err(_) => None, //panic!("key {} could not be parsed", key)
} }
} }
} }
@ -139,7 +177,10 @@ pub async fn index<T: AppDataTrait>(data: web::Data<T>) -> Result<HttpResponse,
Ok(HttpResponse::Ok().content_type("text/html").body(body)) Ok(HttpResponse::Ok().content_type("text/html").body(body))
} }
pub async fn list<T: AppDataTrait, E: ActixAdminViewModelTrait>(req: HttpRequest, data: web::Data<T>) -> Result<HttpResponse, Error> { pub async fn list<T: AppDataTrait, E: ActixAdminViewModelTrait>(
req: HttpRequest,
data: web::Data<T>,
) -> Result<HttpResponse, Error> {
let entity_name = E::get_entity_name(); let entity_name = E::get_entity_name();
let actix_admin = data.get_actix_admin(); let actix_admin = data.get_actix_admin();
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap(); let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
@ -148,7 +189,9 @@ pub async fn list<T: AppDataTrait, E: ActixAdminViewModelTrait>(req: HttpRequest
let params = web::Query::<Params>::from_query(req.query_string()).unwrap(); let params = web::Query::<Params>::from_query(req.query_string()).unwrap();
let page = params.page.unwrap_or(1); let page = params.page.unwrap_or(1);
let entities_per_page = params.entities_per_page.unwrap_or(DEFAULT_ENTITIES_PER_PAGE); let entities_per_page = params
.entities_per_page
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
let db = data.get_db(); let db = data.get_db();
let entities: Vec<ActixAdminModel> = E::list(db, page, entities_per_page).await; let entities: Vec<ActixAdminModel> = E::list(db, page, entities_per_page).await;
@ -167,7 +210,12 @@ pub async fn list<T: AppDataTrait, E: ActixAdminViewModelTrait>(req: HttpRequest
Ok(HttpResponse::Ok().content_type("text/html").body(body)) Ok(HttpResponse::Ok().content_type("text/html").body(body))
} }
pub async fn create_get<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: HttpRequest, data: web::Data<T>, _body: web::Payload, _text: String) -> Result<HttpResponse, Error> { pub async fn create_get<T: AppDataTrait, E: ActixAdminViewModelTrait>(
_req: HttpRequest,
data: web::Data<T>,
_body: web::Payload,
_text: String,
) -> Result<HttpResponse, Error> {
let _db = &data.get_db(); let _db = &data.get_db();
let entity_name = E::get_entity_name(); let entity_name = E::get_entity_name();
let entity_names = &data.get_actix_admin().entity_names; let entity_names = &data.get_actix_admin().entity_names;
@ -187,16 +235,22 @@ pub async fn create_get<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: Http
Ok(HttpResponse::Ok().content_type("text/html").body(body)) Ok(HttpResponse::Ok().content_type("text/html").body(body))
} }
pub async fn create_post<T: AppDataTrait, E: ActixAdminViewModelTrait>(_req: HttpRequest, data: web::Data<T>, text: String) -> Result<HttpResponse, Error> { pub async fn create_post<T: AppDataTrait, E: ActixAdminViewModelTrait>(
_req: HttpRequest,
data: web::Data<T>,
text: String,
) -> Result<HttpResponse, Error> {
let db = &data.get_db(); let db = &data.get_db();
let entity_name = E::get_entity_name(); let entity_name = E::get_entity_name();
let actix_admin = data.get_actix_admin(); let actix_admin = data.get_actix_admin();
let view_model = actix_admin.view_models.get(&entity_name).unwrap(); let view_model = actix_admin.view_models.get(&entity_name).unwrap();
let mut admin_model = ActixAdminModel::from(text); let mut admin_model = ActixAdminModel::from(text);
admin_model = E::create_entity(db, admin_model).await; admin_model = E::create_entity(db, admin_model).await;
Ok(HttpResponse::Found() Ok(HttpResponse::Found()
.append_header((header::LOCATION, format!("/admin/{}/list", view_model.entity_name))) .append_header((
header::LOCATION,
format!("/admin/{}/list", view_model.entity_name),
))
.finish()) .finish())
} }

Binary file not shown.

View File

@ -1,7 +1,7 @@
extern crate serde_derive; extern crate serde_derive;
use actix_admin::{ use actix_admin::{
ActixAdmin, ActixAdminViewModel, ActixAdmin, ActixAdminBuilder, ActixAdminBuilderTrait, ActixAdminViewModel,
AppDataTrait as ActixAdminAppDataTrait, AppDataTrait as ActixAdminAppDataTrait,
}; };
use actix_session::{CookieSession, Session}; use actix_session::{CookieSession, Session};
@ -55,24 +55,15 @@ async fn index(session: Session, data: web::Data<AppState>) -> HttpResponse {
HttpResponse::Ok().body(rendered) HttpResponse::Ok().body(rendered)
} }
// TODO: Generate this with a Macro accepting Tuples of (Entity, viewmodel) fn create_actix_admin_builder() -> ActixAdminBuilder {
fn setup_actix_admin( let post_view_model = ActixAdminViewModel::from(Post);
actix_admin: &ActixAdmin, let comment_view_model = ActixAdminViewModel::from(Comment);
) -> actix_web::Scope {
actix_admin let mut admin_builder = ActixAdminBuilder::new();
.create_scope::<AppState>() admin_builder.add_entity::<AppState, Post>(&post_view_model);
.service( admin_builder.add_entity::<AppState, Comment>(&comment_view_model);
web::scope("/post")
.route("/list", web::get().to(actix_admin::list::<AppState, Post>)) admin_builder
.route("/create", web::get().to(actix_admin::create_get::<AppState, Post>))
.route("/create", web::post().to(actix_admin::create_post::<AppState, Post>))
)
.service(
web::scope("/comment")
.route("/list", web::get().to(actix_admin::list::<AppState, Comment>))
.route("/create", web::get().to(actix_admin::create_get::<AppState, Comment>))
.route("/create", web::post().to(actix_admin::create_post::<AppState, Comment>))
)
} }
#[actix_rt::main] #[actix_rt::main]
@ -109,17 +100,13 @@ async fn main() {
let conn = sea_orm::Database::connect(opt).await.unwrap(); let conn = sea_orm::Database::connect(opt).await.unwrap();
let _ = entity::create_post_table(&conn).await; let _ = entity::create_post_table(&conn).await;
let post_view_model = ActixAdminViewModel::from(Post); let actix_admin = create_actix_admin_builder().get_actix_admin();
let comment_view_model = ActixAdminViewModel::from(Comment);
let actix_admin = ActixAdmin::new()
.add_entity::<Post>(&post_view_model)
.add_entity::<Comment>(&comment_view_model)
;
let app_state = AppState { let app_state = AppState {
oauth: client, oauth: client,
tmpl: tera, tmpl: tera,
db: conn, db: conn,
actix_admin: actix_admin.clone(), actix_admin: actix_admin,
}; };
HttpServer::new(move || { HttpServer::new(move || {
@ -128,9 +115,9 @@ async fn main() {
.wrap(CookieSession::signed(&[0; 32]).secure(false)) .wrap(CookieSession::signed(&[0; 32]).secure(false))
.route("/", web::get().to(index)) .route("/", web::get().to(index))
.service(azure_auth.clone().create_scope::<AppState>()) .service(azure_auth.clone().create_scope::<AppState>())
.service(setup_actix_admin( .service(
&actix_admin create_actix_admin_builder().get_scope::<AppState>()
)) )
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
}) })
.bind("127.0.0.1:5000") .bind("127.0.0.1:5000")