add crud update macro

master
veypi 1 year ago
parent f1289bb18a
commit 24937738db

@ -2,15 +2,12 @@
name = "oab" name = "oab"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
default-run = "web"
[[bin]] # default-run = "web"
name = "web" # [[bin]]
path = "src/main.rs" # 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS `app`
`_key` varchar(32) NOT NULL, `_key` varchar(32) NOT NULL,
`name` varchar(255) NOT NULL, `name` varchar(255) NOT NULL,
`icon` varchar(255), `icon` varchar(255),
`des` varchar(255), `des` MEDIUMTEXT,
`user_count` int NOT NULL DEFAULT 0, `user_count` int NOT NULL DEFAULT 0,
`hide` tinyint(1) NOT NULL DEFAULT 0, `hide` tinyint(1) NOT NULL DEFAULT 0,
`join_method` int NOT NULL DEFAULT 0, `join_method` int NOT NULL DEFAULT 0,

@ -0,0 +1,120 @@
//
// curd.rs
// Copyright (C) 2023 veypi <i@veypi.com>
// 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<Self> {
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<syn::Ident>,
}
impl Crud {
fn new(args: AttributeArgs) -> syn::Result<Self> {
let mut model: Option<syn::Ident> = None;
let mut attrs: Vec<syn::Ident> = 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",
)),
}
}
}

@ -9,7 +9,9 @@ use proc_macro::TokenStream;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{parse_macro_input, AttributeArgs, ItemFn}; use syn::{parse_macro_input, AttributeArgs, ItemFn};
mod access; mod access;
mod curd;
use access::AccessWrap; use access::AccessWrap;
use curd::CrudWrap;
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn have_access(args: TokenStream, input: TokenStream) -> TokenStream { 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)] #[proc_macro_derive(MyDisplay)]
#[doc(hidden)] #[doc(hidden)]
pub fn display(input: TokenStream) -> TokenStream { pub fn display(input: TokenStream) -> TokenStream {

@ -4,3 +4,102 @@
// 2022-07-09 03:10 // 2022-07-09 03:10
// Distributed under terms of the Apache license. // 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<String>,
pub icon: Option<String>,
pub des: Option<String>,
pub join_method: Option<i32>,
pub role_id: Option<String>,
pub redirect: Option<String>,
pub status: Option<i32>,
}
impl UpdateOpt {
#[crud_update(app, name, icon, des, join_method, role_id, redirect, status)]
pub async fn update(
id: web::Path<String>,
stat: web::Data<AppState>,
data: web::Json<UpdateOpt>,
) -> Result<impl Responder> {
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<String>,
stat: web::Data<AppState>,
data: web::Json<UpdateOpt>,
) -> Result<impl Responder> {
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))
}
}
}

@ -5,18 +5,17 @@
// Distributed under terms of the Apache license. // Distributed under terms of the Apache license.
// //
// //
use actix_web::{delete, get, patch, post, put, web, Responder}; use actix_web::{delete, get, patch, post, web, Responder};
use proc::{access_create, access_delete, access_read, access_update}; use proc::{access_create, access_delete, access_read, access_update, crud_update};
use sea_orm::{ActiveModelTrait, EntityTrait, TransactionTrait}; use sea_orm::{ActiveModelTrait, EntityTrait, TransactionTrait};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::info; use tracing::info;
use crate::{ use crate::{
libs, libs,
models::{self, access, app, app_user, rand_str, AUStatus, AccessLevel, Token}, models::{access, app, rand_str, AccessLevel, Token},
AppState, Error, Result, AppState, Error, Result,
}; };
use chrono::NaiveDateTime;
#[get("/app/{id}")] #[get("/app/{id}")]
#[access_read("app")] #[access_read("app")]
@ -30,26 +29,6 @@ pub async fn get(id: web::Path<String>, stat: web::Data<AppState>) -> Result<imp
} }
} }
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
pub struct App {
pub id: String,
pub created: Option<NaiveDateTime>,
pub updated: Option<NaiveDateTime>,
pub name: Option<String>,
pub des: Option<String>,
pub icon: Option<String>,
pub user_count: i64,
pub hide: bool,
pub join_method: models::AppJoin,
pub role_id: Option<String>,
pub redirect: Option<String>,
pub status: i64,
pub u_status: i64,
}
#[get("/app/")] #[get("/app/")]
#[access_read("app")] #[access_read("app")]
pub async fn list(stat: web::Data<AppState>) -> Result<impl Responder> { pub async fn list(stat: web::Data<AppState>) -> Result<impl Responder> {
@ -101,36 +80,26 @@ pub async fn create(
Ok(web::Json(obj)) Ok(web::Json(obj))
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateOpt { pub struct UpdateOpt {
name: Option<String>, pub name: Option<String>,
icon: Option<String>, pub icon: Option<String>,
enable_register: Option<String>, pub des: Option<String>,
des: Option<String>, pub join_method: Option<i32>,
host: Option<String>, pub role_id: Option<String>,
redirect: Option<String>, pub redirect: Option<String>,
pub status: Option<i32>,
} }
#[patch("/app/{id}")] #[patch("/app/{id}")]
#[access_update("app")] #[access_update("app")]
#[crud_update(app, name, icon, des, join_method, role_id, redirect, status)]
pub async fn update( pub async fn update(
id: web::Path<String>, id: web::Path<String>,
stat: web::Data<AppState>, stat: web::Data<AppState>,
data: web::Json<UpdateOpt>, data: web::Json<UpdateOpt>,
) -> Result<impl Responder> { ) -> Result<impl Responder> {
let data = data.into_inner(); Ok("")
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))
} }
#[delete("/app/{id}")] #[delete("/app/{id}")]

@ -25,7 +25,9 @@ pub fn routes(cfg: &mut web::ServiceConfig) {
cfg.service(app::get) cfg.service(app::get)
.service(app::list) .service(app::list)
.service(app::create) .service(app::create)
.service(app::update)
.service(app::del); .service(app::del);
// cfg.route("/acc", web::get().to(access::UpdateOpt::update));
cfg.service(appuser::get).service(appuser::add); cfg.service(appuser::get).service(appuser::add);
} }

