From 7c8cf72668b91b99717479839b235fb0b7b639d4 Mon Sep 17 00:00:00 2001 From: Manuel Gugger Date: Wed, 25 May 2022 13:11:54 +0200 Subject: [PATCH] implement macros for active_model generation --- Cargo.toml | 4 +- actix_admin/Cargo.toml | 2 +- actix_admin/actix_admin_macros/Cargo.toml | 2 +- actix_admin/actix_admin_macros/src/lib.rs | 58 ++++++++++++++---- .../actix_admin_macros/src/struct_fields.rs | 9 +-- actix_admin/src/lib.rs | 37 ++++++----- database.db-wal | Bin 28872 -> 20632 bytes src/entity/comment.rs | 4 +- src/entity/mod.rs | 28 ++++++--- src/main.rs | 17 +++-- 10 files changed, 105 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f716a43..d7174ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ serde = "1.0.136" serde_json = "1.0.79" serde_derive = "1.0.136" quote = "1.0" -sea-orm = { version = "0.6.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false } +sea-orm = { version = "0.8.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false } syn = "1.0.91" actix_admin = { path = "actix_admin" } -azure_auth = { path = "azure_auth" } \ No newline at end of file +azure_auth = { path = "azure_auth" } diff --git a/actix_admin/Cargo.toml b/actix_admin/Cargo.toml index f051e25..ac31fe6 100644 --- a/actix_admin/Cargo.toml +++ b/actix_admin/Cargo.toml @@ -21,4 +21,4 @@ futures = "0.3.21" serde = "1.0.136" serde_json = "1.0.79" serde_derive = "1.0.136" -sea-orm = { version = "0.6.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false } \ No newline at end of file +sea-orm = { version = "0.8.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false } \ No newline at end of file diff --git a/actix_admin/actix_admin_macros/Cargo.toml b/actix_admin/actix_admin_macros/Cargo.toml index d4aaa9d..8ef552e 100644 --- a/actix_admin/actix_admin_macros/Cargo.toml +++ b/actix_admin/actix_admin_macros/Cargo.toml @@ -27,4 +27,4 @@ futures = "0.3.21" serde = "1.0.136" serde_json = "1.0.79" serde_derive = "1.0.136" -sea-orm = { version = "0.6.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false } \ No newline at end of file +sea-orm = { version = "0.8.0", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = false } \ No newline at end of file diff --git a/actix_admin/actix_admin_macros/src/lib.rs b/actix_admin/actix_admin_macros/src/lib.rs index 2ff38f6..02a347d 100644 --- a/actix_admin/actix_admin_macros/src/lib.rs +++ b/actix_admin/actix_admin_macros/src/lib.rs @@ -8,12 +8,37 @@ use struct_fields::get_fields_for_tokenstream; pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let fields = get_fields_for_tokenstream(input); - let names_const_fields_str = fields.iter().map(|(_vis, ident)| { - let ident_name = ident.to_string(); - quote! { - #ident_name - } - }); + let names_const_fields_str = fields + .iter() + .map(|(_vis, ident, _ty)| { + let ident_name = ident.to_string(); + quote! { + #ident_name + } + }) + .collect::>(); + + let fields_for_create_model = fields + .iter() + // TODO: filter id attr based on struct attr or sea_orm primary_key attr + .filter(|(_vis, ident, _ty)| !ident.to_string().eq("id")) + .map(|(_vis, ident, ty)| { + let ident_name = ident.to_string(); + quote! { + #ident: Set(model.get_value::<#ty>(#ident_name).unwrap()) + } + }) + .collect::>(); + + let fields_for_from_model = fields + .iter() + .map(|(_vis, ident, _ty)| { + let ident_name = ident.to_string(); + quote! { + #ident_name => model.#ident.to_string() + } + }) + .collect::>(); let expanded = quote! { use std::convert::From; @@ -25,6 +50,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea use sea_orm::{entity::*, query::*}; use std::collections::HashMap; use sea_orm::EntityTrait; + use quote::quote; impl From for ActixAdminViewModel { fn from(entity: Entity) -> Self { @@ -40,9 +66,10 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea ActixAdminModel { // TODO: create dynamically values: hashmap![ - "title" => model.title, - "text" => model.text, - "id" => model.id.to_string() + #(#fields_for_from_model),* + // "title" => model.title, + // "text" => model.text, + // "id" => model.id.to_string() ] } } @@ -50,13 +77,14 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea impl From for ActiveModel { fn from(model: ActixAdminModel) -> Self { - ActiveModel { - title: Set(model.values.get("title").unwrap().to_string()), - text: Set(model.values.get("text").unwrap().to_string()), + ActiveModel + { + #(#fields_for_create_model),* + , ..Default::default() } } - } + } #[async_trait(?Send)] impl ActixAdminViewModelTrait for Entity { @@ -71,6 +99,10 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea model } + + fn get_entity_name() -> String { + Entity.table_name().to_string() + } } #[async_trait] diff --git a/actix_admin/actix_admin_macros/src/struct_fields.rs b/actix_admin/actix_admin_macros/src/struct_fields.rs index f718c61..83030f2 100644 --- a/actix_admin/actix_admin_macros/src/struct_fields.rs +++ b/actix_admin/actix_admin_macros/src/struct_fields.rs @@ -1,11 +1,11 @@ use proc_macro2::{Span, Ident}; use syn::{ - Attribute, Fields, Meta, NestedMeta, Visibility, DeriveInput + Attribute, Fields, Meta, NestedMeta, Visibility, DeriveInput, Type }; const ATTR_META_SKIP: &'static str = "skip"; -pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<(syn::Visibility, proc_macro2::Ident)> { +pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<(syn::Visibility, proc_macro2::Ident, Type)> { let ast: DeriveInput = syn::parse(input).unwrap(); let (_vis, ty, _generics) = (&ast.vis, &ast.ident, &ast.generics); let _names_struct_ident = Ident::new(&(ty.to_string() + "FieldStaticStr"), Span::call_site()); @@ -32,7 +32,7 @@ pub fn has_skip_attr(attr: &Attribute, path: &'static str) -> bool { false } -pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> { +pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { fields .iter() .filter_map(|field| { @@ -45,7 +45,8 @@ pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> { { let field_vis = field.vis.clone(); let field_ident = field.ident.as_ref().unwrap().clone(); - Some((field_vis, field_ident)) + let field_ty = field.ty.to_owned(); + Some((field_vis, field_ident, field_ty)) } else { None } diff --git a/actix_admin/src/lib.rs b/actix_admin/src/lib.rs index 8883c7a..5af561b 100644 --- a/actix_admin/src/lib.rs +++ b/actix_admin/src/lib.rs @@ -20,14 +20,6 @@ macro_rules! hashmap { }} } -#[macro_export] -macro_rules! make_fields { - ($($element: ident: $ty: ty),*) => { - $($element: $ty),* - } -} - - // globals lazy_static! { static ref TERA: Tera = @@ -70,6 +62,7 @@ pub struct ActixAdminModel { pub trait ActixAdminViewModelTrait { async fn list(db: &DatabaseConnection, page: usize, entities_per_page: usize) -> Vec; async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> ActixAdminModel; + fn get_entity_name() -> String; } #[derive(Clone, Debug, Serialize)] @@ -94,8 +87,8 @@ impl ActixAdmin { actix_admin } - pub fn add_entity(mut self, view_model: &ActixAdminViewModel) -> Self { - self.entity_names.push(view_model.entity_name.to_string()); + pub fn add_entity(mut self, view_model: &ActixAdminViewModel) -> Self { + self.entity_names.push(E::get_entity_name()); let view_model_cloned = view_model.clone(); let key = view_model.entity_name.to_string(); self.view_models.insert(key, view_model_cloned); @@ -124,6 +117,17 @@ impl From for ActixAdminModel { } } +impl ActixAdminModel { + pub fn get_value(&self, key: &str) -> Option { + println!("get value for key {}", key); + let value = self.values.get(key).unwrap().to_string().parse::(); + match value { + Ok(val) => Some(val), + Err(_) => None //panic!("key {} could not be parsed", key) + } + } +} + pub async fn index(data: web::Data) -> Result { let entity_names = &data.get_actix_admin().entity_names; let mut ctx = Context::new(); @@ -135,8 +139,8 @@ pub async fn index(data: web::Data) -> Result(req: HttpRequest, data: web::Data, path: web::Path) -> Result { - let entity_name: String = path.into_inner(); +pub async fn list(req: HttpRequest, data: web::Data) -> Result { + let entity_name = E::get_entity_name(); let actix_admin = data.get_actix_admin(); let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap(); let entity_names = &data.get_actix_admin().entity_names; @@ -163,10 +167,9 @@ pub async fn list(req: HttpRequest Ok(HttpResponse::Ok().content_type("text/html").body(body)) } -pub async fn create_get(_req: HttpRequest, data: web::Data, _body: web::Payload, _text: String, entity_name: web::Path) -> Result { +pub async fn create_get(_req: HttpRequest, data: web::Data, _body: web::Payload, _text: String) -> Result { let _db = &data.get_db(); - let entity_name: String = entity_name.into_inner(); - println!("{}", &entity_name); + let entity_name = E::get_entity_name(); let entity_names = &data.get_actix_admin().entity_names; let actix_admin = data.get_actix_admin(); @@ -184,9 +187,9 @@ pub async fn create_get(_req: Http Ok(HttpResponse::Ok().content_type("text/html").body(body)) } -pub async fn create_post(_req: HttpRequest, data: web::Data, text: String, entity_name: web::Path) -> Result { +pub async fn create_post(_req: HttpRequest, data: web::Data, text: String) -> Result { let db = &data.get_db(); - let entity_name: String = entity_name.into_inner(); + let entity_name = E::get_entity_name(); let actix_admin = data.get_actix_admin(); let view_model = actix_admin.view_models.get(&entity_name).unwrap(); diff --git a/database.db-wal b/database.db-wal index 06043283e4252b93b58a2690f9d3e4d685e20725..6c56214d98e6659fd79ab59cbcf33f14ca7fefaa 100644 GIT binary patch delta 336 zcmX@{ka5OBMho+Lwk8JMM-mJS0t_JV)%?x+`vy|qS}t327+rPl0SYl9i&vVSx0vv< ze91-&4gmp};>ro>MlbGvGu|8^fF$ZtuD2$|^K2bZlv%(L#S{&`+hGr!zr_Uy`ec@* zDx~EXj;s5~w04ncn ASpWb4 delta 337 zcmbQSknzMrMho+Lwk8JMM-mJS0t_JF*RQsICAY%*7T!sTuSHAO1BDn-#CKYSvNqng zp4?y{y)pPLzW_|b_oeyC(j}kuHU|iB2*5=@IsR$9;UU8d6lE5WXF@h9?QwqkY4&r0 zydW$1KQZus+MFoxg5Q9Lk%d82v?R5-1cZT-%peT2rgq=-IL+&KzCm<-XW;)1)b*BM zmz|MWl(QfotO3QESSH^xPxhs!KpL3%9|Nu7e+<-eo!_39iG@L2G$S!5C%-&3rv$&f gZmO?WTVDT_1F`oT1OGRmA#eD(IDqytg6w4i010(wXaE2J diff --git a/src/entity/comment.rs b/src/entity/comment.rs index 58d94c7..9ab70bf 100644 --- a/src/entity/comment.rs +++ b/src/entity/comment.rs @@ -1,9 +1,9 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -//use actix_admin::{ DeriveActixAdminModel }; +use actix_admin::{ DeriveActixAdminModel }; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize/*, DeriveActixAdminModel*/)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdminModel)] #[sea_orm(table_name = "comment")] pub struct Model { #[sea_orm(primary_key)] diff --git a/src/entity/mod.rs b/src/entity/mod.rs index ed5767a..d999ecb 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -1,10 +1,10 @@ // setup use sea_orm::sea_query::{ColumnDef, TableCreateStatement}; use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult}; -pub mod post; pub mod comment; -pub use post::Entity as Post; +pub mod post; pub use comment::Entity as Comment; +pub use post::Entity as Post; // setup async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { @@ -23,16 +23,24 @@ pub async fn create_post_table(db: &DbConn) -> Result { .auto_increment() .primary_key(), ) + .col(ColumnDef::new(post::Column::Title).string().not_null()) + .col(ColumnDef::new(post::Column::Text).string().not_null()) + .to_owned(); + + create_table(db, &stmt).await; + + let stmt = sea_query::Table::create() + .table(comment::Entity) + .if_not_exists() .col( - ColumnDef::new(post::Column::Title) - .string() - .not_null(), - ) - .col( - ColumnDef::new(post::Column::Text) - .string() - .not_null(), + ColumnDef::new(post::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), ) + .col(ColumnDef::new(comment::Column::Comment).string().not_null()) + .col(ColumnDef::new(comment::Column::User).string().not_null()) .to_owned(); create_table(db, &stmt).await diff --git a/src/main.rs b/src/main.rs index ef37717..8d1ed26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ extern crate serde_derive; use actix_admin::{ ActixAdmin, ActixAdminViewModel, AppDataTrait as ActixAdminAppDataTrait, - ActixAdminViewModelTrait }; use actix_session::{CookieSession, Session}; use actix_web::{web, App, HttpResponse, HttpServer, middleware}; @@ -17,7 +16,7 @@ use std::time::Duration; use tera::{Context, Tera}; mod entity; -use entity::{Post}; +use entity::{Post, Comment}; #[derive(Debug, Clone)] pub struct AppState { @@ -63,11 +62,17 @@ fn setup_actix_admin( actix_admin .create_scope::() .service( - web::scope("/{entity_name}") + web::scope("/post") .route("/list", web::get().to(actix_admin::list::)) .route("/create", web::get().to(actix_admin::create_get::)) .route("/create", web::post().to(actix_admin::create_post::)) ) + .service( + web::scope("/comment") + .route("/list", web::get().to(actix_admin::list::)) + .route("/create", web::get().to(actix_admin::create_get::)) + .route("/create", web::post().to(actix_admin::create_post::)) + ) } #[actix_rt::main] @@ -105,10 +110,10 @@ async fn main() { let _ = entity::create_post_table(&conn).await; let post_view_model = ActixAdminViewModel::from(Post); - //let comment_view_model = ActixAdminViewModel::from(Comment); + let comment_view_model = ActixAdminViewModel::from(Comment); let actix_admin = ActixAdmin::new() - .add_entity(&post_view_model) - //.add_entity::(&comment_view_model) + .add_entity::(&post_view_model) + .add_entity::(&comment_view_model) ; let app_state = AppState { oauth: client,