refactor: server hooks
This commit is contained in:
parent
fca6977ad1
commit
159775ff54
@ -17,9 +17,13 @@ describe('server hooks', () => {
|
|||||||
'x-uid': 'table',
|
'x-uid': 'table',
|
||||||
'x-component': 'Table',
|
'x-component': 'Table',
|
||||||
'x-collection': 'posts',
|
'x-collection': 'posts',
|
||||||
'x-server-hooks': {
|
'x-server-hooks': [
|
||||||
afterDestroyCollection: ['onCollectionDestroy'],
|
{
|
||||||
},
|
type: 'onCollectionDestroy',
|
||||||
|
collection: 'posts',
|
||||||
|
method: 'onCollectionDestroy',
|
||||||
|
},
|
||||||
|
],
|
||||||
properties: {
|
properties: {
|
||||||
col1: {
|
col1: {
|
||||||
'x-uid': 'col1',
|
'x-uid': 'col1',
|
||||||
@ -29,9 +33,14 @@ describe('server hooks', () => {
|
|||||||
'x-uid': 'field1',
|
'x-uid': 'field1',
|
||||||
'x-component': 'Input',
|
'x-component': 'Input',
|
||||||
'x-collection-field': 'posts.title',
|
'x-collection-field': 'posts.title',
|
||||||
'x-server-hooks': {
|
'x-server-hooks': [
|
||||||
afterDestroyField: ['onFieldDestroy'],
|
{
|
||||||
},
|
type: 'onCollectionFieldDestroy',
|
||||||
|
collection: 'posts',
|
||||||
|
fields: ['title'],
|
||||||
|
method: 'onFieldDestroy',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -63,29 +72,7 @@ describe('server hooks', () => {
|
|||||||
uiSchemaPlugin = app.getPlugin<PluginUiSchema>('PluginUiSchema');
|
uiSchemaPlugin = app.getPlugin<PluginUiSchema>('PluginUiSchema');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save uiSchemaAttrs', async () => {
|
it('should call server hooks onFieldDestroy', 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 () => {
|
|
||||||
const PostModel = await db.getRepository('collections').create({
|
const PostModel = await db.getRepository('collections').create({
|
||||||
values: {
|
values: {
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
@ -106,7 +93,7 @@ describe('server hooks', () => {
|
|||||||
const serverHooks = uiSchemaPlugin.serverHooks;
|
const serverHooks = uiSchemaPlugin.serverHooks;
|
||||||
const hookFn = jest.fn();
|
const hookFn = jest.fn();
|
||||||
|
|
||||||
serverHooks.register('afterDestroyField', 'onFieldDestroy', hookFn);
|
serverHooks.register('onCollectionFieldDestroy', 'onFieldDestroy', hookFn);
|
||||||
|
|
||||||
// destroy a field
|
// destroy a field
|
||||||
await db.getRepository('fields').destroy({
|
await db.getRepository('fields').destroy({
|
||||||
@ -119,7 +106,7 @@ describe('server hooks', () => {
|
|||||||
expect(hookFn).toHaveBeenCalled();
|
expect(hookFn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call server hooks afterCollectionDestroy', async () => {
|
it('should call server hooks onCollectionDestroy', async () => {
|
||||||
const PostModel = await db.getRepository('collections').create({
|
const PostModel = await db.getRepository('collections').create({
|
||||||
values: {
|
values: {
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
@ -141,7 +128,7 @@ describe('server hooks', () => {
|
|||||||
|
|
||||||
const hookFn = jest.fn();
|
const hookFn = jest.fn();
|
||||||
|
|
||||||
serverHooks.register('afterDestroyCollection', 'onCollectionDestroy', hookFn);
|
serverHooks.register('onCollectionDestroy', 'onCollectionDestroy', hookFn);
|
||||||
|
|
||||||
// destroy a field
|
// destroy a field
|
||||||
await db.getRepository('collections').destroy({
|
await db.getRepository('collections').destroy({
|
||||||
@ -154,18 +141,21 @@ describe('server hooks', () => {
|
|||||||
expect(hookFn).toHaveBeenCalled();
|
expect(hookFn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call server hooks afterUiSchemaCreated', async () => {
|
it('should call server hooks onUiSchemaCreate', async () => {
|
||||||
const menuSchema = {
|
const menuSchema = {
|
||||||
'x-uid': 'menu',
|
'x-uid': 'menu',
|
||||||
'x-server-hooks': {
|
'x-server-hooks': [
|
||||||
afterCreateSelf: ['afterCreateMenu'],
|
{
|
||||||
},
|
type: 'onSelfCreate',
|
||||||
|
method: 'afterCreateMenu',
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const serverHooks = uiSchemaPlugin.serverHooks;
|
const serverHooks = uiSchemaPlugin.serverHooks;
|
||||||
const hookFn = jest.fn();
|
const hookFn = jest.fn();
|
||||||
|
|
||||||
serverHooks.register('afterCreateSelf', 'afterCreateMenu', hookFn);
|
serverHooks.register('onSelfCreate', 'afterCreateMenu', hookFn);
|
||||||
|
|
||||||
await uiSchemaRepository.create({
|
await uiSchemaRepository.create({
|
||||||
values: {
|
values: {
|
||||||
@ -175,4 +165,71 @@ describe('server hooks', () => {
|
|||||||
|
|
||||||
expect(hookFn).toHaveBeenCalled();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { CollectionOptions } from '@nocobase/database';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'uiSchemaAttrs',
|
|
||||||
autoGenId: false,
|
|
||||||
timestamps: false,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'collectionPath',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as CollectionOptions;
|
|
@ -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;
|
@ -26,9 +26,9 @@ export default {
|
|||||||
name: 'name',
|
name: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'hasOne',
|
type: 'hasMany',
|
||||||
name: 'attrs',
|
name: 'serverHooks',
|
||||||
target: 'uiSchemaAttrs',
|
target: 'uiSchemaServerHooks',
|
||||||
foreignKey: 'uid',
|
foreignKey: 'uid',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -3,8 +3,10 @@ import { HookType } from './server-hooks';
|
|||||||
|
|
||||||
class UiSchemaModel extends MagicAttributeModel {
|
class UiSchemaModel extends MagicAttributeModel {
|
||||||
getListenServerHooks(type: HookType) {
|
getListenServerHooks(type: HookType) {
|
||||||
const hooks = this.get('x-server-hooks') || {};
|
const hooks = this.get('x-server-hooks') || [];
|
||||||
return hooks[type] || [];
|
return hooks.filter((hook) => {
|
||||||
|
hook.type = '';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,33 +386,22 @@ export default class UiSchemaRepository extends Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async insertSchemaRecord(name, uid, schema, transaction) {
|
private async insertSchemaRecord(name, uid, schema, transaction) {
|
||||||
|
const serverHooks = schema['x-server-hooks'] || [];
|
||||||
|
|
||||||
const node = await this.create({
|
const node = await this.create({
|
||||||
values: {
|
values: {
|
||||||
name,
|
name,
|
||||||
uid,
|
uid,
|
||||||
schema,
|
schema,
|
||||||
|
serverHooks,
|
||||||
},
|
},
|
||||||
transaction,
|
transaction,
|
||||||
hooks: false,
|
hooks: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.insertSchemaAttrs(node, transaction);
|
|
||||||
return node;
|
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) {
|
async insertSingleNode(schema: SchemaNode, transaction: Transaction) {
|
||||||
const db = this.database;
|
const db = this.database;
|
||||||
const treeCollection = db.getCollection('ui_schema_tree_path');
|
const treeCollection = db.getCollection('ui_schema_tree_path');
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { Database } from '@nocobase/database';
|
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 {
|
export class ServerHooks {
|
||||||
hooks = new Map<HookType, Map<string, any>>();
|
hooks = new Map<HookType, Map<string, any>>();
|
||||||
@ -12,80 +17,82 @@ export class ServerHooks {
|
|||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
this.db.on('fields.afterDestroy', async (model, options) => {
|
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) => {
|
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) => {
|
this.db.on('ui_schemas.afterCreateWithAssociations', async (model, options) => {
|
||||||
await this.afterUiSchemaCreated(model, options);
|
await this.onUiSchemaCreate(model, options);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async afterUiSchemaCreated(uiSchemaModel, options) {
|
protected async onCollectionDestroy(collectionModel, options) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
const listenHooksName = uiSchemaModel.getListenServerHooks('afterCreateSelf');
|
|
||||||
|
|
||||||
for (const listenHookName of listenHooksName) {
|
await this.findHooksAndCall(
|
||||||
const hookFunc = this.hooks.get('afterCreateSelf')?.get(listenHookName);
|
{
|
||||||
|
type: 'onCollectionDestroy',
|
||||||
await hookFunc({
|
collection: collectionModel.get('name'),
|
||||||
schemaInstance: uiSchemaModel,
|
},
|
||||||
|
{
|
||||||
|
collectionModel,
|
||||||
options,
|
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,
|
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 { transaction } = options;
|
||||||
|
const collectionName = fieldModel.get('collectionName');
|
||||||
|
const fieldName = fieldModel.get('name');
|
||||||
|
|
||||||
const collectionPath = `${fieldModel.get('collectionName')}.${fieldModel.get('name')}`;
|
await this.findHooksAndCall(
|
||||||
|
{
|
||||||
const listenSchemas = (await this.db.getRepository('ui_schemas').find({
|
type: 'onCollectionFieldDestroy',
|
||||||
filter: {
|
collection: collectionName,
|
||||||
'attrs.collectionPath': collectionPath,
|
'fields.$anyOf': [fieldName],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldInstance: fieldModel,
|
||||||
|
options,
|
||||||
},
|
},
|
||||||
transaction,
|
transaction,
|
||||||
})) as UiSchemaModel[];
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const listenSchema of listenSchemas) {
|
protected async onUiSchemaCreate(uiSchemaModel, options) {
|
||||||
const listenHooksName = listenSchema.getListenServerHooks('afterDestroyField');
|
const { transaction } = options;
|
||||||
for (const listenHookName of listenHooksName) {
|
|
||||||
const hookFunc = this.hooks.get('afterDestroyField')?.get(listenHookName);
|
|
||||||
|
|
||||||
await hookFunc({
|
await this.findHooksAndCall(
|
||||||
fieldInstance: fieldModel,
|
{
|
||||||
schemaInstance: listenSchema,
|
type: 'onSelfCreate',
|
||||||
options,
|
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: (<any>hookRecord).uiSchema });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
import { Model } from '@nocobase/database';
|
||||||
|
|
||||||
|
export class ServerHookModel extends Model {}
|
@ -5,6 +5,7 @@ import { uiSchemaActions } from './actions/ui-schema-action';
|
|||||||
import UiSchemaRepository from './repository';
|
import UiSchemaRepository from './repository';
|
||||||
import { ServerHooks } from './server-hooks';
|
import { ServerHooks } from './server-hooks';
|
||||||
import { UiSchemaModel } from './model';
|
import { UiSchemaModel } from './model';
|
||||||
|
import { ServerHookModel } from './server-hooks/model';
|
||||||
|
|
||||||
export default class PluginUiSchema extends Plugin {
|
export default class PluginUiSchema extends Plugin {
|
||||||
serverHooks: ServerHooks;
|
serverHooks: ServerHooks;
|
||||||
@ -20,7 +21,7 @@ export default class PluginUiSchema extends Plugin {
|
|||||||
|
|
||||||
this.serverHooks = new ServerHooks(db);
|
this.serverHooks = new ServerHooks(db);
|
||||||
|
|
||||||
this.app.db.registerModels({ MagicAttributeModel, UiSchemaModel });
|
this.app.db.registerModels({ MagicAttributeModel, UiSchemaModel, ServerHookModel });
|
||||||
|
|
||||||
this.registerRepository();
|
this.registerRepository();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user