@ -9,7 +9,7 @@ use std::fmt::Debug;
use crate::{ use crate::{
libs, libs,
models::{self, access, app, app_user, user, user_role, AUStatus, UserPlugin}, models::{self, app_user, user, AUStatus, UserPlugin},
AppState, Error, Result, AppState, Error, Result,
}; };
use actix_web::{delete, get, head, http, post, web, HttpResponse, Responder}; use actix_web::{delete, get, head, http, post, web, HttpResponse, Responder};
@ -17,7 +17,7 @@ use base64;
use proc::access_read; use proc::access_read;
use rand::Rng; use rand::Rng;
use sea_orm::{ use sea_orm::{
ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseConnection, DatabaseTransaction, ActiveModelTrait, ColumnTrait,
EntityTrait, QueryFilter, TransactionTrait, EntityTrait, QueryFilter, TransactionTrait,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

@ -1,5 +1,5 @@
// //
// cors.rs // Cors.rs
// Copyright (C) 2023 veypi <i@veypi.com> // Copyright (C) 2023 veypi <i@veypi.com>
// 2023-10-06 15:02 // 2023-10-06 15:02
// Distributed under terms of the MIT license. // Distributed under terms of the MIT license.
@ -17,12 +17,12 @@ use futures_util::future::LocalBoxFuture;
// 1. Middleware initialization, middleware factory gets called with // 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter. // next service in chain as parameter.
// 2. Middleware's call method gets called with normal request. // 2. Middleware's call method gets called with normal request.
pub struct cors; pub struct Cors;
// Middleware factory is `Transform` trait // Middleware factory is `Transform` trait
// `S` - type of the next service // `S` - type of the next service
// `B` - type of response's body // `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for cors impl<S, B> Transform<S, ServiceRequest> for Cors
where where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,
@ -31,19 +31,19 @@ where
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = corsMiddleware<S>; type Transform = CorsMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(corsMiddleware { service })) ready(Ok(CorsMiddleware { service }))
} }
} }
pub struct corsMiddleware<S> { pub struct CorsMiddleware<S> {
service: S, service: S,
} }
impl<S, B> Service<ServiceRequest> for corsMiddleware<S> impl<S, B> Service<ServiceRequest> for CorsMiddleware<S>
where where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,

@ -86,7 +86,7 @@ pub async fn dav_handler(
warn!("handle file failed: {}", e); warn!("handle file failed: {}", e);
if is_request_preflight(&req) { if is_request_preflight(&req) {
let origin = match req.request.headers().get("Origin") { let origin = match req.request.headers().get("Origin") {
Some(o) => o.to_str().unwrap().clone(), Some(o) => o.to_str().unwrap(),
None => "", None => "",
}; };
Response::builder() Response::builder()

@ -5,8 +5,8 @@
// Distributed under terms of the Apache license. // Distributed under terms of the Apache license.
// //
pub mod app;
pub mod auth; pub mod auth;
pub mod cors; pub mod cors;
pub mod fs; pub mod fs;
pub mod user; pub mod user;
pub mod app;

@ -1,10 +1,10 @@
use actix_cors::Cors;
// //
// main.rs // main.rs
// Copyright (C) 2022 veypi <i@veypi.com> // Copyright (C) 2022 veypi <i@veypi.com>
// 2022-07-07 23:51 // 2022-07-07 23:51
// Distributed under terms of the Apache license. // Distributed under terms of the Apache license.
// //
use actix_files as fs; use actix_files as fs;
use actix_web::{ use actix_web::{
dev::{self, Service}, dev::{self, Service},

@ -1,8 +0,0 @@
//
// test.rs
// Copyright (C) 2023 veypi <i@veypi.com>
// 2023-10-06 22:45
// Distributed under terms of the MIT license.
//
fn main() {}
Loading…
Cancel
Save