Feature/action logs (#62)
* feat: add hasMany field for action logs * fix: add scope to log field * test: add cases * test: remove console.log * test: add expects * refactor: use constants for hook types * fix: missing pageInfo Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
134f72f6e8
commit
f9c4fb9df0
@ -4,6 +4,7 @@ export default {
|
|||||||
title: '示例',
|
title: '示例',
|
||||||
name: 'examples',
|
name: 'examples',
|
||||||
showInDataMenu: true,
|
showInDataMenu: true,
|
||||||
|
logging: false,
|
||||||
// createdBy: true,
|
// createdBy: true,
|
||||||
// updatedBy: true,
|
// updatedBy: true,
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -6,5 +6,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/database": "^0.3.0-alpha.0",
|
"@nocobase/database": "^0.3.0-alpha.0",
|
||||||
"@nocobase/resourcer": "^0.3.0-alpha.0"
|
"@nocobase/resourcer": "^0.3.0-alpha.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nocobase/server": "^0.3.0-alpha.0",
|
||||||
|
"@nocobase/actions": "^0.3.0-alpha.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
packages/plugin-action-logs/src/__tests__/hook.test.ts
Normal file
69
packages/plugin-action-logs/src/__tests__/hook.test.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { getApp, getAPI, getAgent } from '.';
|
||||||
|
|
||||||
|
describe('hook', () => {
|
||||||
|
let app;
|
||||||
|
let anonymousAPI;
|
||||||
|
let userAPI;
|
||||||
|
let db;
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await getApp();
|
||||||
|
db = app.database;
|
||||||
|
|
||||||
|
anonymousAPI = getAPI(getAgent(app));
|
||||||
|
|
||||||
|
const User = db.getModel('users');
|
||||||
|
user = await User.create({ nickname: 'a', token: 'token1' });
|
||||||
|
|
||||||
|
const userAgent = getAgent(app);
|
||||||
|
userAgent.set('Authorization', `Bearer ${user.token}`);
|
||||||
|
userAPI = getAPI(userAgent);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => db.close());
|
||||||
|
|
||||||
|
describe('common', () => {
|
||||||
|
it('create log', async () => {
|
||||||
|
await userAPI.resource('posts').create({
|
||||||
|
values: { title: 't1' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const p1 = await Post.findByPk(1);
|
||||||
|
|
||||||
|
const logs = await p1.getAction_logs();
|
||||||
|
|
||||||
|
expect(logs.length).toBe(1);
|
||||||
|
expect(logs[0].get()).toMatchObject({
|
||||||
|
type: 'create',
|
||||||
|
index: p1.id,
|
||||||
|
user_id: user.id,
|
||||||
|
collection_name: 'posts'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs should be scoped (no other model logs)', async () => {
|
||||||
|
await userAPI.resource('posts').create({
|
||||||
|
values: { title: 't1' }
|
||||||
|
});
|
||||||
|
await userAPI.resource('posts').update({
|
||||||
|
resourceKey: '1',
|
||||||
|
values: { title: 't11' }
|
||||||
|
});
|
||||||
|
await userAPI.resource('posts').create({
|
||||||
|
values: { title: 't2' }
|
||||||
|
});
|
||||||
|
await userAPI.resource('comments').create({
|
||||||
|
values: { content: 'c1' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const p1 = await Post.findByPk(1);
|
||||||
|
|
||||||
|
const logs = await p1.getAction_logs();
|
||||||
|
expect(logs.length).toBe(2);
|
||||||
|
expect(logs.map(item => item.collection_name)).toEqual(['posts', 'posts']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
145
packages/plugin-action-logs/src/__tests__/index.ts
Normal file
145
packages/plugin-action-logs/src/__tests__/index.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import qs from 'qs';
|
||||||
|
import supertest from 'supertest';
|
||||||
|
import bodyParser from 'koa-bodyparser';
|
||||||
|
import { Dialect } from 'sequelize';
|
||||||
|
import Database from '@nocobase/database';
|
||||||
|
import { actions, middlewares } from '@nocobase/actions';
|
||||||
|
import { Application } from '@nocobase/server';
|
||||||
|
import middleware from '@nocobase/server/src/middleware';
|
||||||
|
import plugin from '../server';
|
||||||
|
|
||||||
|
function getTestKey() {
|
||||||
|
const { id } = require.main;
|
||||||
|
const key = id
|
||||||
|
.replace(`${process.env.PWD}/packages`, '')
|
||||||
|
.replace(/src\/__tests__/g, '')
|
||||||
|
.replace('.test.ts', '')
|
||||||
|
.replace(/[^\w]/g, '_')
|
||||||
|
.replace(/_+/g, '_');
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
username: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_DATABASE,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: Number.parseInt(process.env.DB_PORT, 10),
|
||||||
|
dialect: process.env.DB_DIALECT as Dialect,
|
||||||
|
logging: process.env.DB_LOG_SQL === 'on',
|
||||||
|
sync: {
|
||||||
|
force: true,
|
||||||
|
alter: {
|
||||||
|
drop: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeDefine(columns, model) {
|
||||||
|
model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getDatabase() {
|
||||||
|
return new Database(config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getApp() {
|
||||||
|
const app = new Application({
|
||||||
|
database: config,
|
||||||
|
resourcer: {
|
||||||
|
prefix: '/api',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.resourcer.use(middlewares.associated);
|
||||||
|
app.resourcer.registerActionHandlers({...actions.associate, ...actions.common});
|
||||||
|
app.registerPlugin({
|
||||||
|
collections: path.resolve(__dirname, '../../../plugin-collections'),
|
||||||
|
users: path.resolve(__dirname, '../../../plugin-users'),
|
||||||
|
logs: plugin
|
||||||
|
});
|
||||||
|
await app.loadPlugins();
|
||||||
|
const testTables = app.database.import({
|
||||||
|
directory: path.resolve(__dirname, './tables')
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await app.database.sync();
|
||||||
|
} catch(err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const table of testTables.values()) {
|
||||||
|
// TODO(bug): 由于每个用例结束后不会清理用于测试的数据表,导致再次创建和更新
|
||||||
|
// 创建和更新里面仍会再次创建 fields,导致创建相关的数据重复,数据库报错。
|
||||||
|
await app.database.getModel('collections').import(table.getOptions(), { update: true, migrate: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
app.context.db = app.database;
|
||||||
|
app.use(bodyParser());
|
||||||
|
app.use(middleware({
|
||||||
|
prefix: '/api',
|
||||||
|
resourcer: app.resourcer,
|
||||||
|
database: app.database,
|
||||||
|
}));
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionParams {
|
||||||
|
resourceKey?: string | number;
|
||||||
|
// resourceName?: string;
|
||||||
|
// associatedName?: string;
|
||||||
|
associatedKey?: string | number;
|
||||||
|
fields?: any;
|
||||||
|
filter?: any;
|
||||||
|
values?: any;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Handler {
|
||||||
|
get: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
list: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
create: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
update: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
destroy: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
[name: string]: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Agent {
|
||||||
|
resource: (name: string) => Handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAgent(app: Application) {
|
||||||
|
return supertest.agent(app.callback());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAPI(agent) {
|
||||||
|
return {
|
||||||
|
resource(name: string): any {
|
||||||
|
return new Proxy({}, {
|
||||||
|
get(target, method: string, receiver) {
|
||||||
|
return (params: ActionParams = {}) => {
|
||||||
|
const { associatedKey, resourceKey, values = {}, filePath, ...restParams } = params;
|
||||||
|
let url = `/api/${name}`;
|
||||||
|
if (associatedKey) {
|
||||||
|
url = `/api/${name.split('.').join(`/${associatedKey}/`)}`;
|
||||||
|
}
|
||||||
|
url += `:${method as string}`;
|
||||||
|
if (resourceKey) {
|
||||||
|
url += `/${resourceKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'list':
|
||||||
|
case 'get':
|
||||||
|
return agent.get(`${url}?${qs.stringify(restParams)}`);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return agent.post(`${url}?${qs.stringify(restParams)}`).send(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
15
packages/plugin-action-logs/src/__tests__/tables/comments.ts
Normal file
15
packages/plugin-action-logs/src/__tests__/tables/comments.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { TableOptions } from "@nocobase/database";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'comments',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'content',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'post',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} as TableOptions;
|
22
packages/plugin-action-logs/src/__tests__/tables/posts.ts
Normal file
22
packages/plugin-action-logs/src/__tests__/tables/posts.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { TableOptions } from "@nocobase/database";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'posts',
|
||||||
|
// 目前默认就带了
|
||||||
|
// createdBy: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'status',
|
||||||
|
defaultValue: 'draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'comments',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as TableOptions;
|
@ -9,6 +9,7 @@ export default {
|
|||||||
updatedBy: false,
|
updatedBy: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
|
logging: false,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
interface: 'linkTo',
|
interface: 'linkTo',
|
||||||
@ -45,6 +46,13 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'list',
|
||||||
|
title: '查看'
|
||||||
|
}
|
||||||
|
],
|
||||||
views: [
|
views: [
|
||||||
{
|
{
|
||||||
type: 'table',
|
type: 'table',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { TableOptions } from '@nocobase/database';
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
import { LOG_TYPE_CREATE, LOG_TYPE_UPDATE, LOG_TYPE_DESTROY } from '../constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'action_logs',
|
name: 'action_logs',
|
||||||
@ -8,6 +9,7 @@ export default {
|
|||||||
createdBy: false,
|
createdBy: false,
|
||||||
updatedBy: false,
|
updatedBy: false,
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
|
logging: false,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
interface: 'createdAt',
|
interface: 'createdAt',
|
||||||
@ -54,9 +56,9 @@ export default {
|
|||||||
title: '操作类型',
|
title: '操作类型',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{ value: 'create', label: '新增' },
|
{ value: LOG_TYPE_CREATE, label: '新增' },
|
||||||
{ value: 'update', label: '更新' },
|
{ value: LOG_TYPE_UPDATE, label: '更新' },
|
||||||
{ value: 'destroy', label: '删除' },
|
{ value: LOG_TYPE_DESTROY, label: '删除' },
|
||||||
],
|
],
|
||||||
component: {
|
component: {
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
@ -83,6 +85,11 @@ export default {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'filter',
|
||||||
|
name: 'filter',
|
||||||
|
title: '筛选'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'list',
|
name: 'list',
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import { extend } from '@nocobase/database';
|
||||||
|
|
||||||
|
// TODO(bug): collections 表创建关联字段有问题
|
||||||
|
export default extend({
|
||||||
|
name: 'collections',
|
||||||
|
logging: false
|
||||||
|
});
|
3
packages/plugin-action-logs/src/constants.ts
Normal file
3
packages/plugin-action-logs/src/constants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const LOG_TYPE_CREATE = 'create';
|
||||||
|
export const LOG_TYPE_UPDATE = 'update';
|
||||||
|
export const LOG_TYPE_DESTROY = 'destroy';
|
@ -1,52 +1,51 @@
|
|||||||
import { Field } from '@nocobase/database';
|
import { Field } from '@nocobase/database';
|
||||||
|
import { LOG_TYPE_CREATE } from '../constants';
|
||||||
|
|
||||||
export default async function(model, options) {
|
export default async function(model, options) {
|
||||||
if (!options.context) {
|
if (!options.context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { database: db } = model;
|
const { database: db } = model;
|
||||||
const { context, transaction = await db.sequelize.transaction() } = options;
|
const { context: { state }, transaction = await db.sequelize.transaction() } = options;
|
||||||
const {
|
|
||||||
state,
|
|
||||||
action: {
|
|
||||||
params: {
|
|
||||||
actionName,
|
|
||||||
resourceName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} = context;
|
|
||||||
const ActionLog = db.getModel('action_logs');
|
const ActionLog = db.getModel('action_logs');
|
||||||
// 创建操作记录
|
// 创建操作记录
|
||||||
const log = await ActionLog.create({
|
const log = await ActionLog.create({
|
||||||
type: actionName,
|
type: LOG_TYPE_CREATE,
|
||||||
collection_name: model.constructor.name,
|
collection_name: model.constructor.name,
|
||||||
index: model.get(model.constructor.primaryKeyAttribute),
|
index: model.get(model.constructor.primaryKeyAttribute),
|
||||||
created_at: model.get('created_at')
|
created_at: model.get('created_at')
|
||||||
}, {
|
}, {
|
||||||
transaction
|
transaction
|
||||||
});
|
});
|
||||||
|
if (state.currentUser) {
|
||||||
|
// TODO(bug): state.currentUser 不是 belongsTo field 的 target 实例
|
||||||
|
// Sequelize 会另外创建一个 Model 的继承类,直传 instance 因为无法匹配类会当做 id 造成类型错误
|
||||||
|
// await log.setUser(state.currentUser, { transaction });
|
||||||
|
await log.updateAssociations({ user: state.currentUser.id }, {
|
||||||
|
transaction
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const fields = db.getTable(model.constructor.name).getFields();
|
const fields = db.getTable(model.constructor.name).getFields();
|
||||||
const fieldsList = Array.from(fields.values());
|
const fieldsList = Array.from(fields.values());
|
||||||
const changes = [];
|
const changes = [];
|
||||||
model.changed().forEach((key: string) => {
|
const changed = model.changed();
|
||||||
const field = fields.get(key) || fieldsList.find((item: Field) => item.options.field === key);
|
if (changed) {
|
||||||
if (field) {
|
changed.forEach((key: string) => {
|
||||||
changes.push({
|
const field = fields.get(key) || fieldsList.find((item: Field) => item.options.field === key);
|
||||||
field: field.options,
|
if (field) {
|
||||||
after: model.get(key)
|
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({
|
||||||
await log.updateAssociations({
|
changes
|
||||||
...(state.currentUser ? { user: state.currentUser.id } : {}),
|
}, {
|
||||||
changes
|
transaction
|
||||||
}, {
|
});
|
||||||
transaction
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!options.transaction) {
|
if (!options.transaction) {
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
|
@ -1,31 +1,28 @@
|
|||||||
import { Field } from '@nocobase/database';
|
import { Field } from '@nocobase/database';
|
||||||
|
import { LOG_TYPE_DESTROY } from '../constants';
|
||||||
|
|
||||||
export default async function(model, options) {
|
export default async function(model, options) {
|
||||||
if (!options.context) {
|
if (!options.context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { database: db } = model;
|
const { database: db } = model;
|
||||||
const { context, transaction = await db.sequelize.transaction() } = options;
|
const { context: { state }, transaction = await db.sequelize.transaction() } = options;
|
||||||
const {
|
|
||||||
state,
|
|
||||||
action: {
|
|
||||||
params: {
|
|
||||||
actionName,
|
|
||||||
resourceName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} = context;
|
|
||||||
const ActionLog = db.getModel('action_logs');
|
const ActionLog = db.getModel('action_logs');
|
||||||
// 创建操作记录
|
// 创建操作记录
|
||||||
const log = await ActionLog.create({
|
const log = await ActionLog.create({
|
||||||
// user_id: state.currentUser ? state.currentUser.id : null,
|
// user_id: state.currentUser ? state.currentUser.id : null,
|
||||||
type: actionName,
|
type: LOG_TYPE_DESTROY,
|
||||||
collection_name: model.constructor.name,
|
collection_name: model.constructor.name,
|
||||||
index: model.get(model.constructor.primaryKeyAttribute),
|
index: model.get(model.constructor.primaryKeyAttribute),
|
||||||
// created_at: model.get('created_at')
|
// created_at: model.get('created_at')
|
||||||
}, {
|
}, {
|
||||||
transaction
|
transaction
|
||||||
});
|
});
|
||||||
|
if (state.currentUser) {
|
||||||
|
await log.updateAssociations({ user: state.currentUser.id }, {
|
||||||
|
transaction
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const fields = db.getTable(model.constructor.name).getFields();
|
const fields = db.getTable(model.constructor.name).getFields();
|
||||||
const fieldsList = Array.from(fields.values());
|
const fieldsList = Array.from(fields.values());
|
||||||
@ -41,7 +38,6 @@ export default async function(model, options) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await log.updateAssociations({
|
await log.updateAssociations({
|
||||||
...(state.currentUser ? { user: state.currentUser.id } : {}),
|
|
||||||
changes
|
changes
|
||||||
}, {
|
}, {
|
||||||
transaction
|
transaction
|
||||||
|
@ -1,58 +1,50 @@
|
|||||||
import { Field } from '@nocobase/database';
|
import { Field } from '@nocobase/database';
|
||||||
|
import { LOG_TYPE_UPDATE } from '../constants';
|
||||||
|
|
||||||
export default async function(model, options) {
|
export default async function(model, options) {
|
||||||
if (!options.context) {
|
if (!options.context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { database: db } = model;
|
const { database: db } = model;
|
||||||
const { context, transaction = await db.sequelize.transaction() } = options;
|
const { context: { state }, transaction = await db.sequelize.transaction() } = options;
|
||||||
const {
|
|
||||||
state,
|
|
||||||
action: {
|
|
||||||
params: {
|
|
||||||
actionName,
|
|
||||||
resourceName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} = context;
|
|
||||||
const ActionLog = db.getModel('action_logs');
|
const ActionLog = db.getModel('action_logs');
|
||||||
|
|
||||||
const fields = db.getTable(model.constructor.name).getFields();
|
const fields = db.getTable(model.constructor.name).getFields();
|
||||||
const fieldsList = Array.from(fields.values());
|
const fieldsList = Array.from(fields.values());
|
||||||
const changes = [];
|
const changes = [];
|
||||||
|
const changed = model.changed();
|
||||||
|
if (changed) {
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
model.changed().forEach((key: string) => {
|
if (changes.length) {
|
||||||
const field = fields.get(key) || fieldsList.find((item: Field) => item.options.field === key);
|
// 创建操作记录
|
||||||
if (field && field.options.type !== 'formula') {
|
const log = await ActionLog.create({
|
||||||
changes.push({
|
type: LOG_TYPE_UPDATE,
|
||||||
field: field.options,
|
collection_name: model.constructor.name,
|
||||||
after: model.get(key),
|
index: model.get(model.constructor.primaryKeyAttribute),
|
||||||
before: model.previous(key)
|
created_at: model.get('updated_at')
|
||||||
|
}, {
|
||||||
|
transaction
|
||||||
|
});
|
||||||
|
|
||||||
|
await log.updateAssociations({
|
||||||
|
...(state.currentUser ? { user: state.currentUser.id } : {}),
|
||||||
|
changes
|
||||||
|
}, {
|
||||||
|
transaction
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
if (!options.transaction) {
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
import { addAll } from './hooks';
|
import { addAll } from './hooks';
|
||||||
|
|
||||||
@ -10,11 +11,46 @@ export default async function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 为所有的表都加上日志的 hooks
|
// 为所有的表都加上日志的 hooks
|
||||||
database.addHook('afterTableInit', function (table) {
|
database.addHook('afterTableInit', (table) => {
|
||||||
if (['action_logs', 'action_changes'].includes(table.options.name)) {
|
if (table.options.logging === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const Model = database.getModel(table.options.name);
|
addAll(database.getModel(table.options.name));
|
||||||
addAll(Model);
|
});
|
||||||
|
|
||||||
|
const Collection = database.getModel('collections');
|
||||||
|
Collection.addHook('afterCreate', async (model, options) => {
|
||||||
|
if (model.get('logging') === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { transaction = await model.sequelize.transaction() } = options;
|
||||||
|
|
||||||
|
const exists = await model.countFields({
|
||||||
|
where: {
|
||||||
|
type: { [Op.iLike]: 'hasMany' },
|
||||||
|
name: 'action_logs'
|
||||||
|
},
|
||||||
|
transaction
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
await model.createField({
|
||||||
|
interface: 'linkTo',
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'action_logs',
|
||||||
|
target: 'action_logs',
|
||||||
|
title: '数据动态',
|
||||||
|
foreignKey: 'index',
|
||||||
|
scope: {
|
||||||
|
collection_name: model.get('name')
|
||||||
|
},
|
||||||
|
constraints: false
|
||||||
|
}, { transaction });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user