Feature/action logs (#61)
* feat: add action logs plugin * feat: add afterUpdate/afterDestroy hooks for logs * 子表格细节改进 * fix: subtable * activity * bugfix Co-authored-by: mytharcher <mytharcher@gmail.com>
This commit is contained in:
		
							parent
							
								
									4ed21682ac
								
							
						
					
					
						commit
						1ea08e62b6
					
				| @ -4,6 +4,7 @@ | ||||
|   "scripts": { | ||||
|     "start": "cd packages/app && npm start", | ||||
|     "db-migrate": "cd packages/app && npm run db-migrate", | ||||
|     "start:app:server": "cd packages/app && nodemon", | ||||
|     "start-server": "yarn nodemon", | ||||
|     "bootstrap": "lerna bootstrap --no-ci", | ||||
|     "build": "npm run build-father-build && node packages/father-build/bin/father-build.js", | ||||
|  | ||||
| @ -29,6 +29,7 @@ | ||||
|     "@nocobase/client": "^0.3.0-alpha.0", | ||||
|     "@nocobase/database": "^0.3.0-alpha.0", | ||||
|     "@nocobase/father-build": "^0.3.0-alpha.0", | ||||
|     "@nocobase/plugin-action-logs": "^0.3.0-alpha.0", | ||||
|     "@nocobase/plugin-collections": "^0.3.0-alpha.0", | ||||
|     "@nocobase/plugin-pages": "^0.3.0-alpha.0", | ||||
|     "@nocobase/server": "^0.3.0-alpha.0", | ||||
|  | ||||
| @ -62,6 +62,7 @@ api.resourcer.registerActionHandlers({...actions.common, ...actions.associate}); | ||||
| // });
 | ||||
| 
 | ||||
| api.registerPlugin('plugin-collections', [path.resolve(__dirname, '../../../plugin-collections'), {}]); | ||||
| api.registerPlugin('plugin-action-logs', [path.resolve(__dirname, '../../../plugin-action-logs'), {}]); | ||||
| api.registerPlugin('plugin-pages', [path.resolve(__dirname, '../../../plugin-pages'), {}]); | ||||
| api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-users'), {}]); | ||||
| api.registerPlugin('plugin-file-manager', [path.resolve(__dirname, '../../../plugin-file-manager'), {}]); | ||||
|  | ||||
							
								
								
									
										42
									
								
								packages/app/src/api/migrations/create-action-logs-table.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/app/src/api/migrations/create-action-logs-table.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| import api from '../app'; | ||||
| import Database from '@nocobase/database'; | ||||
| 
 | ||||
| (async () => { | ||||
|   await api.loadPlugins(); | ||||
|   const database: Database = api.database; | ||||
|   await api.database.sync({ | ||||
|     tables: ['actions_scopes', 'action_logs', 'action_changes'], | ||||
|   }); | ||||
| 
 | ||||
|   const [Collection, Page] = database.getModels(['collections', 'pages']); | ||||
| 
 | ||||
|   const tables = database.getTables(['actions_scopes', 'action_logs', 'action_changes']); | ||||
| 
 | ||||
|   for (let table of tables) { | ||||
|     console.log(table.getName()); | ||||
|     await Collection.import(table.getOptions(), { update: true, migrate: false }); | ||||
|   } | ||||
| 
 | ||||
|   await Page.import({ | ||||
|     title: '动态', | ||||
|     type: 'layout', | ||||
|     path: '/activity', | ||||
|     icon: 'NotificationOutlined', | ||||
|     template: 'SideMenuLayout', | ||||
|     sort: 85, | ||||
|     showInMenu: true, | ||||
|     parent_id: 1, | ||||
|     children: [ | ||||
|       { | ||||
|         title: '操作记录', | ||||
|         type: 'collection', | ||||
|         path: '/activity/logs', | ||||
|         icon: 'HistoryOutlined', | ||||
|         template: 'collection', | ||||
|         collection: 'action_logs', | ||||
|         sort: 80, | ||||
|         showInMenu: true, | ||||
|       }, | ||||
|     ] | ||||
|   }); | ||||
| })(); | ||||
| @ -74,6 +74,27 @@ const data = [ | ||||
|           }, | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         title: '动态', | ||||
|         type: 'layout', | ||||
|         path: '/activity', | ||||
|         icon: 'NotificationOutlined', | ||||
|         template: 'SideMenuLayout', | ||||
|         sort: 85, | ||||
|         showInMenu: true, | ||||
|         children: [ | ||||
|           { | ||||
|             title: '操作记录', | ||||
|             type: 'collection', | ||||
|             path: '/activity/logs', | ||||
|             icon: 'HistoryOutlined', | ||||
|             template: 'collection', | ||||
|             collection: 'action_logs', | ||||
|             sort: 80, | ||||
|             showInMenu: true, | ||||
|           }, | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         title: '配置', | ||||
|         type: 'layout', | ||||
|  | ||||
| @ -8,6 +8,8 @@ import { | ||||
|   SettingOutlined, | ||||
|   TableOutlined, | ||||
|   MenuOutlined, | ||||
|   HistoryOutlined, | ||||
|   NotificationOutlined, | ||||
| } from '@ant-design/icons'; | ||||
| 
 | ||||
| export const IconFont = createFromIconfontCN({ | ||||
| @ -36,6 +38,7 @@ export function registerIcons(components) { | ||||
| } | ||||
| 
 | ||||
| registerIcons({ | ||||
|   HistoryOutlined, | ||||
|   MenuOutlined, | ||||
|   TableOutlined, | ||||
|   SettingOutlined, | ||||
| @ -43,6 +46,7 @@ registerIcons({ | ||||
|   UserOutlined, | ||||
|   DatabaseOutlined, | ||||
|   DashboardOutlined, | ||||
|   NotificationOutlined, | ||||
| }); | ||||
| 
 | ||||
| interface IconProps { | ||||
|  | ||||
| @ -361,6 +361,20 @@ export function AttachmentFieldItem(props: any) { | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function LogField(props) { | ||||
|   const { value = {} } = props; | ||||
|   return ( | ||||
|     <div>{value.title||value.name}</div> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function LogFieldValue(props) { | ||||
|   const { value, schema, data } = props; | ||||
|   return ( | ||||
|     <div>{value}</div> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| registerFieldComponents({ | ||||
|   string: StringField, | ||||
|   textarea: TextareaField, | ||||
| @ -380,10 +394,12 @@ registerFieldComponents({ | ||||
|   subTable: SubTableField, | ||||
|   linkTo: LinkToField, | ||||
|   attachment: AttachmentField, | ||||
|   'logs.field': LogField, | ||||
|   'logs.fieldValue': LogFieldValue, | ||||
| }); | ||||
| 
 | ||||
| export default function Field(props: any) { | ||||
|   const { schema = {} } = props; | ||||
|   const Component = getFieldComponent(schema.interface); | ||||
|   const Component = getFieldComponent(schema.interface||get(schema, 'component.type')); | ||||
|   return <Component {...props}/>; | ||||
| } | ||||
|  | ||||
							
								
								
									
										10
									
								
								packages/plugin-action-logs/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/plugin-action-logs/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| { | ||||
|   "name": "@nocobase/plugin-action-logs", | ||||
|   "version": "0.3.0-alpha.0", | ||||
|   "main": "lib/index.js", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "@nocobase/database": "^0.3.0-alpha.0", | ||||
|     "@nocobase/resourcer": "^0.3.0-alpha.0" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,57 @@ | ||||
| import { TableOptions } from '@nocobase/database'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'action_changes', | ||||
|   title: '变动值', | ||||
|   developerMode: true, | ||||
|   internal: true, | ||||
|   createdBy: false, | ||||
|   updatedBy: false, | ||||
|   createdAt: false, | ||||
|   updatedAt: false, | ||||
|   fields: [ | ||||
|     { | ||||
|       interface: 'linkTo', | ||||
|       type: 'belongsTo', | ||||
|       name: 'log', | ||||
|       target: 'action_logs', | ||||
|       title: '所属操作' | ||||
|     }, | ||||
|     { | ||||
|       type: 'jsonb', | ||||
|       name: 'field', | ||||
|       title: '字段信息', | ||||
|       component: { | ||||
|         type: 'logs.field', | ||||
|         showInTable: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       type: 'jsonb', | ||||
|       name: 'before', | ||||
|       title: '操作前', | ||||
|       component: { | ||||
|         type: 'logs.fieldValue', | ||||
|         showInTable: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       type: 'jsonb', | ||||
|       name: 'after', | ||||
|       title: '操作后', | ||||
|       component: { | ||||
|         type: 'logs.fieldValue', | ||||
|         showInTable: true, | ||||
|       }, | ||||
|     } | ||||
|   ], | ||||
|   views: [ | ||||
|     { | ||||
|       type: 'table', | ||||
|       name: 'table', | ||||
|       title: '列表', | ||||
|       template: 'Table', | ||||
|       default: true, | ||||
|     }, | ||||
|   ], | ||||
| } as TableOptions; | ||||
							
								
								
									
										108
									
								
								packages/plugin-action-logs/src/collections/action_logs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								packages/plugin-action-logs/src/collections/action_logs.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| import { TableOptions } from '@nocobase/database'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'action_logs', | ||||
|   title: '操作记录', | ||||
|   developerMode: true, | ||||
|   internal: true, | ||||
|   createdBy: false, | ||||
|   updatedBy: false, | ||||
|   updatedAt: false, | ||||
|   fields: [ | ||||
|     { | ||||
|       interface: 'createdAt', | ||||
|       name: 'created_at', | ||||
|       type: 'date', | ||||
|       title: '操作时间', | ||||
|       showTime: true, | ||||
|       component: { | ||||
|         showInTable: true, | ||||
|         showInDetail: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       interface: 'linkTo', | ||||
|       type: 'belongsTo', | ||||
|       name: 'user', | ||||
|       target: 'users', | ||||
|       title: '操作用户', | ||||
|       appends: true, | ||||
|       labelField: 'nickname', | ||||
|       component: { | ||||
|         showInTable: true, | ||||
|         showInDetail: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       interface: 'linkTo', | ||||
|       type: 'belongsTo', | ||||
|       name: 'collection', | ||||
|       target: 'collections', | ||||
|       title: '数据表', | ||||
|       targetKey: 'name', | ||||
|       appends: true, | ||||
|       labelField: 'title', | ||||
|       component: { | ||||
|         showInTable: true, | ||||
|         showInDetail: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       interface: 'select', | ||||
|       type: 'string', | ||||
|       name: 'type', | ||||
|       title: '操作类型', | ||||
|       filterable: true, | ||||
|       dataSource: [ | ||||
|         { value: 'create', label: '新增' }, | ||||
|         { value: 'update', label: '更新' }, | ||||
|         { value: 'destroy', label: '删除' }, | ||||
|       ], | ||||
|       component: { | ||||
|         showInTable: true, | ||||
|         showInDetail: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       type: 'integer', | ||||
|       name: 'index', | ||||
|       title: '对象索引', | ||||
|       component: { | ||||
|         showInTable: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       interface: 'subTable', | ||||
|       type: 'hasMany', | ||||
|       name: 'changes', | ||||
|       target: 'action_changes', | ||||
|       title: '数据变动', | ||||
|       component: { | ||||
|         showInDetail: true, | ||||
|       }, | ||||
|     } | ||||
|   ], | ||||
|   actions: [ | ||||
|     { | ||||
|       type: 'list', | ||||
|       name: 'list', | ||||
|       title: '查看', | ||||
|       sort: '-created_at' | ||||
|     }, | ||||
|   ], | ||||
|   views: [ | ||||
|     { | ||||
|       type: 'table', | ||||
|       name: 'table', | ||||
|       title: '列表', | ||||
|       template: 'Table', | ||||
|       default: true | ||||
|     }, | ||||
|     { | ||||
|       type: 'details', | ||||
|       name: 'details', | ||||
|       title: '详情', | ||||
|       template: 'Details', | ||||
|     }, | ||||
|   ], | ||||
| } as TableOptions; | ||||
							
								
								
									
										54
									
								
								packages/plugin-action-logs/src/hooks/after-create.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								packages/plugin-action-logs/src/hooks/after-create.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| import { Field } from '@nocobase/database'; | ||||
| 
 | ||||
| export default async function(model, options) { | ||||
|   if (!options.context) { | ||||
|     return; | ||||
|   } | ||||
|   const { database: db } = model; | ||||
|   const { context, transaction = await db.sequelize.transaction() } = options; | ||||
|   const { | ||||
|     state, | ||||
|     action: { | ||||
|       params: { | ||||
|         actionName, | ||||
|         resourceName, | ||||
|       } | ||||
|     } | ||||
|   } = context; | ||||
|   const ActionLog = db.getModel('action_logs'); | ||||
|   // 创建操作记录
 | ||||
|   const log = await ActionLog.create({ | ||||
|     type: actionName, | ||||
|     collection_name: model.constructor.name, | ||||
|     index: model.get(model.constructor.primaryKeyAttribute), | ||||
|     created_at: model.get('created_at') | ||||
|   }, { | ||||
|     transaction | ||||
|   }); | ||||
| 
 | ||||
|   const fields = db.getTable(model.constructor.name).getFields(); | ||||
|   const fieldsList = Array.from(fields.values()); | ||||
|   const changes = []; | ||||
|   model.changed().forEach((key: string) => { | ||||
|     const field = fields.get(key) || fieldsList.find((item: Field) => item.options.field === key); | ||||
|     if (field) { | ||||
|       changes.push({ | ||||
|         field: field.options, | ||||
|         after: model.get(key) | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
|   // TODO(bug): state.currentUser 不是 belongsTo field 的 target 实例
 | ||||
|   // Sequelize 会另外创建一个 Model 的继承类,无法直传 instance
 | ||||
|   // await log.setUser(state.currentUser, { transaction });
 | ||||
|   await log.updateAssociations({ | ||||
|     ...(state.currentUser ? { user: state.currentUser.id } : {}), | ||||
|     changes | ||||
|   }, { | ||||
|     transaction | ||||
|   }); | ||||
| 
 | ||||
|   if (!options.transaction) { | ||||
|     await transaction.commit(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										53
									
								
								packages/plugin-action-logs/src/hooks/after-destroy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								packages/plugin-action-logs/src/hooks/after-destroy.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| import { Field } from '@nocobase/database'; | ||||
| 
 | ||||
| export default async function(model, options) { | ||||
|   if (!options.context) { | ||||
|     return; | ||||
|   } | ||||
|   const { database: db } = model; | ||||
|   const { context, transaction = await db.sequelize.transaction() } = options; | ||||
|   const { | ||||
|     state, | ||||
|     action: { | ||||
|       params: { | ||||
|         actionName, | ||||
|         resourceName, | ||||
|       } | ||||
|     } | ||||
|   } = context; | ||||
|   const ActionLog = db.getModel('action_logs'); | ||||
|   // 创建操作记录
 | ||||
|   const log = await ActionLog.create({ | ||||
|     // user_id: state.currentUser ? state.currentUser.id : null,
 | ||||
|     type: actionName, | ||||
|     collection_name: model.constructor.name, | ||||
|     index: model.get(model.constructor.primaryKeyAttribute), | ||||
|     // created_at: model.get('created_at')
 | ||||
|   }, { | ||||
|     transaction | ||||
|   }); | ||||
| 
 | ||||
|   const fields = db.getTable(model.constructor.name).getFields(); | ||||
|   const fieldsList = Array.from(fields.values()); | ||||
|   const changes = []; | ||||
|   Object.keys(model.get()).forEach((key: string) => { | ||||
|     const field = fields.get(key) || fieldsList.find((item: Field) => item.options.field === key); | ||||
|     if (field) { | ||||
|       changes.push({ | ||||
|         field: field.options, | ||||
|         before: model.get(key) | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   await log.updateAssociations({ | ||||
|     ...(state.currentUser ? { user: state.currentUser.id } : {}), | ||||
|     changes | ||||
|   }, { | ||||
|     transaction | ||||
|   }); | ||||
| 
 | ||||
|   if (!options.transaction) { | ||||
|     await transaction.commit(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										59
									
								
								packages/plugin-action-logs/src/hooks/after-update.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								packages/plugin-action-logs/src/hooks/after-update.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| import { Field } from '@nocobase/database'; | ||||
| 
 | ||||
| export default async function(model, options) { | ||||
|   if (!options.context) { | ||||
|     return; | ||||
|   } | ||||
|   const { database: db } = model; | ||||
|   const { context, transaction = await db.sequelize.transaction() } = options; | ||||
|   const { | ||||
|     state, | ||||
|     action: { | ||||
|       params: { | ||||
|         actionName, | ||||
|         resourceName, | ||||
|       } | ||||
|     } | ||||
|   } = context; | ||||
|   const ActionLog = db.getModel('action_logs'); | ||||
| 
 | ||||
|   const fields = db.getTable(model.constructor.name).getFields(); | ||||
|   const fieldsList = Array.from(fields.values()); | ||||
|   const changes = []; | ||||
| 
 | ||||
|   model.changed().forEach((key: string) => { | ||||
|     const field = fields.get(key) || fieldsList.find((item: Field) => item.options.field === key); | ||||
|     if (field && field.options.type !== 'formula') { | ||||
|       changes.push({ | ||||
|         field: field.options, | ||||
|         after: model.get(key), | ||||
|         before: model.previous(key) | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   if (changes.length === 0) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // 创建操作记录
 | ||||
|   const log = await ActionLog.create({ | ||||
|     type: actionName, | ||||
|     collection_name: model.constructor.name, | ||||
|     index: model.get(model.constructor.primaryKeyAttribute), | ||||
|     created_at: model.get('updated_at') | ||||
|   }, { | ||||
|     transaction | ||||
|   }); | ||||
| 
 | ||||
|   await log.updateAssociations({ | ||||
|     ...(state.currentUser ? { user: state.currentUser.id } : {}), | ||||
|     changes | ||||
|   }, { | ||||
|     transaction | ||||
|   }); | ||||
| 
 | ||||
|   if (!options.transaction) { | ||||
|     await transaction.commit(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										10
									
								
								packages/plugin-action-logs/src/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/plugin-action-logs/src/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| import afterCreate from './after-create'; | ||||
| import afterUpdate from './after-update'; | ||||
| import afterDestroy from './after-destroy'; | ||||
| 
 | ||||
| export function addAll(Model) { | ||||
|   Model.addHook('afterCreate', afterCreate); | ||||
|   // Model.addHook('afterBulkCreate', hooks.afterBulkCreate);
 | ||||
|   Model.addHook('afterUpdate', afterUpdate); | ||||
|   Model.addHook('afterDestroy', afterDestroy); | ||||
| } | ||||
							
								
								
									
										20
									
								
								packages/plugin-action-logs/src/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/plugin-action-logs/src/server.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| import path from 'path'; | ||||
| 
 | ||||
| import { addAll } from './hooks'; | ||||
| 
 | ||||
| export default async function() { | ||||
|   const { database } = this; | ||||
| 
 | ||||
|   database.import({ | ||||
|     directory: path.resolve(__dirname, 'collections'), | ||||
|   }); | ||||
| 
 | ||||
|   // 为所有的表都加上日志的 hooks
 | ||||
|   database.addHook('afterTableInit', function (table) { | ||||
|     if (['action_logs', 'action_changes'].includes(table.options.name)) { | ||||
|       return; | ||||
|     } | ||||
|     const Model = database.getModel(table.options.name); | ||||
|     addAll(Model); | ||||
|   }); | ||||
| } | ||||
| @ -157,6 +157,7 @@ const transforms = { | ||||
|     return schema; | ||||
|   }, | ||||
|   details: async (fields: Model[], context?: any) => { | ||||
|     const [Field] = context.db.getModels(['fields']) as ModelCtor<Model>[]; | ||||
|     const arr = []; | ||||
|     for (const field of fields) { | ||||
|       if (!get(field.component, 'showInDetail')) { | ||||
| @ -167,10 +168,17 @@ const transforms = { | ||||
|       } | ||||
|       const props = {}; | ||||
|       if (field.get('interface') === 'subTable') { | ||||
|         const children = await field.getChildren({ | ||||
|           order: [['sort', 'asc']], | ||||
|         }); | ||||
|         props['children'] = children.map(child => ({...child.toJSON(), dataIndex: child.name.split('.')})) | ||||
|         const children = await Field.findAll(Field.parseApiJson({ | ||||
|           filter: { | ||||
|             collection_name: field.get('target'), | ||||
|           }, | ||||
|           perPage: -1, | ||||
|           sort: ['sort'], | ||||
|         })); | ||||
|         // const children = await field.getChildren({
 | ||||
|         //   order: [['sort', 'asc']],
 | ||||
|         // });
 | ||||
|         props['children'] = children.filter(item => item.get('component.showInTable')).map(child => ({...child.toJSON(), dataIndex: child.name.split('.')})) | ||||
|       } | ||||
|       arr.push({ | ||||
|         ...field.toJSON(), | ||||
|  | ||||
| @ -35,7 +35,6 @@ export class Permissions { | ||||
|     }); | ||||
| 
 | ||||
|     database.getModel('collections').addHook('afterCreate', async (model: any, options) => { | ||||
|       // console.log('plugin-permissions hook');
 | ||||
|       await model.updateAssociations({ | ||||
|         scopes: [ | ||||
|           { | ||||
|  | ||||
| @ -18,10 +18,6 @@ export default async function (options = {}) { | ||||
| 
 | ||||
|   registerFields(fields); | ||||
| 
 | ||||
|   database.import({ | ||||
|     directory: path.resolve(__dirname, 'collections'), | ||||
|   }); | ||||
| 
 | ||||
|   database.addHook('afterTableInit', (table) => { | ||||
|     let { createdBy, updatedBy, internal } = table.getOptions(); | ||||
|     // 非内置表,默认创建 createdBy 和 updatedBy
 | ||||
| @ -39,6 +35,10 @@ export default async function (options = {}) { | ||||
|       .map(type => table.addField(makeOptions(type, fieldsToMake[type]))); | ||||
|   }); | ||||
| 
 | ||||
|   database.import({ | ||||
|     directory: path.resolve(__dirname, 'collections'), | ||||
|   }); | ||||
| 
 | ||||
|   resourcer.registerActionHandlers({ | ||||
|     'users:login': login, | ||||
|     'users:register': register, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user