From f426c8a3ba74b59ad207be72f9457995941344df Mon Sep 17 00:00:00 2001 From: chenos Date: Tue, 19 Apr 2022 17:04:54 +0800 Subject: [PATCH] feat: add $isCurrentUser filter operator (#299) * feat: add $isCurrentUser filter operator * fix: supports three levels --- packages/core/actions/src/actions/get.ts | 1 + packages/core/actions/src/actions/list.ts | 1 + .../interfaces/createdBy.ts | 10 +++++++++ .../interfaces/properties/operators.ts | 12 ++++++++-- .../interfaces/updatedBy.ts | 10 +++++++++ .../antd/filter/useFilterActionProps.ts | 22 +++++++++++++------ .../schema-component/antd/filter/useValues.ts | 2 +- .../src/schema-component/hooks/useCompile.ts | 7 +++--- packages/core/database/src/database.ts | 3 ++- packages/core/database/src/filter-parser.ts | 2 ++ packages/core/database/src/index.ts | 2 +- packages/core/database/src/options-parser.ts | 7 +++++- .../relation-repository/hasmany-repository.ts | 2 +- .../multiple-relation-repository.ts | 17 +++++++------- .../relation-repository.ts | 5 ++++- packages/core/database/src/repository.ts | 10 ++++++--- packages/plugins/users/src/server.ts | 9 +++++++- 17 files changed, 91 insertions(+), 31 deletions(-) diff --git a/packages/core/actions/src/actions/get.ts b/packages/core/actions/src/actions/get.ts index 0f9f6559a..7a875267a 100644 --- a/packages/core/actions/src/actions/get.ts +++ b/packages/core/actions/src/actions/get.ts @@ -12,6 +12,7 @@ export async function get(ctx: Context, next) { appends, except, filter, + context: ctx, }); ctx.body = instance; diff --git a/packages/core/actions/src/actions/list.ts b/packages/core/actions/src/actions/list.ts index 409a90b68..d193969a2 100644 --- a/packages/core/actions/src/actions/list.ts +++ b/packages/core/actions/src/actions/list.ts @@ -33,6 +33,7 @@ async function listWithPagination(ctx: Context) { const repository = getRepositoryFromParams(ctx); const [rows, count] = await repository.findAndCount({ + context: ctx, ...findArgs(ctx.action.params), ...pageArgsToLimitArgs(parseInt(String(page)), parseInt(String(pageSize))), }); diff --git a/packages/core/client/src/collection-manager/interfaces/createdBy.ts b/packages/core/client/src/collection-manager/interfaces/createdBy.ts index 350e9b6c5..8619fccc9 100644 --- a/packages/core/client/src/collection-manager/interfaces/createdBy.ts +++ b/packages/core/client/src/collection-manager/interfaces/createdBy.ts @@ -33,6 +33,16 @@ export const createdBy: IField = { }, filterable: { children: [ + { + name: 'id', + title: '{{t("ID")}}', + operators: operators.id, + schema: { + title: '{{t("ID")}}', + type: 'number', + 'x-component': 'InputNumber', + }, + }, { name: 'nickname', title: '{{t("Nickname")}}', diff --git a/packages/core/client/src/collection-manager/interfaces/properties/operators.ts b/packages/core/client/src/collection-manager/interfaces/properties/operators.ts index c0d5265b6..15d43e2ca 100644 --- a/packages/core/client/src/collection-manager/interfaces/properties/operators.ts +++ b/packages/core/client/src/collection-manager/interfaces/properties/operators.ts @@ -70,8 +70,16 @@ export const number = [ export const id = [ { label: '{{t("is")}}', value: '$eq', selected: true }, { 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 = [ diff --git a/packages/core/client/src/collection-manager/interfaces/updatedBy.ts b/packages/core/client/src/collection-manager/interfaces/updatedBy.ts index 95e535753..fdb0b934c 100644 --- a/packages/core/client/src/collection-manager/interfaces/updatedBy.ts +++ b/packages/core/client/src/collection-manager/interfaces/updatedBy.ts @@ -32,6 +32,16 @@ export const updatedBy: IField = { }, filterable: { children: [ + { + name: 'id', + title: '{{t("ID")}}', + operators: operators.id, + schema: { + title: '{{t("ID")}}', + type: 'number', + 'x-component': 'InputNumber', + }, + }, { name: 'nickname', title: '{{t("Nickname")}}', diff --git a/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts b/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts index 4866d3877..0be3c3fa5 100644 --- a/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts +++ b/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts @@ -10,8 +10,9 @@ export const useFilterOptions = (collectionName: string) => { const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || []; const { getCollectionFields, getInterface } = useCollectionManager(); const fields = getCollectionFields(collectionName); - const field2option = (field, nochildren) => { - if (nonfilterable.length && !nochildren && nonfilterable.includes(field.name)) { + let depth = 0; + const field2option = (field) => { + if (nonfilterable.length && depth !== 1 && nonfilterable.includes(field.name)) { return; } if (!field.interface) { @@ -26,9 +27,15 @@ export const useFilterOptions = (collectionName: string) => { name: field.name, title: field?.uiSchema?.title || field.name, 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; } if (children?.length) { @@ -36,16 +43,17 @@ export const useFilterOptions = (collectionName: string) => { } if (nested) { const targetFields = getCollectionFields(field.target); - const options = getOptions(targetFields, true); + const options = getOptions(targetFields).filter(Boolean); option['children'] = option['children'] || []; option['children'].push(...options); } return option; }; - const getOptions = (fields, nochildren = false) => { + const getOptions = (fields) => { + ++depth; const options = []; fields.forEach((field) => { - const option = field2option(field, nochildren); + const option = field2option(field); if (option) { options.push(option); } diff --git a/packages/core/client/src/schema-component/antd/filter/useValues.ts b/packages/core/client/src/schema-component/antd/filter/useValues.ts index acb1b4402..9079ea1cc 100644 --- a/packages/core/client/src/schema-component/antd/filter/useValues.ts +++ b/packages/core/client/src/schema-component/antd/filter/useValues.ts @@ -70,7 +70,7 @@ export const useValues = () => { const operator = field.data?.operators?.find?.((item) => item.value === operatorValue); field.data.operator = operator; 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(); console.log('setOperator', field.data); }, diff --git a/packages/core/client/src/schema-component/hooks/useCompile.ts b/packages/core/client/src/schema-component/hooks/useCompile.ts index 3eff88cd1..922ef9885 100644 --- a/packages/core/client/src/schema-component/hooks/useCompile.ts +++ b/packages/core/client/src/schema-component/hooks/useCompile.ts @@ -1,14 +1,13 @@ -import { compile } from '@formily/json-schema/lib/compiler'; -import { SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react'; +import { Schema, SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react'; import { useContext } from 'react'; export const useCompile = () => { const options = useContext(SchemaOptionsContext); const scope = useContext(SchemaExpressionScopeContext); - return (source: any) => { + return (source: any, ext?: any) => { if (!source) { return source; } - return compile(source, { ...options.scope, ...scope }); + return Schema.compile(source, { ...options.scope, ...scope, ...ext }); }; }; diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts index 37964c31e..024412837 100644 --- a/packages/core/database/src/database.ts +++ b/packages/core/database/src/database.ts @@ -1,6 +1,7 @@ import { applyMixins, AsyncEmitter } from '@nocobase/utils'; import merge from 'deepmerge'; import { EventEmitter } from 'events'; +import lodash from 'lodash'; import { ModelCtor, Op, Options, QueryInterfaceDropAllTablesOptions, Sequelize, SyncOptions, Utils } from 'sequelize'; import { Collection, CollectionOptions, RepositoryType } from './collection'; import { ImporterReader, ImportFileExtension } from './collection-importer'; @@ -11,7 +12,6 @@ import { ModelHook } from './model-hook'; import extendOperators from './operators'; import { RelationRepository } from './relation-repository/relation-repository'; import { Repository } from './repository'; -import lodash from 'lodash'; export interface MergeOptions extends merge.Options {} @@ -34,6 +34,7 @@ interface RegisterOperatorsContext { db?: Database; path?: string; field?: Field; + app?: any; } export interface CleanOptions extends QueryInterfaceDropAllTablesOptions { diff --git a/packages/core/database/src/filter-parser.ts b/packages/core/database/src/filter-parser.ts index 5a33ea30c..7ae323306 100644 --- a/packages/core/database/src/filter-parser.ts +++ b/packages/core/database/src/filter-parser.ts @@ -11,6 +11,7 @@ type FilterType = any; interface FilterParserContext { collection: Collection; + app?: any; } export default class FilterParser { @@ -125,6 +126,7 @@ export default class FilterParser { const queryValue = lodash.get(unflatten(originalFiler), skipPrefix); value = opKey(queryValue, { + app: this.context.app, db: this.database, path: skipPrefix, fieldName: this.getFieldNameFromQueryPath(skipPrefix), diff --git a/packages/core/database/src/index.ts b/packages/core/database/src/index.ts index 7943d41e3..8a1a9e2e8 100644 --- a/packages/core/database/src/index.ts +++ b/packages/core/database/src/index.ts @@ -1,4 +1,4 @@ -export { ModelCtor, SyncOptions } from 'sequelize'; +export { ModelCtor, Op, SyncOptions } from 'sequelize'; export * from './collection'; export * from './database'; export { Database as default } from './database'; diff --git a/packages/core/database/src/options-parser.ts b/packages/core/database/src/options-parser.ts index 44a09fb3b..02cb3b06b 100644 --- a/packages/core/database/src/options-parser.ts +++ b/packages/core/database/src/options-parser.ts @@ -26,7 +26,12 @@ export class OptionsParser { this.model = collection.model; this.options = options; 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; } diff --git a/packages/core/database/src/relation-repository/hasmany-repository.ts b/packages/core/database/src/relation-repository/hasmany-repository.ts index 690845488..8e820f1f8 100644 --- a/packages/core/database/src/relation-repository/hasmany-repository.ts +++ b/packages/core/database/src/relation-repository/hasmany-repository.ts @@ -66,7 +66,7 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa ]; if (options && options['filter']) { - const filterResult = this.parseFilter(options['filter']); + const filterResult = this.parseFilter(options['filter'], options); if (filterResult.include && filterResult.include.length > 0) { return await this.destroyByFilter(options['filter'], transaction); diff --git a/packages/core/database/src/relation-repository/multiple-relation-repository.ts b/packages/core/database/src/relation-repository/multiple-relation-repository.ts index 7493ee779..fd808114f 100644 --- a/packages/core/database/src/relation-repository/multiple-relation-repository.ts +++ b/packages/core/database/src/relation-repository/multiple-relation-repository.ts @@ -1,8 +1,5 @@ -import { RelationRepository, transaction } from './relation-repository'; -import lodash, { omit } from 'lodash'; +import { omit } from 'lodash'; import { MultiAssociationAccessors, Op, Sequelize, Transaction } from 'sequelize'; -import { UpdateGuard } from '../update-guard'; -import { updateModelByValues } from '../update-associations'; import { CommonFindOptions, CountOptions, @@ -10,11 +7,14 @@ import { Filter, FilterByTk, FindOptions, - TK, TargetKey, + TK, TransactionAble, - UpdateOptions, + UpdateOptions } from '../repository'; +import { updateModelByValues } from '../update-associations'; +import { UpdateGuard } from '../update-guard'; +import { RelationRepository, transaction } from './relation-repository'; export interface FindAndCountOptions extends CommonFindOptions {} @@ -187,10 +187,11 @@ export abstract class MultipleRelationRepository extends RelationRepository { }); } - protected filterHasInclude(filter: Filter) { - const filterResult = this.parseFilter(filter); + protected filterHasInclude(filter: Filter, options?: any) { + const filterResult = this.parseFilter(filter, options); return filterResult.include && filterResult.include.length > 0; } + protected accessors() { return super.accessors(); } diff --git a/packages/core/database/src/relation-repository/relation-repository.ts b/packages/core/database/src/relation-repository/relation-repository.ts index 2fd2f770d..49ddb3a55 100644 --- a/packages/core/database/src/relation-repository/relation-repository.ts +++ b/packages/core/database/src/relation-repository/relation-repository.ts @@ -90,9 +90,12 @@ export abstract class RelationRepository { return { ...options, ...params }; } - protected parseFilter(filter: Filter) { + protected parseFilter(filter: Filter, options?: any) { const parser = new FilterParser(filter, { collection: this.targetCollection, + app: { + ctx: options?.context, + }, }); return parser.toSequelizeParams(); } diff --git a/packages/core/database/src/repository.ts b/packages/core/database/src/repository.ts index 56feb320f..5d98d7aec 100644 --- a/packages/core/database/src/repository.ts +++ b/packages/core/database/src/repository.ts @@ -9,7 +9,7 @@ import { ModelCtor, Op, Transaction, - UpdateOptions as SequelizeUpdateOptions, + UpdateOptions as SequelizeUpdateOptions } from 'sequelize'; import { Collection } from './collection'; import { Database } from './database'; @@ -74,6 +74,7 @@ export interface CommonFindOptions { appends?: Appends; except?: Except; sort?: Sort; + context?: any; } interface FindOneOptions extends FindOptions, CommonFindOptions {} @@ -187,7 +188,7 @@ export class Repository { } async beforeLoad() { + this.db.registerOperators({ + $isCurrentUser(_, ctx) { + return { + [Op.eq]: ctx?.app?.ctx?.state?.currentUser?.id || -1, + }; + }, + }); this.db.registerModels({ UserModel }); this.db.on('users.afterCreateWithAssociations', async (model, options) => { const { transaction } = options;