From 159775ff5412dd8de3ddf422c6b5d26bec00e6f7 Mon Sep 17 00:00:00 2001 From: Chareice Date: Wed, 9 Feb 2022 20:24:25 +0800 Subject: [PATCH] refactor: server hooks --- .../src/__tests__/server-hook.test.ts | 131 +++++++++++++----- .../src/collections/uiSchemaAttr.ts | 13 -- .../src/collections/uiSchemaServerHooks.ts | 24 ++++ .../src/collections/ui_schemas.ts | 6 +- .../plugin-ui-schema-storage/src/model.ts | 6 +- .../src/repository.ts | 17 +-- .../src/server-hooks/index.ts | 117 ++++++++-------- .../src/server-hooks/model.ts | 3 + .../plugin-ui-schema-storage/src/server.ts | 3 +- 9 files changed, 195 insertions(+), 125 deletions(-) delete mode 100644 packages/plugin-ui-schema-storage/src/collections/uiSchemaAttr.ts create mode 100644 packages/plugin-ui-schema-storage/src/collections/uiSchemaServerHooks.ts create mode 100644 packages/plugin-ui-schema-storage/src/server-hooks/model.ts diff --git a/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts b/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts index 3462f71f0..fe5c34a3a 100644 --- a/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts +++ b/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts @@ -17,9 +17,13 @@ describe('server hooks', () => { 'x-uid': 'table', 'x-component': 'Table', 'x-collection': 'posts', - 'x-server-hooks': { - afterDestroyCollection: ['onCollectionDestroy'], - }, + 'x-server-hooks': [ + { + type: 'onCollectionDestroy', + collection: 'posts', + method: 'onCollectionDestroy', + }, + ], properties: { col1: { 'x-uid': 'col1', @@ -29,9 +33,14 @@ describe('server hooks', () => { 'x-uid': 'field1', 'x-component': 'Input', 'x-collection-field': 'posts.title', - 'x-server-hooks': { - afterDestroyField: ['onFieldDestroy'], - }, + 'x-server-hooks': [ + { + type: 'onCollectionFieldDestroy', + collection: 'posts', + fields: ['title'], + method: 'onFieldDestroy', + }, + ], }, }, }, @@ -63,29 +72,7 @@ describe('server hooks', () => { uiSchemaPlugin = app.getPlugin('PluginUiSchema'); }); - it('should save uiSchemaAttrs', async () => { - const node = await uiSchemaRepository.findOne({ - filter: { - uid: 'table', - }, - }); - - // @ts-ignore - const nodeAttr = await node.getAttrs(); - expect(nodeAttr.get('collectionPath')).toEqual('posts'); - - const nodeField = await uiSchemaRepository.findOne({ - filter: { - uid: 'field1', - }, - }); - - // @ts-ignore - const nodeFieldAttr = await nodeField.getAttrs(); - expect(nodeFieldAttr.get('collectionPath')).toEqual('posts.title'); - }); - - it('should call server hooks afterFieldDestroy', async () => { + it('should call server hooks onFieldDestroy', async () => { const PostModel = await db.getRepository('collections').create({ values: { name: 'posts', @@ -106,7 +93,7 @@ describe('server hooks', () => { const serverHooks = uiSchemaPlugin.serverHooks; const hookFn = jest.fn(); - serverHooks.register('afterDestroyField', 'onFieldDestroy', hookFn); + serverHooks.register('onCollectionFieldDestroy', 'onFieldDestroy', hookFn); // destroy a field await db.getRepository('fields').destroy({ @@ -119,7 +106,7 @@ describe('server hooks', () => { expect(hookFn).toHaveBeenCalled(); }); - it('should call server hooks afterCollectionDestroy', async () => { + it('should call server hooks onCollectionDestroy', async () => { const PostModel = await db.getRepository('collections').create({ values: { name: 'posts', @@ -141,7 +128,7 @@ describe('server hooks', () => { const hookFn = jest.fn(); - serverHooks.register('afterDestroyCollection', 'onCollectionDestroy', hookFn); + serverHooks.register('onCollectionDestroy', 'onCollectionDestroy', hookFn); // destroy a field await db.getRepository('collections').destroy({ @@ -154,18 +141,21 @@ describe('server hooks', () => { expect(hookFn).toHaveBeenCalled(); }); - it('should call server hooks afterUiSchemaCreated', async () => { + it('should call server hooks onUiSchemaCreate', async () => { const menuSchema = { 'x-uid': 'menu', - 'x-server-hooks': { - afterCreateSelf: ['afterCreateMenu'], - }, + 'x-server-hooks': [ + { + type: 'onSelfCreate', + method: 'afterCreateMenu', + }, + ], }; const serverHooks = uiSchemaPlugin.serverHooks; const hookFn = jest.fn(); - serverHooks.register('afterCreateSelf', 'afterCreateMenu', hookFn); + serverHooks.register('onSelfCreate', 'afterCreateMenu', hookFn); await uiSchemaRepository.create({ values: { @@ -175,4 +165,71 @@ describe('server hooks', () => { expect(hookFn).toHaveBeenCalled(); }); + + it('should rollback after throw error', async () => { + const testSchema = { + 'x-uid': 'test', + 'x-collection-field': 'posts.title', + 'x-server-hooks': [ + { + type: 'onCollectionFieldDestroy', + collection: 'posts', + fields: ['title'], + method: 'preventDestroy', + }, + ], + }; + + await uiSchemaRepository.create({ + values: { + schema: testSchema, + }, + }); + + const PostModel = await db.getRepository('collections').create({ + values: { + name: 'posts', + }, + }); + + const fieldModel = await db.getRepository('fields').create({ + values: { + name: 'title', + type: 'string', + collectionName: 'posts', + }, + }); + + // @ts-ignore + await PostModel.migrate(); + + const serverHooks = uiSchemaPlugin.serverHooks; + + const jestFn = jest.fn(); + + serverHooks.register('onCollectionFieldDestroy', 'preventDestroy', async ({ options }) => { + await options.transaction.rollback(); + jestFn(); + throw new Error('cant delete field'); + }); + + try { + // destroy a field + await db.getRepository('fields').destroy({ + filter: { + name: 'title', + }, + individualHooks: true, + }); + } catch (e) {} + + expect(jestFn).toHaveBeenCalled(); + expect( + await db.getRepository('fields').findOne({ + filter: { + name: 'title', + }, + }), + ).toBeDefined(); + }); }); diff --git a/packages/plugin-ui-schema-storage/src/collections/uiSchemaAttr.ts b/packages/plugin-ui-schema-storage/src/collections/uiSchemaAttr.ts deleted file mode 100644 index 3445f9ed1..000000000 --- a/packages/plugin-ui-schema-storage/src/collections/uiSchemaAttr.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CollectionOptions } from '@nocobase/database'; - -export default { - name: 'uiSchemaAttrs', - autoGenId: false, - timestamps: false, - fields: [ - { - type: 'string', - name: 'collectionPath', - }, - ], -} as CollectionOptions; diff --git a/packages/plugin-ui-schema-storage/src/collections/uiSchemaServerHooks.ts b/packages/plugin-ui-schema-storage/src/collections/uiSchemaServerHooks.ts new file mode 100644 index 000000000..9fe75eb1b --- /dev/null +++ b/packages/plugin-ui-schema-storage/src/collections/uiSchemaServerHooks.ts @@ -0,0 +1,24 @@ +import { CollectionOptions } from '@nocobase/database'; + +export default { + name: 'uiSchemaServerHooks', + model: 'ServerHookModel', + // autoGenId: false, + timestamps: false, + fields: [ + { type: 'belongsTo', name: 'uiSchema', target: 'ui_schemas', foreignKey: 'uid' }, + { type: 'string', name: 'type' }, + { + type: 'string', + name: 'collection', + }, + { + type: 'array', + name: 'fields', + }, + { + type: 'string', + name: 'method', + }, + ], +} as CollectionOptions; diff --git a/packages/plugin-ui-schema-storage/src/collections/ui_schemas.ts b/packages/plugin-ui-schema-storage/src/collections/ui_schemas.ts index 41b092cae..fe12b653d 100644 --- a/packages/plugin-ui-schema-storage/src/collections/ui_schemas.ts +++ b/packages/plugin-ui-schema-storage/src/collections/ui_schemas.ts @@ -26,9 +26,9 @@ export default { name: 'name', }, { - type: 'hasOne', - name: 'attrs', - target: 'uiSchemaAttrs', + type: 'hasMany', + name: 'serverHooks', + target: 'uiSchemaServerHooks', foreignKey: 'uid', }, { diff --git a/packages/plugin-ui-schema-storage/src/model.ts b/packages/plugin-ui-schema-storage/src/model.ts index 994082cd4..3aee2ec0e 100644 --- a/packages/plugin-ui-schema-storage/src/model.ts +++ b/packages/plugin-ui-schema-storage/src/model.ts @@ -3,8 +3,10 @@ import { HookType } from './server-hooks'; class UiSchemaModel extends MagicAttributeModel { getListenServerHooks(type: HookType) { - const hooks = this.get('x-server-hooks') || {}; - return hooks[type] || []; + const hooks = this.get('x-server-hooks') || []; + return hooks.filter((hook) => { + hook.type = ''; + }); } } diff --git a/packages/plugin-ui-schema-storage/src/repository.ts b/packages/plugin-ui-schema-storage/src/repository.ts index c6c8b8d4e..872ad49f5 100644 --- a/packages/plugin-ui-schema-storage/src/repository.ts +++ b/packages/plugin-ui-schema-storage/src/repository.ts @@ -386,33 +386,22 @@ export default class UiSchemaRepository extends Repository { } private async insertSchemaRecord(name, uid, schema, transaction) { + const serverHooks = schema['x-server-hooks'] || []; + const node = await this.create({ values: { name, uid, schema, + serverHooks, }, transaction, hooks: false, }); - await this.insertSchemaAttrs(node, transaction); return node; } - private async insertSchemaAttrs(schema, transaction) { - const collectionPath = schema.get('x-collection') || schema.get('x-collection-field'); - - if (collectionPath) { - await this.database.getRepository('uiSchemaAttrs').create({ - values: { - uid: schema.get('uid'), - collectionPath: collectionPath, - }, - }); - } - } - async insertSingleNode(schema: SchemaNode, transaction: Transaction) { const db = this.database; const treeCollection = db.getCollection('ui_schema_tree_path'); diff --git a/packages/plugin-ui-schema-storage/src/server-hooks/index.ts b/packages/plugin-ui-schema-storage/src/server-hooks/index.ts index 3b8b8cde6..5f46dc299 100644 --- a/packages/plugin-ui-schema-storage/src/server-hooks/index.ts +++ b/packages/plugin-ui-schema-storage/src/server-hooks/index.ts @@ -1,7 +1,12 @@ import { Database } from '@nocobase/database'; -import { UiSchemaModel } from '../model'; +import { ServerHookModel } from './model'; -export type HookType = 'afterDestroyField' | 'afterDestroyCollection' | 'afterCreateSelf'; +export type HookType = + | 'onSchemaDestroy' + | 'onCollectionDestroy' + | 'onCollectionFieldDestroy' + | 'onAnyCollectionFieldDestroy' + | 'onSelfCreate'; export class ServerHooks { hooks = new Map>(); @@ -12,80 +17,82 @@ export class ServerHooks { listen() { this.db.on('fields.afterDestroy', async (model, options) => { - await this.afterFieldDestroy(model, options); + await this.onCollectionFieldDestroy(model, options); }); this.db.on('collections.afterDestroy', async (model, options) => { - await this.afterCollectionDestroy(model, options); + await this.onCollectionDestroy(model, options); }); - this.db.on('ui_schemas.afterCreate', async (model, options) => { - await this.afterUiSchemaCreated(model, options); + this.db.on('ui_schemas.afterCreateWithAssociations', async (model, options) => { + await this.onUiSchemaCreate(model, options); }); } - protected async afterUiSchemaCreated(uiSchemaModel, options) { + protected async onCollectionDestroy(collectionModel, options) { const { transaction } = options; - const listenHooksName = uiSchemaModel.getListenServerHooks('afterCreateSelf'); - for (const listenHookName of listenHooksName) { - const hookFunc = this.hooks.get('afterCreateSelf')?.get(listenHookName); - - await hookFunc({ - schemaInstance: uiSchemaModel, + await this.findHooksAndCall( + { + type: 'onCollectionDestroy', + collection: collectionModel.get('name'), + }, + { + collectionModel, options, - }); - } - } - - protected async afterCollectionDestroy(collectionModel, options) { - const { transaction } = options; - - const collectionPath = collectionModel.get('name'); - - const listenSchemas = (await this.db.getRepository('ui_schemas').find({ - filter: { - 'attrs.collectionPath': collectionPath, }, transaction, - })) as UiSchemaModel[]; - - for (const listenSchema of listenSchemas) { - const listenHooksName = listenSchema.getListenServerHooks('afterDestroyCollection'); - for (const listenHookName of listenHooksName) { - const hookFunc = this.hooks.get('afterDestroyCollection')?.get(listenHookName); - - await hookFunc({ - collectionInstance: collectionModel, - schemaInstance: listenSchema, - options, - }); - } - } + ); } - protected async afterFieldDestroy(fieldModel, options) { + protected async onCollectionFieldDestroy(fieldModel, options) { const { transaction } = options; + const collectionName = fieldModel.get('collectionName'); + const fieldName = fieldModel.get('name'); - const collectionPath = `${fieldModel.get('collectionName')}.${fieldModel.get('name')}`; - - const listenSchemas = (await this.db.getRepository('ui_schemas').find({ - filter: { - 'attrs.collectionPath': collectionPath, + await this.findHooksAndCall( + { + type: 'onCollectionFieldDestroy', + collection: collectionName, + 'fields.$anyOf': [fieldName], + }, + { + fieldInstance: fieldModel, + options, }, transaction, - })) as UiSchemaModel[]; + ); + } - for (const listenSchema of listenSchemas) { - const listenHooksName = listenSchema.getListenServerHooks('afterDestroyField'); - for (const listenHookName of listenHooksName) { - const hookFunc = this.hooks.get('afterDestroyField')?.get(listenHookName); + protected async onUiSchemaCreate(uiSchemaModel, options) { + const { transaction } = options; - await hookFunc({ - fieldInstance: fieldModel, - schemaInstance: listenSchema, - options, - }); + await this.findHooksAndCall( + { + type: 'onSelfCreate', + uid: uiSchemaModel.schema['x-uid'], + }, + { + uiSchemaModel, + options, + }, + transaction, + ); + } + + protected async findHooksAndCall(hooksFilter, hooksArgs, transaction) { + const hooks = (await this.db.getRepository('uiSchemaServerHooks').find({ + filter: hooksFilter, + appends: ['uiSchema'], + transaction, + })) as ServerHookModel[]; + + for (const hookRecord of hooks) { + const hoodMethodName = hookRecord.get('method') as string; + const hookFunc = this.hooks.get(hookRecord.get('type') as HookType)?.get(hoodMethodName); + + if (hookFunc) { + await hookFunc({ ...hooksArgs, schemaInstance: (hookRecord).uiSchema }); } } } diff --git a/packages/plugin-ui-schema-storage/src/server-hooks/model.ts b/packages/plugin-ui-schema-storage/src/server-hooks/model.ts new file mode 100644 index 000000000..c95f4db13 --- /dev/null +++ b/packages/plugin-ui-schema-storage/src/server-hooks/model.ts @@ -0,0 +1,3 @@ +import { Model } from '@nocobase/database'; + +export class ServerHookModel extends Model {} diff --git a/packages/plugin-ui-schema-storage/src/server.ts b/packages/plugin-ui-schema-storage/src/server.ts index 5cedd7fdb..8b17d6e98 100644 --- a/packages/plugin-ui-schema-storage/src/server.ts +++ b/packages/plugin-ui-schema-storage/src/server.ts @@ -5,6 +5,7 @@ import { uiSchemaActions } from './actions/ui-schema-action'; import UiSchemaRepository from './repository'; import { ServerHooks } from './server-hooks'; import { UiSchemaModel } from './model'; +import { ServerHookModel } from './server-hooks/model'; export default class PluginUiSchema extends Plugin { serverHooks: ServerHooks; @@ -20,7 +21,7 @@ export default class PluginUiSchema extends Plugin { this.serverHooks = new ServerHooks(db); - this.app.db.registerModels({ MagicAttributeModel, UiSchemaModel }); + this.app.db.registerModels({ MagicAttributeModel, UiSchemaModel, ServerHookModel }); this.registerRepository();