update crud macro

master
veypi 1 year ago
parent 1de22a98d4
commit 2dda72bbf3

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

@ -6,8 +6,8 @@
//
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::{AttributeArgs, ItemFn, NestedMeta, ReturnType};
use quote::{format_ident, quote, ToTokens};
use syn::{AttributeArgs, ItemFn, NestedMeta, ReturnType, Token};
pub struct CrudWrap {
func: ItemFn,
@ -49,24 +49,61 @@ 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];
(
vec![quote! {let #_k = #v; }],
vec![quote! {
filter(crate::models::#model_name::Column::#k.eq(#_k))
}],
)
}
_ => (
self.args
.filters
.iter()
.enumerate()
.map(|(idx, [k, v])| {
let _k = format_ident!("_{}", k);
quote! {
let #_k = #v.#idx;
}
})
.collect(),
self.args
.filters
.iter()
.map(|[k, _]| {
let _k = format_ident!("_{}", k);
quote! {
filter(crate::models::#model_name::Column::#k.eq(#_k))
}
})
.collect(),
),
};
let stream = quote! {
#(#fn_attrs)*
#func_vis #fn_async fn #fn_name #fn_generics(
#fn_args
) -> #fn_output {
let _id = &id.clone();
let _id = id.clone();
let _data = data.clone();
let _db = &stat.db().clone();
#(#args_fields)*
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 obj = crate::models::#model_name::Entity::find().#(#filter_fields).*.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())),
None => return Err(Error::NotFound("".into())),
};
#(#builder_fields)*
let obj = obj.update(_db).await?;
@ -83,12 +120,14 @@ impl ToTokens for CrudWrap {
struct Crud {
model: syn::Ident,
attrs: Vec<syn::Ident>,
filters: Vec<[syn::Ident; 2]>,
}
impl Crud {
fn new(args: AttributeArgs) -> 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();
for arg in args {
match arg {
// NestedMeta::Lit(syn::Lit::Str(lit)) => {
@ -103,14 +142,36 @@ impl Crud {
}
None => {}
},
NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(lit_str),
..
})) => {
match path.get_ident() {
Some(expr) => {
filters.push([expr.to_owned(), format_ident!("{}", lit_str.value())])
}
None => {
return Err(syn::Error::new_spanned(
path,
"Unknown identifier. Available: 'aid='AppId'",
));
}
};
}
_ => {
return Err(syn::Error::new_spanned(arg, "Unknown attribute."));
}
}
}
println!("|||||||||||____________________");
match model {
Some(model) => Ok(Self { model, attrs }),
Some(model) => Ok(Self {
model,
attrs,
filters,
}),
None => Err(syn::Error::new(
Span::call_site(),
"The #[crud(..)] macro requires one `model` name",

@ -8,13 +8,12 @@
//
use actix_web::{web, Responder};
use proc::crud_update;
use sea_orm::{ActiveModelTrait, EntityTrait};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
use serde::{Deserialize, Serialize};
use crate::{models::app, AppState, Error, Result};
// #[derive(Debug, Deserialize, Serialize)]
#[derive(Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct UpdateOpt {
pub name: Option<String>,
pub icon: Option<String>,
@ -25,7 +24,17 @@ pub struct UpdateOpt {
pub status: Option<i32>,
}
impl UpdateOpt {
#[crud_update(app, name, icon, des, join_method, role_id, redirect, status)]
// #[crud_update(
// app,
// id = "Id",
// name,
// icon,
// des,
// join_method,
// role_id,
// redirect,
// status
// )]
pub async fn update(
id: web::Path<String>,
stat: web::Data<AppState>,

@ -7,7 +7,7 @@
//
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 sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
use serde::{Deserialize, Serialize};
use tracing::info;
@ -93,7 +93,17 @@ pub struct UpdateOpt {
#[patch("/app/{id}")]
#[access_update("app")]
#[crud_update(app, name, icon, des, join_method, role_id, redirect, status)]
#[crud_update(
app,
Id = "_id",
name,
icon,
des,
join_method,
role_id,
redirect,
status
)]
pub async fn update(
id: web::Path<String>,
stat: web::Data<AppState>,

@ -5,9 +5,11 @@
// Distributed under terms of the MIT license.
//
use actix_web::{get, post, web, Responder};
use proc::access_read;
use sea_orm::{ColumnTrait, EntityTrait, LoaderTrait, QueryFilter, TransactionTrait};
use actix_web::{get, patch, post, web, Responder};
use proc::{access_delete, access_read, crud_update};
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, LoaderTrait, QueryFilter, TransactionTrait,
};
use serde::{Deserialize, Serialize};
use crate::{
@ -92,3 +94,19 @@ pub async fn add(
db.commit().await?;
Ok(web::Json(res))
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateOpt {
pub status: Option<i32>,
}
#[patch("/app/{aid}/user/{uid}")]
#[access_delete("app")]
#[crud_update(app_user, AppId = "_id", UserId = "_id", status)]
pub async fn update(
id: web::Path<(String, String)>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
Ok("")
}

@ -29,5 +29,7 @@ pub fn routes(cfg: &mut web::ServiceConfig) {
.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)
.service(appuser::update);
}

@ -6,6 +6,8 @@
//
//
use std::{fs, path::Path};
use actix_multipart::form::{tempfile::TempFile, MultipartForm};
use actix_web::{post, web, Responder};
@ -19,29 +21,45 @@ struct UploadForm {
files: Vec<TempFile>,
}
#[post("/upload/")]
#[post("/upload/{dir:.*}")]
#[access_read("app")]
async fn save_files(
MultipartForm(form): MultipartForm<UploadForm>,
t: web::ReqData<Token>,
dir: web::Path<String>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let l = form.files.len();
let t = t.into_inner();
let mut dir = dir.into_inner();
if dir.is_empty() {
dir = "tmp".to_string();
}
let mut res: Vec<String> = Vec::new();
info!("!|||||||||||_{}_|", l);
form.files.into_iter().for_each(|v| {
let fname = v.file_name.unwrap_or("unknown".to_string());
let path = format!("{}tmp/{}.{}", stat.media_path, t.id, fname);
info!("saving to {path}");
match v.file.persist(path) {
let root = Path::new(&stat.media_path).join(dir.clone());
if !root.exists() {
match fs::create_dir_all(root.clone()) {
Ok(_) => {}
Err(e) => {
warn!("{}", e);
}
}
}
let temp_file = format!(
"{}/{}.{}",
root.to_str().unwrap_or(&stat.media_path),
t.id,
fname
);
info!("saving to {temp_file}");
match v.file.persist(temp_file) {
Ok(p) => {
info!("{:#?}", p);
res.push(format!("/media/tmp/{}.{}", t.id, fname))
res.push(format!("/media/{}/{}.{}", dir, t.id, fname))
}
Err(e) => {
warn!("{}", e);
// return Err(Error::InternalServerError);
}
};
});

@ -129,8 +129,8 @@ impl AppState {
log_dir: None,
log_temp_size: None,
log_pack_compress: None,
media_path: "/Users/veypi/test/media/".to_string(),
fs_root: "/Users/veypi/test/media/".to_string(),
media_path: "/Users/veypi/test/media".to_string(),
fs_root: "/Users/veypi/test/media".to_string(),
log_level: None,
jwt_secret: None,
_sqlx: None,
@ -197,7 +197,7 @@ pub fn init_log() {
tracing_subscriber::fmt()
.with_line_number(true)
.with_timer(FormatTime {})
.with_max_level(Level::DEBUG)
.with_max_level(Level::TRACE)
.with_target(false)
.with_file(true)
.init();

@ -90,15 +90,10 @@ async fn web(data: AppState) -> Result<()> {
.unwrap(),
HeaderValue::from_str(&origin).unwrap(),
);
headers.insert(
HeaderName::try_from("123").unwrap(),
HeaderValue::from_str("asd").unwrap(),
);
Ok(expr)
}
Err(e) => Err(e),
};
println!("Hi from response");
res
})
})

@ -10,8 +10,8 @@ use serde::{Deserialize, Serialize};
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub created: Option<DateTime>,
pub updated: Option<DateTime>,
pub created: DateTime,
pub updated: DateTime,
pub app_id: String,
pub name: String,
pub role_id: Option<String>,

@ -10,11 +10,14 @@ use serde::{Deserialize, Serialize};
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub created: Option<DateTime>,
pub updated: Option<DateTime>,
pub created: DateTime,
pub updated: DateTime,
#[sea_orm(column_name = "_key")]
#[serde(skip)]
pub key: String,
pub name: String,
pub icon: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub des: Option<String>,
pub user_count: i32,
pub hide: i8,

@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
)]
#[sea_orm(table_name = "app_user")]
pub struct Model {
pub created: Option<DateTime>,
pub updated: Option<DateTime>,
pub created: DateTime,
pub updated: DateTime,
#[sea_orm(primary_key, auto_increment = false)]
pub app_id: String,
#[sea_orm(primary_key, auto_increment = false)]

@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
)]
#[sea_orm(table_name = "resource")]
pub struct Model {
pub created: Option<DateTime>,
pub updated: Option<DateTime>,
pub created: DateTime,
pub updated: DateTime,
#[sea_orm(primary_key, auto_increment = false)]
pub app_id: String,
#[sea_orm(primary_key, auto_increment = false)]

@ -10,8 +10,8 @@ use serde::{Deserialize, Serialize};
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub created: Option<DateTime>,
pub updated: Option<DateTime>,
pub created: DateTime,
pub updated: DateTime,
pub app_id: String,
pub name: String,
pub des: Option<String>,

@ -10,8 +10,8 @@ use serde::{Deserialize, Serialize};
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub created: Option<DateTime>,
pub updated: Option<DateTime>,
pub created: DateTime,
pub updated: DateTime,
#[sea_orm(unique)]
pub username: String,
pub nickname: Option<String>,

@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
)]
#[sea_orm(table_name = "user_role")]
pub struct Model {
pub created: Option<DateTime>,
pub updated: Option<DateTime>,
pub created: DateTime,
pub updated: DateTime,
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: String,
#[sea_orm(primary_key, auto_increment = false)]

@ -27,8 +27,8 @@ pub async fn init(data: AppState) {
#[derive(Debug, Deserialize, Serialize)]
pub struct UnionAppUser {
pub created: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
pub created: chrono::NaiveDateTime,
pub updated: chrono::NaiveDateTime,
pub app: Option<app::Model>,
pub user: Option<user::Model>,
pub app_id: String,

@ -40,7 +40,7 @@ export default {
add(uid: string) {
return ajax.post(this.local + uid)
},
update(uid: number, status: string) {
update(uid: string, status: number) {
return ajax.patch(this.local + uid, { status })
},
}

@ -12,7 +12,7 @@
'']" style="font-size: 24px;" :name="root.type ===
'directory' ? 'v-caret-right' : 'v-file'"> </q-icon>
<div>
{{ root.filename }}
{{ root.basename || '/' }}
</div>
<div class="grow"></div>
<div>{{ new Date(root.lastmod).toLocaleString() }}</div>

@ -64,11 +64,9 @@ const fileUpload = (f: File, cb: (url: string, params: any) => void) => {
* @param params.width 设置宽度可以是像素也可以是百分比图片视频场景下生效
* @param params.height 设置高度可以是像素也可以是百分比图片视频场景下生效
*/
console.log('uploading file' + f.name)
let url = '/abc/'
oafs.appdav().upload(url, oafs.rename(f.name), f).then((e: any) => {
cb(e, {
name: f.name, isBorder: false, isShadow: false, isRadius: false, width: '80%', height: '80%',
oafs.upload([f], props.static_dir).then((e: any) => {
cb(e[0], {
name: f.name, isBorder: false, isShadow: false, isRadius: false, width: '', height: '',
})
})
}
@ -77,7 +75,8 @@ const init = () => {
value: props.content,
id: props.eid,
// isPreviewOnly: props.preview,
callback: {},
callback: {
},
fileUpload: fileUpload,
} as CherryOptions;
config.callback.afterInit = () => {

@ -16,7 +16,8 @@ let emits = defineEmits<{
}>()
let props = withDefaults(defineProps<{
multiple?: boolean,
renames?: string
renames?: string,
dir?: string,
}>(), {
multiple: false,
renames: ''
@ -29,7 +30,7 @@ function click() {
const upload = (evt: Event) => {
evt.preventDefault()
let f = (evt.target as HTMLInputElement).files as FileList
oafs.upload(f, props.renames?.split(/[, ]+/)).then((e: any) => {
oafs.upload(f, props.dir, props.renames?.split(/[, ]+/)).then((e: any) => {
console.log(e)
emits('success', props.multiple ? e : e[0])
})

@ -51,14 +51,15 @@ const get = (url: string): Promise<string> => {
return fetch(url, { headers: { auth_token: util.getToken() } }).then((response) => response.text())
}
const upload = (f: FileList | File[], renames?: string[]) => {
// rename 可以保持url不变
const upload = (f: FileList | File[], dir?: string, renames?: string[]) => {
return new Promise<string[]>((resolve, reject) => {
var data = new FormData();
for (let i = 0; i < f.length; i++) {
let nf = renames ? new File([f[i]], rename(f[i].name, renames[i]), { type: f[i].type }) : f[i]
let nf = new File([f[i]], rename(f[i].name, renames && renames[i] ? renames[i] : undefined), { type: f[i].type })
data.append('files', nf, nf.name)
}
axios.post("/api/upload/", data, {
axios.post("/api/upload/" + (dir || ''), data, {
headers: {
"Content-Type": 'multipart/form-data',
'auth_token': cfg.token,

@ -85,8 +85,8 @@ export enum AUStatus {
}
export interface modelsAppUser {
app?: modelsApp,
user?: modelsUser,
app: modelsApp,
user: modelsUser,
app_id: string
user_id: string
status: AUStatus
@ -105,6 +105,7 @@ export interface modelsUser {
status: number
used: number
space: number
au: AUStatus
// Index 前端缓存
// Index?: number

@ -0,0 +1,15 @@
<!--
* AppAuth.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-09 23:18
* Distributed under terms of the MIT license.
-->
<template>
<div></div>
</template>
<script lang="ts" setup>
</script>
<style scoped></style>

@ -44,7 +44,7 @@ const sync = () => {
const save = (des: string) => {
let a = new File([des], app.value.name + '.md');
oafs.upload([a]).then(url => {
oafs.upload([a], app.value.id).then(url => {
api.app.update(app.value.id, { des: url[0] }).then(e => {
edit_mode.value = false
app.value.des = url[0]

@ -10,17 +10,17 @@
<template v-slot:body-cell-status="props">
<q-td :props="props">
<div>
<q-chip outline :color="statusOpts[props.row.status][1]" text-color="white" icon="event">
{{ statusOpts[props.row.status][0] }}
<q-chip outline :color="auOpts[props.row.au][1]" text-color="white" icon="event">
{{ auOpts[props.row.au][0] }}
</q-chip>
</div>
<q-popup-edit v-model="props.row.status" v-slot="scope" buttons
@save="update_status(props.row.id, $event, props.row.status)" label-set="确定" label-cancel="取消">
<q-popup-edit v-model="props.row.au" v-slot="scope" buttons
@save="update_status(props.row.id, $event, props.row.au)" label-set="确定" label-cancel="取消">
<div class="mt-4 mb-2">切换状态至</div>
<div class="q-gutter-sm">
<q-radio :key="i" v-for="i in [0, 1, 2, 3]" keep-color v-model="scope.value" :val="i"
:label="statusOpts[i][0]" :color="statusOpts[i][1]" />
<q-radio :key="i" v-for="i in [0, 1, 2, 3]" keep-color v-model="scope.value" :val="i" :label="auOpts[i][0]"
:color="auOpts[i][1]" />
</div>
</q-popup-edit>
@ -31,18 +31,18 @@
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { AUStatus, modelsAppUser } from 'src/models';
import { QTableProps } from 'quasar';
import { computed, inject, onMounted, Ref, ref, watch } from 'vue';
import { AUStatus, modelsAppUser, modelsUser, modelsApp } from 'src/models';
import api from 'src/boot/api';
import { useAppStore } from 'src/stores/app';
import msg from '@veypi/msg';
const statusOpts: { [index: number]: any } = {
const auOpts: { [index: number]: any } = {
[AUStatus.OK]: ['正常', 'positive'],
[AUStatus.Deny]: ['拒绝', 'warning'],
[AUStatus.Applying]: ['申请中', 'primary'],
[AUStatus.Disabled]: ['禁用', 'warning'],
}
let app = inject('app') as Ref<modelsApp>
const columns = [
{
name: 'id',
@ -64,23 +64,35 @@ const columns = [
{ name: 'action', field: 'action', align: 'center', label: '操作' },
] as any
const rows = ref([
{
id: '1', username: 'name', nickname: 'asd', created: 'asdsss',
status: 0
}
])
const rows = ref([] as modelsUser[])
const update_status = (d, s) => {
const update_status = (id: string, n: number, old: number) => {
api.app.user(app.value.id).update(id, n).then(e => {
msg.Info('修改成功')
}).catch(e => {
let a = rows.value.find(a => a.id = id) || {} as any
a.status = old
})
console.log([d, s])
console.log([id, n, old])
}
onMounted(() => {
api.app.user(useAppStore().id).list('-', { user: true }).then((e:
const sync = () => {
if (!app.value.id) {
return
}
api.app.user(app.value.id).list('-', { user: true }).then((e:
modelsAppUser[]) => {
rows.value = e.map(i => i.user)
rows.value = e.map(i => {
i.user.au = i.status
return i.user
})
})
}
watch(computed(() => app.value.id), () => sync())
onMounted(() => {
sync()
})
</script>

@ -32,7 +32,7 @@
<q-form @submit="create_new">
<q-input label="应用名" v-model="temp_app.name" :rules="rules.name"></q-input>
<div class="flex justify-center my-4 items-center" label='icon'>
<uploader @success="temp_app.icon = $event">
<uploader @success="temp_app.icon = $event" dir="app_icon">
<q-avatar>
<img :src="temp_app.icon">
</q-avatar>

@ -45,7 +45,7 @@ const routes: RouteRecordRaw[] = [
children: [
loadcomponents('home', 'app.home', 'AppHome'),
loadcomponents('user', 'app.user', 'AppUser'),
loadcomponents('user', 'app.user', 'AppUser'),
loadcomponents('auth', 'app.auth', 'AppAuth'),
loadcomponents('settings', 'app.settings', 'IndexPage'),
]
}

Loading…
Cancel
Save