feat: add $isCurrentUser filter operator (#299)
* feat: add $isCurrentUser filter operator * fix: supports three levels
This commit is contained in:
parent
a37609e71b
commit
f426c8a3ba
@ -12,6 +12,7 @@ export async function get(ctx: Context, next) {
|
|||||||
appends,
|
appends,
|
||||||
except,
|
except,
|
||||||
filter,
|
filter,
|
||||||
|
context: ctx,
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.body = instance;
|
ctx.body = instance;
|
||||||
|
@ -33,6 +33,7 @@ async function listWithPagination(ctx: Context) {
|
|||||||
const repository = getRepositoryFromParams(ctx);
|
const repository = getRepositoryFromParams(ctx);
|
||||||
|
|
||||||
const [rows, count] = await repository.findAndCount({
|
const [rows, count] = await repository.findAndCount({
|
||||||
|
context: ctx,
|
||||||
...findArgs(ctx.action.params),
|
...findArgs(ctx.action.params),
|
||||||
...pageArgsToLimitArgs(parseInt(String(page)), parseInt(String(pageSize))),
|
...pageArgsToLimitArgs(parseInt(String(page)), parseInt(String(pageSize))),
|
||||||
});
|
});
|
||||||
|
@ -33,6 +33,16 @@ export const createdBy: IField = {
|
|||||||
},
|
},
|
||||||
filterable: {
|
filterable: {
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
title: '{{t("ID")}}',
|
||||||
|
operators: operators.id,
|
||||||
|
schema: {
|
||||||
|
title: '{{t("ID")}}',
|
||||||
|
type: 'number',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'nickname',
|
name: 'nickname',
|
||||||
title: '{{t("Nickname")}}',
|
title: '{{t("Nickname")}}',
|
||||||
|
@ -70,8 +70,16 @@ export const number = [
|
|||||||
export const id = [
|
export const id = [
|
||||||
{ label: '{{t("is")}}', value: '$eq', selected: true },
|
{ label: '{{t("is")}}', value: '$eq', selected: true },
|
||||||
{ label: '{{t("is not")}}', value: '$ne' },
|
{ label: '{{t("is not")}}', value: '$ne' },
|
||||||
{ label: '{{t("Exists")}}', value: '$exists', noValue: true },
|
{
|
||||||
{ label: '{{t("Not exists")}}', value: '$notExists', noValue: true },
|
label: '{{t("is current logged-in user")}}',
|
||||||
|
value: '$isCurrentUser',
|
||||||
|
noValue: true,
|
||||||
|
visible(field) {
|
||||||
|
return field.collectionName === 'users';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ label: '{{t("exists")}}', value: '$exists', noValue: true },
|
||||||
|
{ label: '{{t("not exists")}}', value: '$notExists', noValue: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const enumType = [
|
export const enumType = [
|
||||||
|
@ -32,6 +32,16 @@ export const updatedBy: IField = {
|
|||||||
},
|
},
|
||||||
filterable: {
|
filterable: {
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
title: '{{t("ID")}}',
|
||||||
|
operators: operators.id,
|
||||||
|
schema: {
|
||||||
|
title: '{{t("ID")}}',
|
||||||
|
type: 'number',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'nickname',
|
name: 'nickname',
|
||||||
title: '{{t("Nickname")}}',
|
title: '{{t("Nickname")}}',
|
||||||
|
@ -10,8 +10,9 @@ export const useFilterOptions = (collectionName: string) => {
|
|||||||
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
|
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
|
||||||
const { getCollectionFields, getInterface } = useCollectionManager();
|
const { getCollectionFields, getInterface } = useCollectionManager();
|
||||||
const fields = getCollectionFields(collectionName);
|
const fields = getCollectionFields(collectionName);
|
||||||
const field2option = (field, nochildren) => {
|
let depth = 0;
|
||||||
if (nonfilterable.length && !nochildren && nonfilterable.includes(field.name)) {
|
const field2option = (field) => {
|
||||||
|
if (nonfilterable.length && depth !== 1 && nonfilterable.includes(field.name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!field.interface) {
|
if (!field.interface) {
|
||||||
@ -26,9 +27,15 @@ export const useFilterOptions = (collectionName: string) => {
|
|||||||
name: field.name,
|
name: field.name,
|
||||||
title: field?.uiSchema?.title || field.name,
|
title: field?.uiSchema?.title || field.name,
|
||||||
schema: field?.uiSchema,
|
schema: field?.uiSchema,
|
||||||
operators: operators || [],
|
operators:
|
||||||
|
operators?.filter?.((operator) => {
|
||||||
|
return !operator?.visible || operator.visible(field);
|
||||||
|
}) || [],
|
||||||
};
|
};
|
||||||
if (nochildren) {
|
if (field.target && depth > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (depth > 2) {
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
if (children?.length) {
|
if (children?.length) {
|
||||||
@ -36,16 +43,17 @@ export const useFilterOptions = (collectionName: string) => {
|
|||||||
}
|
}
|
||||||
if (nested) {
|
if (nested) {
|
||||||
const targetFields = getCollectionFields(field.target);
|
const targetFields = getCollectionFields(field.target);
|
||||||
const options = getOptions(targetFields, true);
|
const options = getOptions(targetFields).filter(Boolean);
|
||||||
option['children'] = option['children'] || [];
|
option['children'] = option['children'] || [];
|
||||||
option['children'].push(...options);
|
option['children'].push(...options);
|
||||||
}
|
}
|
||||||
return option;
|
return option;
|
||||||
};
|
};
|
||||||
const getOptions = (fields, nochildren = false) => {
|
const getOptions = (fields) => {
|
||||||
|
++depth;
|
||||||
const options = [];
|
const options = [];
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
const option = field2option(field, nochildren);
|
const option = field2option(field);
|
||||||
if (option) {
|
if (option) {
|
||||||
options.push(option);
|
options.push(option);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ export const useValues = () => {
|
|||||||
const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
|
const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
|
||||||
field.data.operator = operator;
|
field.data.operator = operator;
|
||||||
field.data.schema = merge(field.data.schema, operator.schema);
|
field.data.schema = merge(field.data.schema, operator.schema);
|
||||||
field.data.value = operator.noValue ? true : null;
|
field.data.value = operator.noValue ? operator.default || true : null;
|
||||||
data2value();
|
data2value();
|
||||||
console.log('setOperator', field.data);
|
console.log('setOperator', field.data);
|
||||||
},
|
},
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { compile } from '@formily/json-schema/lib/compiler';
|
import { Schema, SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react';
|
||||||
import { SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react';
|
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
export const useCompile = () => {
|
export const useCompile = () => {
|
||||||
const options = useContext(SchemaOptionsContext);
|
const options = useContext(SchemaOptionsContext);
|
||||||
const scope = useContext(SchemaExpressionScopeContext);
|
const scope = useContext(SchemaExpressionScopeContext);
|
||||||
return (source: any) => {
|
return (source: any, ext?: any) => {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
return compile(source, { ...options.scope, ...scope });
|
return Schema.compile(source, { ...options.scope, ...scope, ...ext });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
||||||
import merge from 'deepmerge';
|
import merge from 'deepmerge';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import lodash from 'lodash';
|
||||||
import { ModelCtor, Op, Options, QueryInterfaceDropAllTablesOptions, Sequelize, SyncOptions, Utils } from 'sequelize';
|
import { ModelCtor, Op, Options, QueryInterfaceDropAllTablesOptions, Sequelize, SyncOptions, Utils } from 'sequelize';
|
||||||
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
||||||
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
||||||
@ -11,7 +12,6 @@ import { ModelHook } from './model-hook';
|
|||||||
import extendOperators from './operators';
|
import extendOperators from './operators';
|
||||||
import { RelationRepository } from './relation-repository/relation-repository';
|
import { RelationRepository } from './relation-repository/relation-repository';
|
||||||
import { Repository } from './repository';
|
import { Repository } from './repository';
|
||||||
import lodash from 'lodash';
|
|
||||||
|
|
||||||
export interface MergeOptions extends merge.Options {}
|
export interface MergeOptions extends merge.Options {}
|
||||||
|
|
||||||
@ -34,6 +34,7 @@ interface RegisterOperatorsContext {
|
|||||||
db?: Database;
|
db?: Database;
|
||||||
path?: string;
|
path?: string;
|
||||||
field?: Field;
|
field?: Field;
|
||||||
|
app?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CleanOptions extends QueryInterfaceDropAllTablesOptions {
|
export interface CleanOptions extends QueryInterfaceDropAllTablesOptions {
|
||||||
|
@ -11,6 +11,7 @@ type FilterType = any;
|
|||||||
|
|
||||||
interface FilterParserContext {
|
interface FilterParserContext {
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
|
app?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FilterParser {
|
export default class FilterParser {
|
||||||
@ -125,6 +126,7 @@ export default class FilterParser {
|
|||||||
const queryValue = lodash.get(unflatten(originalFiler), skipPrefix);
|
const queryValue = lodash.get(unflatten(originalFiler), skipPrefix);
|
||||||
|
|
||||||
value = opKey(queryValue, {
|
value = opKey(queryValue, {
|
||||||
|
app: this.context.app,
|
||||||
db: this.database,
|
db: this.database,
|
||||||
path: skipPrefix,
|
path: skipPrefix,
|
||||||
fieldName: this.getFieldNameFromQueryPath(skipPrefix),
|
fieldName: this.getFieldNameFromQueryPath(skipPrefix),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export { ModelCtor, SyncOptions } from 'sequelize';
|
export { ModelCtor, Op, SyncOptions } from 'sequelize';
|
||||||
export * from './collection';
|
export * from './collection';
|
||||||
export * from './database';
|
export * from './database';
|
||||||
export { Database as default } from './database';
|
export { Database as default } from './database';
|
||||||
|
@ -26,7 +26,12 @@ export class OptionsParser {
|
|||||||
this.model = collection.model;
|
this.model = collection.model;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.database = collection.context.database;
|
this.database = collection.context.database;
|
||||||
this.filterParser = new FilterParser(options?.filter, { collection });
|
this.filterParser = new FilterParser(options?.filter, {
|
||||||
|
collection,
|
||||||
|
app: {
|
||||||
|
ctx: options?.context,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (options && options['filter']) {
|
if (options && options['filter']) {
|
||||||
const filterResult = this.parseFilter(options['filter']);
|
const filterResult = this.parseFilter(options['filter'], options);
|
||||||
|
|
||||||
if (filterResult.include && filterResult.include.length > 0) {
|
if (filterResult.include && filterResult.include.length > 0) {
|
||||||
return await this.destroyByFilter(options['filter'], transaction);
|
return await this.destroyByFilter(options['filter'], transaction);
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import { RelationRepository, transaction } from './relation-repository';
|
import { omit } from 'lodash';
|
||||||
import lodash, { omit } from 'lodash';
|
|
||||||
import { MultiAssociationAccessors, Op, Sequelize, Transaction } from 'sequelize';
|
import { MultiAssociationAccessors, Op, Sequelize, Transaction } from 'sequelize';
|
||||||
import { UpdateGuard } from '../update-guard';
|
|
||||||
import { updateModelByValues } from '../update-associations';
|
|
||||||
import {
|
import {
|
||||||
CommonFindOptions,
|
CommonFindOptions,
|
||||||
CountOptions,
|
CountOptions,
|
||||||
@ -10,11 +7,14 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
FilterByTk,
|
FilterByTk,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
TK,
|
|
||||||
TargetKey,
|
TargetKey,
|
||||||
|
TK,
|
||||||
TransactionAble,
|
TransactionAble,
|
||||||
UpdateOptions,
|
UpdateOptions
|
||||||
} from '../repository';
|
} from '../repository';
|
||||||
|
import { updateModelByValues } from '../update-associations';
|
||||||
|
import { UpdateGuard } from '../update-guard';
|
||||||
|
import { RelationRepository, transaction } from './relation-repository';
|
||||||
|
|
||||||
export interface FindAndCountOptions extends CommonFindOptions {}
|
export interface FindAndCountOptions extends CommonFindOptions {}
|
||||||
|
|
||||||
@ -187,10 +187,11 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected filterHasInclude(filter: Filter) {
|
protected filterHasInclude(filter: Filter, options?: any) {
|
||||||
const filterResult = this.parseFilter(filter);
|
const filterResult = this.parseFilter(filter, options);
|
||||||
return filterResult.include && filterResult.include.length > 0;
|
return filterResult.include && filterResult.include.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected accessors() {
|
protected accessors() {
|
||||||
return <MultiAssociationAccessors>super.accessors();
|
return <MultiAssociationAccessors>super.accessors();
|
||||||
}
|
}
|
||||||
|
@ -90,9 +90,12 @@ export abstract class RelationRepository {
|
|||||||
return { ...options, ...params };
|
return { ...options, ...params };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseFilter(filter: Filter) {
|
protected parseFilter(filter: Filter, options?: any) {
|
||||||
const parser = new FilterParser(filter, {
|
const parser = new FilterParser(filter, {
|
||||||
collection: this.targetCollection,
|
collection: this.targetCollection,
|
||||||
|
app: {
|
||||||
|
ctx: options?.context,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return parser.toSequelizeParams();
|
return parser.toSequelizeParams();
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
ModelCtor,
|
ModelCtor,
|
||||||
Op,
|
Op,
|
||||||
Transaction,
|
Transaction,
|
||||||
UpdateOptions as SequelizeUpdateOptions,
|
UpdateOptions as SequelizeUpdateOptions
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { Collection } from './collection';
|
import { Collection } from './collection';
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
@ -74,6 +74,7 @@ export interface CommonFindOptions {
|
|||||||
appends?: Appends;
|
appends?: Appends;
|
||||||
except?: Except;
|
except?: Except;
|
||||||
sort?: Sort;
|
sort?: Sort;
|
||||||
|
context?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FindOneOptions extends FindOptions, CommonFindOptions {}
|
interface FindOneOptions extends FindOptions, CommonFindOptions {}
|
||||||
@ -187,7 +188,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|||||||
if (countOptions?.filter) {
|
if (countOptions?.filter) {
|
||||||
options = {
|
options = {
|
||||||
...options,
|
...options,
|
||||||
...this.parseFilter(countOptions.filter),
|
...this.parseFilter(countOptions.filter, countOptions),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,9 +454,12 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|||||||
return { where: {}, ...options, ...params };
|
return { where: {}, ...options, ...params };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseFilter(filter: Filter) {
|
protected parseFilter(filter: Filter, options?: any) {
|
||||||
const parser = new FilterParser(filter, {
|
const parser = new FilterParser(filter, {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
|
app: {
|
||||||
|
ctx: options?.context,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return parser.toSequelizeParams();
|
return parser.toSequelizeParams();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Collection } from '@nocobase/database';
|
import { Collection, Op } from '@nocobase/database';
|
||||||
import { Plugin } from '@nocobase/server';
|
import { Plugin } from '@nocobase/server';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import * as actions from './actions/users';
|
import * as actions from './actions/users';
|
||||||
@ -25,6 +25,13 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
|
this.db.registerOperators({
|
||||||
|
$isCurrentUser(_, ctx) {
|
||||||
|
return {
|
||||||
|
[Op.eq]: ctx?.app?.ctx?.state?.currentUser?.id || -1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
this.db.registerModels({ UserModel });
|
this.db.registerModels({ UserModel });
|
||||||
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
|
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
|
Loading…
Reference in New Issue
Block a user