crud macro / login token redirect

master
veypi 1 year ago
parent 5f752ba9ac
commit 521aee9683

@ -5,9 +5,9 @@
// Distributed under terms of the MIT license.
//
use proc_macro2::{Ident, Span};
use proc_macro2::Span;
use quote::{format_ident, quote, ToTokens};
use syn::{AttributeArgs, ItemFn, NestedMeta, ReturnType, Token};
use syn::{AttributeArgs, ItemFn, NestedMeta, ReturnType};
pub struct CrudWrap {
func: ItemFn,
@ -17,14 +17,11 @@ pub struct CrudWrap {
impl CrudWrap {
pub fn new(args: AttributeArgs, func: ItemFn, method: i32) -> syn::Result<Self> {
let args = Crud::new(args)?;
let args = Crud::new(args, method)?;
Ok(Self { func, args, method })
}
}
impl ToTokens for CrudWrap {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
fn update(&self, tokens: &mut proc_macro2::TokenStream) {
let func_vis = &self.func.vis;
let func_block = &self.func.block;
@ -42,7 +39,7 @@ impl ToTokens for CrudWrap {
};
let model_name = &self.args.model;
let builder_fields = self.args.attrs.iter().map(|field| {
let builder_fields = self.args.props.iter().map(|field| {
quote! {
if let Some(#field) = _data.#field {
obj.#field = sea_orm::Set(#field.into())
@ -51,12 +48,10 @@ impl ToTokens for CrudWrap {
});
let (args_fields, filter_fields) = match self.args.filters.len() {
1 => {
let pair = &self.args.filters[0];
let k = &pair[0];
let _k = format_ident!("_{}", &pair[0]);
let v = &pair[1];
let k = &self.args.filters[0];
let _k = format_ident!("_{}", k);
(
vec![quote! {let #_k = #v; }],
vec![quote! {let #_k = _id; }],
vec![quote! {
filter(crate::models::#model_name::Column::#k.eq(#_k))
}],
@ -67,17 +62,17 @@ impl ToTokens for CrudWrap {
.filters
.iter()
.enumerate()
.map(|(idx, [k, v])| {
.map(|(idx, k)| {
let _k = format_ident!("_{}", k);
quote! {
let #_k = #v.#idx;
let #_k = &_id[#idx];
}
})
.collect(),
self.args
.filters
.iter()
.map(|[k, _]| {
.map(|k| {
let _k = format_ident!("_{}", k);
quote! {
filter(crate::models::#model_name::Column::#k.eq(#_k))
@ -115,19 +110,43 @@ impl ToTokens for CrudWrap {
let _stream = tokens.extend(stream);
}
fn copy(&self, _: &mut proc_macro2::TokenStream) {
let _ = self.args.attrs.len();
}
}
impl ToTokens for CrudWrap {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self.method {
3 => self.update(tokens),
// 3 => self.update(tokens),
_ => self.copy(tokens),
}
}
}
struct Crud {
model: syn::Ident,
attrs: Vec<syn::Ident>,
filters: Vec<[syn::Ident; 2]>,
filters: Vec<syn::Ident>,
props: Vec<syn::Ident>,
}
impl Crud {
fn new(args: AttributeArgs) -> syn::Result<Self> {
fn new(args: AttributeArgs, method: i32) -> syn::Result<Self> {
let mut model: Option<syn::Ident> = None;
let mut attrs: Vec<syn::Ident> = Vec::new();
let mut filters: Vec<[syn::Ident; 2]> = Vec::new();
let mut filters: Vec<syn::Ident> = Vec::new();
let mut props: Vec<syn::Ident> = Vec::new();
if method == 0 {
return Ok(Self {
model: format_ident!("a"),
attrs,
filters,
props,
});
}
for arg in args {
match arg {
// NestedMeta::Lit(syn::Lit::Str(lit)) => {
@ -147,17 +166,32 @@ impl Crud {
lit: syn::Lit::Str(lit_str),
..
})) => {
match path.get_ident() {
Some(expr) => {
filters.push([expr.to_owned(), format_ident!("{}", lit_str.value())])
}
None => {
if path.is_ident("filter") {
filters = lit_str
.value()
.replace(" ", "")
.split(",")
.into_iter()
.map(|l| {
return format_ident!("{}", l);
})
.collect();
} else if path.is_ident("props") {
props = lit_str
.value()
.replace(" ", "")
.split(",")
.into_iter()
.map(|l| {
return format_ident!("{}", l);
})
.collect();
} else {
return Err(syn::Error::new_spanned(
path,
"Unknown identifier. Available: 'aid='AppId'",
"Unknown identifier. Available: filter, props ",
));
}
};
}
_ => {
@ -165,12 +199,12 @@ impl Crud {
}
}
}
println!("|||||||||||____________________");
match model {
Some(model) => Ok(Self {
model,
attrs,
filters,
props,
}),
None => Err(syn::Error::new(
Span::call_site(),

@ -53,6 +53,11 @@ pub fn crud_update(args: TokenStream, input: TokenStream) -> TokenStream {
derive_crud(3, args, input)
}
#[proc_macro_attribute]
pub fn crud_test(args: TokenStream, input: TokenStream) -> TokenStream {
derive_crud(0, 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);

@ -87,9 +87,9 @@ pub struct UpdateOpt {
#[patch("/app/{aid}/access/{id}")]
#[access_delete("app")]
#[crud_update(access, AppId = "_id", Id = "_id", level, rid)]
#[crud_update(access, filter = "AppId, Id", props = "level, rid")]
pub async fn update(
id: web::Path<(String, String)>,
id: web::Path<[String; 2]>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
@ -111,3 +111,34 @@ pub async fn delete(
info!("{:#?}", res);
Ok("ok")
}
// mod test {
// use crate::{
// models::{self},
// AppState, Error, Result,
// };
// use actix_web::{delete, get, patch, post, web, Responder};
// use proc::crud_test;
// use proc::{access_create, access_delete, access_read, crud_update};
// use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter};
// use serde::{Deserialize, Serialize};
// use tracing::info;
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub struct UpdateOpt {
// pub level: Option<i32>,
// pub rid: Option<String>,
// }
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub struct IDOpt {
// pub app_id: Option<String>,
// pub id: Option<String>,
// }
// #[crud_test(access, filter = "AppId, Id", props = "level, rid")]
// pub async fn update(
// id: web::Path<[String; 2]>,
// data: web::Json<UpdateOpt>,
// stat: web::Data<AppState>,
// ) -> Result<impl Responder> {
// Ok("")
// }
// }

@ -96,15 +96,8 @@ pub struct UpdateOpt {
#[access_update("app")]
#[crud_update(
app,
Id = "_id",
name,
icon,
des,
join_method,
role_id,
redirect,
host,
status
filter = "Id",
props = "name,icon,des,join_method,role_id,redirect,host,status"
)]
pub async fn update(
id: web::Path<String>,

@ -102,9 +102,9 @@ pub struct UpdateOpt {
#[patch("/app/{aid}/user/{uid}")]
#[access_delete("app")]
#[crud_update(app_user, AppId = "_id", UserId = "_id", status)]
#[crud_update(app_user, filter = "AppId, UserId", props = "status")]
pub async fn update(
id: web::Path<(String, String)>,
id: web::Path<[String; 2]>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {

@ -11,6 +11,7 @@ mod app;
mod appuser;
mod resource;
mod role;
mod token;
mod upload;
mod user;
use actix_web::web;
@ -29,6 +30,7 @@ pub fn routes(cfg: &mut web::ServiceConfig) {
.service(app::update)
.service(app::del);
// cfg.route("/acc", web::get().to(access::UpdateOpt::update));
cfg.service(token::get);
cfg.service(appuser::get)
.service(appuser::add)

@ -6,9 +6,7 @@
//
use actix_web::{delete, get, patch, post, web, Responder};
use proc::{access_create, access_delete, access_read, crud_update};
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, LoaderTrait, QueryFilter, TransactionTrait,
};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter};
use serde::{Deserialize, Serialize};
use tracing::info;
@ -78,9 +76,9 @@ pub struct UpdateOpt {
#[patch("/app/{aid}/resource/{rid}")]
#[access_delete("app")]
#[crud_update(resource, AppId = "_id", Name = "_id", des)]
#[crud_update(resource, filter = "AppId, Name", des)]
pub async fn update(
id: web::Path<(String, String)>,
id: web::Path<[String; 2]>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {

@ -7,15 +7,11 @@
//
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, ColumnTrait, ConnectionTrait, EntityTrait, LoaderTrait, QueryFilter,
TransactionTrait,
};
use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter};
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::{
libs,
models::{self},
AppState, Error, Result,
};
@ -82,10 +78,10 @@ pub struct UpdateOpt {
}
#[patch("/app/{aid}/role/{rid}")]
#[access_update("app", id = "&id.clone().0")]
#[crud_update(role, AppId = "_id", Id = "_id", des)]
#[access_update("app", id = "&id.clone()[0]")]
#[crud_update(role, filter = "AppId, Id", props = "des")]
pub async fn update(
id: web::Path<(String, String)>,
id: web::Path<[String; 2]>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {

@ -0,0 +1,68 @@
//
// token.rs
// Copyright (C) 2023 veypi <i@veypi.com>
// 2023-10-13 02:29
// Distributed under terms of the MIT license.
//
use actix_web::{get, web, Responder};
use proc::access_read;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use std::time::{Duration, Instant};
use tokio::{self};
use crate::{
models::{self, AUStatus, Token, UserPlugin},
AppState, Error, Result,
};
#[get("/app/{aid}/token/")]
#[access_read("app")]
pub async fn get(
aid: web::Path<String>,
stat: web::Data<AppState>,
t: web::ReqData<Token>,
) -> Result<impl Responder> {
let n = aid.into_inner();
if !n.is_empty() {
let s = models::app_user::Entity::find()
.filter(models::app_user::Column::AppId.eq(&n))
.filter(models::app_user::Column::UserId.eq(&t.id))
.one(stat.db())
.await?;
if s.is_none() {
return Err(Error::NotAuthed);
};
let s = s.unwrap();
if s.status == AUStatus::OK as i32 {
let result = sqlx::query_as::<_, models::AccessCore>(
"select access.name, access.rid, access.level from access, user_role, role WHERE user_role.user_id = ? && access.role_id=user_role.role_id && role.id=user_role.role_id && role.app_id = ?",
)
.bind(&t.id)
.bind(n)
.fetch_all(stat.sqlx())
.await?;
let u = models::user::Entity::find_by_id(&t.id)
.one(stat.db())
.await?
.unwrap();
let str = u.token(result).to_string()?;
// tokio::spawn(async move {
// let mut interval = tokio::time::interval(Duration::from_secs(5));
// interval.tick().await;
// let start = Instant::now();
// println!("time:{:?}", start);
// loop {
// interval.tick().await;
// println!("time:{:?}", start.elapsed());
// }
// });
Ok(str)
} else {
Err(Error::NotAuthed)
}
} else {
Err(Error::Missing("id".to_string()))
}
}

@ -66,47 +66,3 @@ async fn save_files(
Ok(web::Json(res))
}
// #[actix_web::main]
// async fn main() -> std::io::Result<()> {
// HttpServer::new(|| {
// App::new()
// .wrap(middleware::Logger::default())
// .app_data(TempFileConfig::default().directory("./tmp"))
// .service(
// web::resource("/")
// .route(web::get().to(index))
// .route(web::post().to(save_files)),
// )
// })
// .bind(("127.0.0.1", 8080))?
// .workers(2)
// .run()
// .await
// }
// /// Example of the old manual way of processing multipart forms.
// #[allow(unused)]
// async fn save_file_manual(mut payload: Multipart) -> Result<HttpResponse, Error> {
// // iterate over multipart stream
// while let Some(mut field) = payload.try_next().await? {
// // A multipart/form-data stream has to contain `content_disposition`
// let content_disposition = field.content_disposition();
// let filename = content_disposition
// .get_filename()
// .map_or_else(|| Uuid::new_v4().to_string(), sanitize_filename::sanitize);
// let filepath = format!("./tmp/{filename}");
// // File::create is blocking operation, use threadpool
// let mut f = web::block(|| std::fs::File::create(filepath)).await??;
// // Field in turn is stream of *Bytes* object
// while let Some(chunk) = field.try_next().await? {
// // filesystem operations are blocking, we have to use threadpool
// f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
// }
// }
// Ok(HttpResponse::Ok().into())
// }

@ -14,7 +14,7 @@ use crate::{
};
use actix_web::{delete, get, head, http, patch, post, web, HttpResponse, Responder};
use base64;
use chrono::{DateTime, Local, NaiveDateTime};
use chrono::Local;
use proc::{access_read, access_update, crud_update};
use rand::Rng;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
@ -222,7 +222,7 @@ pub struct UpdateOpt {
#[patch("/user/{id}")]
#[access_update("user")]
#[crud_update(user, Id = "_id", username, icon, nickname, email, phone)]
#[crud_update(user, filter = "Id", props = "username, icon, nickname, email, phone")]
pub async fn update(
id: web::Path<String>,
stat: web::Data<AppState>,

@ -9,8 +9,8 @@ mod app_plugin;
pub mod entity;
mod user_plugin;
use chrono::DateTime;
use sea_orm::EntityTrait;
use serde::{Deserialize, Serialize};
use tracing::info;

@ -61,7 +61,7 @@ impl UserPlugin for super::entity::user::Model {
fn token(&self, ac: Vec<AccessCore>) -> Token {
let default_ico = "/media/".to_string();
let t = Token {
iss: "onedt".to_string(),
iss: "oa".to_string(),
aud: "".to_string(),
exp: (Utc::now() + Duration::days(4)).timestamp(),
iat: Utc::now().timestamp(),

@ -7,7 +7,7 @@
</q-avatar>
</div>
<div class="col-span-3 grid grid-cols-1 items-center text-left">
<div class="h-10 flex items-center text-2xl italic font-bold">
<div class="truncate h-10 flex items-center text-xl italic font-bold">
{{ core.name }}
</div>
<span class="truncate">{{ }}</span>

@ -35,6 +35,8 @@
item[k.name] }}
</span>
</template>
<slot :name="`k_${k.name}_append`" :row="item" :value="item[k.name]" :set="setv(item, k.name)">
</slot>
</slot>
</div>
</template>

@ -5,7 +5,7 @@
* Distributed under terms of the MIT license.
-->
<template>
<div class="w-full h-full relative">
<div class="w-full h-full">
<!-- <div class="absolute bg-red-400 left-0 top-0 w-full h-full"></div> -->
<div class="w-full h-full" :id="eid"></div>
</div>
@ -85,6 +85,7 @@ const init = () => {
let config = {
value: props.content,
id: props.eid,
// isPreviewOnly: true,
callback: {
},
fileUpload: fileUpload,
@ -112,6 +113,7 @@ iframe.cherry-dialog-iframe {
.cherry {
background: none;
box-shadow: none;
height: 100%;
}
.cherry-previewer {

@ -14,7 +14,8 @@
</div>
<router-view v-slot="{ Component }">
<transition mode="out-in" enter-active-class="animate__fadeIn" leave-active-class="animate__fadeOut">
<component class="animate__animated animate__400ms" :is="Component"></component>
<component class="animate__animated animate__400ms p-10" style="min-height:calc(100% - 96px)" :is="Component">
</component>
</transition>
</router-view>
</div>

@ -5,7 +5,7 @@
* Distributed under terms of the MIT license.
-->
<template>
<div class="px-10">
<div class="">
<div class="flex justify-between">
<div class="text-3xl">角色管理</div>
<q-btn @click="created(0)"></q-btn>

@ -23,6 +23,9 @@
<q-btn color='primary'>获取秘钥</q-btn>
</div>
</template>
<template #k_redirect_append>
<q-btn class="mx-8" @click="$router.push('/login?uuid=' + app.id)">Go</q-btn>
</template>
</CRUD>
</div>
<div v-if="newApp" class="flex justify-center gap-8 mt-6">

@ -5,11 +5,11 @@
* Distributed under terms of the MIT license.
-->
<template>
<div>
<div class="-mt-16">
<q-page-sticky position="top-right" style="z-index: 20" :offset="[27, 27]">
<q-btn v-if="preview_mode" @click="preview_mode = false" round icon="save_as" class="" />
</q-page-sticky>
<Editor v-if="app.id" :eid="app.id + '.des'" v-model="preview_mode" :content="content" @save="save"></Editor>
<Editor style="" v-if="app.id" :eid="app.id + '.des'" v-model="preview_mode" :content="content" @save="save"></Editor>
</div>
</template>

@ -5,7 +5,7 @@
* Distributed under terms of the MIT license.
-->
<template>
<div class="px-10">
<div class="">
<q-table title="用户管理" :rows="rows" :columns="columns" row-key="name">
<template #body-cell-created="props">
<q-td :props="props">

@ -29,6 +29,7 @@ import util from 'src/libs/util'
import { useUserStore } from 'src/stores/user'
import { useAppStore } from 'src/stores/app'
import { AUStatus, modelsApp } from 'src/models'
import { Base64 } from 'js-base64'
const app = useAppStore()
@ -88,24 +89,25 @@ function redirect(url: string) {
api.app.get(uuid.value as string).then((app: modelsApp) => {
api.token(uuid.value as string).get().then(e => {
url = url || app.redirect
// let data = JSON.parse(Base64.decode(e.split('.')[1]))
// console.log(data)
e = encodeURIComponent(e)
console.log(e)
// e = encodeURIComponent(e)
// url = url.replaceAll('$token', e)
url = url.replaceAll('$token', e)
console.log(url)
window.location.href = url
})
})
} else if (util.checkLogin()) {
if (url) {
} else if (url) {
router.push(url)
} else {
router.push({ name: 'home' })
}
}
}
onMounted(() => {
if (!ifLogOut.value) {
if (!ifLogOut.value && util.checkLogin()) {
redirect('')
}
})

Loading…
Cancel
Save