From 24937738db86fda8d4b5be6d0f6219390369af09 Mon Sep 17 00:00:00 2001 From: veypi Date: Sat, 7 Oct 2023 21:49:14 +0800 Subject: [PATCH] add crud update macro --- oab/Cargo.toml | 11 +-- oab/migrations/20220720220617_base.sql | 2 +- oab/proc/src/curd.rs | 120 +++++++++++++++++++++++++ oab/proc/src/lib.rs | 17 ++++ oab/src/api/access.rs | 99 ++++++++++++++++++++ oab/src/api/app.rs | 57 +++--------- oab/src/api/mod.rs | 2 + oab/src/api/user.rs | 4 +- oab/src/libs/cors.rs | 14 +-- oab/src/libs/fs.rs | 2 +- oab/src/libs/mod.rs | 2 +- oab/src/main.rs | 2 +- oab/src/test.rs | 8 -- 13 files changed, 268 insertions(+), 72 deletions(-) delete mode 100644 oab/src/test.rs diff --git a/oab/Cargo.toml b/oab/Cargo.toml index 6bacd82..775ce77 100644 --- a/oab/Cargo.toml +++ b/oab/Cargo.toml @@ -2,15 +2,12 @@ name = "oab" version = "0.1.0" edition = "2021" -default-run = "web" -[[bin]] -name = "web" -path = "src/main.rs" +# default-run = "web" +# [[bin]] +# name = "web" +# path = "src/main.rs" -[[bin]] -name = "test" -path = "src/test.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/oab/migrations/20220720220617_base.sql b/oab/migrations/20220720220617_base.sql index 7f36cf8..fe4f228 100644 --- a/oab/migrations/20220720220617_base.sql +++ b/oab/migrations/20220720220617_base.sql @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS `app` `_key` varchar(32) NOT NULL, `name` varchar(255) NOT NULL, `icon` varchar(255), - `des` varchar(255), + `des` MEDIUMTEXT, `user_count` int NOT NULL DEFAULT 0, `hide` tinyint(1) NOT NULL DEFAULT 0, `join_method` int NOT NULL DEFAULT 0, diff --git a/oab/proc/src/curd.rs b/oab/proc/src/curd.rs index e69de29..8772b8f 100644 --- a/oab/proc/src/curd.rs +++ b/oab/proc/src/curd.rs @@ -0,0 +1,120 @@ +// +// curd.rs +// Copyright (C) 2023 veypi +// 2023-10-06 23:47 +// Distributed under terms of the MIT license. +// + +use proc_macro2::{Ident, Span}; +use quote::{quote, ToTokens}; +use syn::{AttributeArgs, ItemFn, NestedMeta, ReturnType}; + +pub struct CrudWrap { + func: ItemFn, + args: Crud, + method: i32, +} + +impl CrudWrap { + pub fn new(args: AttributeArgs, func: ItemFn, method: i32) -> syn::Result { + let args = Crud::new(args)?; + + Ok(Self { func, args, method }) + } +} + +impl ToTokens for CrudWrap { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let func_vis = &self.func.vis; + let func_block = &self.func.block; + + let fn_sig = &self.func.sig; + let fn_attrs = &self.func.attrs; + let fn_name = &fn_sig.ident; + let fn_generics = &fn_sig.generics; + let fn_args = &fn_sig.inputs; + let fn_async = &fn_sig.asyncness.unwrap(); + let fn_output = match &fn_sig.output { + ReturnType::Type(ref _arrow, ref ty) => ty.to_token_stream(), + ReturnType::Default => { + quote! {()} + } + }; + let model_name = &self.args.model; + + let builder_fields = self.args.attrs.iter().map(|field| { + quote! { + if let Some(#field) = _data.#field { + obj.#field = sea_orm::Set(#field.into()) + }; + } + }); + + let stream = quote! { + #(#fn_attrs)* + #func_vis #fn_async fn #fn_name #fn_generics( + #fn_args + ) -> #fn_output { + let _id = &id.clone(); + let _data = data.clone(); + let _db = &stat.db().clone(); + let f = || async move #func_block; + let res = f().await; + match res { + Err(e) => Err(e), + Ok(res) => { + let obj = crate::models::#model_name::Entity::find_by_id(_id).one(_db).await?; + let mut obj: crate::models::#model_name::ActiveModel = match obj { + Some(o) => o.into(), + None => return Err(Error::NotFound(_id.to_owned())), + }; + #(#builder_fields)* + let obj = obj.update(_db).await?; + Ok(actix_web::web::Json(obj)) + } + } + } + }; + + let _stream = tokens.extend(stream); + } +} + +struct Crud { + model: syn::Ident, + attrs: Vec, +} + +impl Crud { + fn new(args: AttributeArgs) -> syn::Result { + let mut model: Option = None; + let mut attrs: Vec = Vec::new(); + for arg in args { + match arg { + // NestedMeta::Lit(syn::Lit::Str(lit)) => { + // } + NestedMeta::Meta(syn::Meta::Path(i)) => match i.get_ident() { + Some(i) => { + if None == model { + model = Some(i.to_owned()); + } else { + attrs.push(i.to_owned()); + } + } + None => {} + }, + + _ => { + return Err(syn::Error::new_spanned(arg, "Unknown attribute.")); + } + } + } + match model { + Some(model) => Ok(Self { model, attrs }), + None => Err(syn::Error::new( + Span::call_site(), + "The #[crud(..)] macro requires one `model` name", + )), + } + } +} diff --git a/oab/proc/src/lib.rs b/oab/proc/src/lib.rs index a3d8da5..b15ee27 100644 --- a/oab/proc/src/lib.rs +++ b/oab/proc/src/lib.rs @@ -9,7 +9,9 @@ use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::{parse_macro_input, AttributeArgs, ItemFn}; mod access; +mod curd; use access::AccessWrap; +use curd::CrudWrap; #[proc_macro_attribute] pub fn have_access(args: TokenStream, input: TokenStream) -> TokenStream { @@ -46,6 +48,21 @@ fn check_permissions(cb_fn: Option<&str>, args: TokenStream, input: TokenStream) } } +#[proc_macro_attribute] +pub fn crud_update(args: TokenStream, input: TokenStream) -> TokenStream { + derive_crud(3, args, input) +} + +fn derive_crud(method: i32, args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as AttributeArgs); + let func = parse_macro_input!(input as ItemFn); + + match CrudWrap::new(args, func, method) { + Ok(ac) => ac.into_token_stream().into(), + Err(err) => err.to_compile_error().into(), + } +} + #[proc_macro_derive(MyDisplay)] #[doc(hidden)] pub fn display(input: TokenStream) -> TokenStream { diff --git a/oab/src/api/access.rs b/oab/src/api/access.rs index ffedd8f..f271318 100644 --- a/oab/src/api/access.rs +++ b/oab/src/api/access.rs @@ -4,3 +4,102 @@ // 2022-07-09 03:10 // Distributed under terms of the Apache license. // +// +// +use actix_web::{web, Responder}; +use proc::crud_update; +use sea_orm::{ActiveModelTrait, EntityTrait}; +use serde::{Deserialize, Serialize}; + +use crate::{models::app, AppState, Error, Result}; + +// #[derive(Debug, Deserialize, Serialize)] +#[derive(Clone)] +pub struct UpdateOpt { + pub name: Option, + pub icon: Option, + pub des: Option, + pub join_method: Option, + pub role_id: Option, + pub redirect: Option, + pub status: Option, +} +impl UpdateOpt { + #[crud_update(app, name, icon, des, join_method, role_id, redirect, status)] + pub async fn update( + id: web::Path, + stat: web::Data, + data: web::Json, + ) -> Result { + let data = data.into_inner(); + let id = id.into_inner(); + let obj = app::Entity::find_by_id(&id).one(stat.db()).await?; + let mut obj: app::ActiveModel = match obj { + Some(o) => o.into(), + None => return Err(Error::NotFound(id)), + }; + if let Some(name) = data.name { + obj.name = sea_orm::Set(name) + }; + let obj = obj.update(stat.db()).await?; + Ok(web::Json(obj)) + } +} + +pub async fn update( + id: web::Path, + stat: web::Data, + data: web::Json, +) -> Result { + let _id = &id.clone(); + let _data = data.clone(); + let _db = &stat.db().clone(); + let f = || async move { + let data = data.into_inner(); + let id = id.into_inner(); + let obj = app::Entity::find_by_id(&id).one(stat.db()).await?; + let mut obj: app::ActiveModel = match obj { + Some(o) => o.into(), + None => return Err(Error::NotFound(id)), + }; + if let Some(name) = data.name { + obj.name = sea_orm::Set(name) + }; + let obj = obj.update(stat.db()).await?; + Ok(web::Json(obj)) + }; + let res = f().await; + match res { + Err(e) => Err(e), + Ok(res) => { + let obj = crate::models::app::Entity::find_by_id(_id).one(_db).await?; + let mut obj: crate::models::app::ActiveModel = match obj { + Some(o) => o.into(), + None => return Err(Error::NotFound(_id.to_owned())), + }; + if let Some(name) = _data.name { + obj.name = sea_orm::Set(name.into()) + }; + if let Some(icon) = _data.icon { + obj.icon = sea_orm::Set(icon.into()) + }; + if let Some(des) = _data.des { + obj.des = sea_orm::Set(des.into()) + }; + if let Some(join_method) = _data.join_method { + obj.join_method = sea_orm::Set(join_method.into()) + }; + if let Some(role_id) = _data.role_id { + obj.role_id = sea_orm::Set(role_id.into()) + }; + if let Some(redirect) = _data.redirect { + obj.redirect = sea_orm::Set(redirect.into()) + }; + if let Some(status) = _data.status { + obj.status = sea_orm::Set(status.into()) + }; + let obj = obj.update(_db).await?; + Ok(actix_web::web::Json(obj)) + } + } +} diff --git a/oab/src/api/app.rs b/oab/src/api/app.rs index 58f297a..4daa221 100644 --- a/oab/src/api/app.rs +++ b/oab/src/api/app.rs @@ -5,18 +5,17 @@ // Distributed under terms of the Apache license. // // -use actix_web::{delete, get, patch, post, put, web, Responder}; -use proc::{access_create, access_delete, access_read, access_update}; +use actix_web::{delete, get, patch, post, web, Responder}; +use proc::{access_create, access_delete, access_read, access_update, crud_update}; use sea_orm::{ActiveModelTrait, EntityTrait, TransactionTrait}; use serde::{Deserialize, Serialize}; use tracing::info; use crate::{ libs, - models::{self, access, app, app_user, rand_str, AUStatus, AccessLevel, Token}, + models::{access, app, rand_str, AccessLevel, Token}, AppState, Error, Result, }; -use chrono::NaiveDateTime; #[get("/app/{id}")] #[access_read("app")] @@ -30,26 +29,6 @@ pub async fn get(id: web::Path, stat: web::Data) -> Result, - pub updated: Option, - - pub name: Option, - pub des: Option, - pub icon: Option, - pub user_count: i64, - - pub hide: bool, - pub join_method: models::AppJoin, - pub role_id: Option, - pub redirect: Option, - - pub status: i64, - pub u_status: i64, -} - #[get("/app/")] #[access_read("app")] pub async fn list(stat: web::Data) -> Result { @@ -101,36 +80,26 @@ pub async fn create( Ok(web::Json(obj)) } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct UpdateOpt { - name: Option, - icon: Option, - enable_register: Option, - des: Option, - host: Option, - redirect: Option, + pub name: Option, + pub icon: Option, + pub des: Option, + pub join_method: Option, + pub role_id: Option, + pub redirect: Option, + pub status: Option, } #[patch("/app/{id}")] #[access_update("app")] +#[crud_update(app, name, icon, des, join_method, role_id, redirect, status)] pub async fn update( id: web::Path, stat: web::Data, data: web::Json, ) -> Result { - let data = data.into_inner(); - let id = id.into_inner(); - let obj = app::Entity::find_by_id(&id).one(stat.db()).await?; - let mut obj: app::ActiveModel = match obj { - Some(o) => o.into(), - None => return Err(Error::NotFound(id)), - }; - if let Some(name) = data.name { - obj.name = sea_orm::Set(name) - }; - - let obj = obj.update(stat.db()).await?; - Ok(web::Json(obj)) + Ok("") } #[delete("/app/{id}")] diff --git a/oab/src/api/mod.rs b/oab/src/api/mod.rs index 4e4b9c0..5bd3fdc 100644 --- a/oab/src/api/mod.rs +++ b/oab/src/api/mod.rs @@ -25,7 +25,9 @@ pub fn routes(cfg: &mut web::ServiceConfig) { cfg.service(app::get) .service(app::list) .service(app::create) + .service(app::update) .service(app::del); + // cfg.route("/acc", web::get().to(access::UpdateOpt::update)); cfg.service(appuser::get).service(appuser::add); } diff --git a/oab/src/api/user.rs b/oab/src/api/user.rs index 0e29534..7fc52c8 100644 --- a/oab/src/api/user.rs +++ b/oab/src/api/user.rs @@ -9,7 +9,7 @@ use std::fmt::Debug; use crate::{ libs, - models::{self, access, app, app_user, user, user_role, AUStatus, UserPlugin}, + models::{self, app_user, user, AUStatus, UserPlugin}, AppState, Error, Result, }; use actix_web::{delete, get, head, http, post, web, HttpResponse, Responder}; @@ -17,7 +17,7 @@ use base64; use proc::access_read; use rand::Rng; use sea_orm::{ - ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseConnection, DatabaseTransaction, + ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait, }; use serde::{Deserialize, Serialize}; diff --git a/oab/src/libs/cors.rs b/oab/src/libs/cors.rs index 5e1ef24..9fab565 100644 --- a/oab/src/libs/cors.rs +++ b/oab/src/libs/cors.rs @@ -1,5 +1,5 @@ // -// cors.rs +// Cors.rs // Copyright (C) 2023 veypi // 2023-10-06 15:02 // Distributed under terms of the MIT license. @@ -17,12 +17,12 @@ use futures_util::future::LocalBoxFuture; // 1. Middleware initialization, middleware factory gets called with // next service in chain as parameter. // 2. Middleware's call method gets called with normal request. -pub struct cors; +pub struct Cors; // Middleware factory is `Transform` trait // `S` - type of the next service // `B` - type of response's body -impl Transform for cors +impl Transform for Cors where S: Service, Error = Error>, S::Future: 'static, @@ -31,19 +31,19 @@ where type Response = ServiceResponse; type Error = Error; type InitError = (); - type Transform = corsMiddleware; + type Transform = CorsMiddleware; type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ready(Ok(corsMiddleware { service })) + ready(Ok(CorsMiddleware { service })) } } -pub struct corsMiddleware { +pub struct CorsMiddleware { service: S, } -impl Service for corsMiddleware +impl Service for CorsMiddleware where S: Service, Error = Error>, S::Future: 'static, diff --git a/oab/src/libs/fs.rs b/oab/src/libs/fs.rs index cd15fa3..a99d25b 100644 --- a/oab/src/libs/fs.rs +++ b/oab/src/libs/fs.rs @@ -86,7 +86,7 @@ pub async fn dav_handler( warn!("handle file failed: {}", e); if is_request_preflight(&req) { let origin = match req.request.headers().get("Origin") { - Some(o) => o.to_str().unwrap().clone(), + Some(o) => o.to_str().unwrap(), None => "", }; Response::builder() diff --git a/oab/src/libs/mod.rs b/oab/src/libs/mod.rs index 610e15e..0f7b405 100644 --- a/oab/src/libs/mod.rs +++ b/oab/src/libs/mod.rs @@ -5,8 +5,8 @@ // Distributed under terms of the Apache license. // +pub mod app; pub mod auth; pub mod cors; pub mod fs; pub mod user; -pub mod app; diff --git a/oab/src/main.rs b/oab/src/main.rs index cf3da71..969afc5 100644 --- a/oab/src/main.rs +++ b/oab/src/main.rs @@ -1,10 +1,10 @@ -use actix_cors::Cors; // // main.rs // Copyright (C) 2022 veypi // 2022-07-07 23:51 // Distributed under terms of the Apache license. // + use actix_files as fs; use actix_web::{ dev::{self, Service}, diff --git a/oab/src/test.rs b/oab/src/test.rs deleted file mode 100644 index 32f1613..0000000 --- a/oab/src/test.rs +++ /dev/null @@ -1,8 +0,0 @@ -// -// test.rs -// Copyright (C) 2023 veypi -// 2023-10-06 22:45 -// Distributed under terms of the MIT license. -// - -fn main() {}