Skip to content

Commit

Permalink
remove all hooks and attach manual functions to models for auditing
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Tong committed Nov 10, 2017
1 parent 87d39e5 commit de8e822
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 80 deletions.
10 changes: 8 additions & 2 deletions instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ const providers = [

## Making a model auditable

Add the following to your models `boot` method:
Add the following to your model's `boot` method:

```js
class MyClass extends Model {
class MyModel extends Model {
boot () {
super.boot()
this.addTrait('@provider:Auditable')
}
}
```

This you can start using as follows:

```js
await MyModel.createWithAudit(/** model data **/)
```
9 changes: 6 additions & 3 deletions providers/AuditableProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ const {ServiceProvider} = require('@adonisjs/fold')

class AuditableProvider extends ServiceProvider {
register () {
const Auditable = require('../src/Traits/Auditable')
this.app.bind('Adonis/Traits/Auditable', () => new Auditable)
this.app.singleton('Adonis/Traits/Auditable', () => {
// const Config = this.app.use('Adonis/Src/Config')
const Auditable = require('../src/Traits/Auditable')
return new Auditable()
})
this.app.alias('Adonis/Traits/Auditable', 'Auditable')
}

boot () {
const Context = this.app.use('Adonis/Src/HttpContext')
const Auditable = this.app.use('Auditable')

// add ctx to auditable
// add ctx to datagrid
Context.onReady(ctx => {
Auditable.ctx = ctx
})
Expand Down
143 changes: 95 additions & 48 deletions src/Traits/Auditable.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,84 +4,131 @@ const _ = require('lodash')
const Audit = use('App/Models/Audit')

class Auditable {
constructor () {
this._oldData = {}
}

register (Model) {
// Model.addHook('beforeUpdate', this._beforeUpdate.bind(this))
// Model.addHook('afterUpdate', this._afterUpdate.bind(this))
// Model.addHook('afterCreate', this._afterCreate.bind(this))
// Model.addHook('afterDelete', this._afterDelete.bind(this))
Model.createWithAudit = createWithAudit(this.ctx)
Model.prototype.updateWithAudit = updateWithAudit(this.ctx)
Model.prototype.deleteWithAudit = deleteWithAudit(this.ctx)
}
}

async _afterCreate (model) {
const event = Audit.EVENT_CREATE
const newModel = (await this.find(model.primaryKeyValue))
/**
* Create with audit
*
* @param auth
* @param request
* @returns {*}
*/
function createWithAudit ({request, auth}) {
return async function (data) {
const result = await this.create(data)
const newModel = (await this.find(result.primaryKeyValue))
const auditable = newModel.constructor.name
const auditableId = newModel.id
const newData = newModel.$attributes

// save audit
await this._createAudit(event, auditable, auditableId, null, newData)
}
await createAudit(Audit.events.CREATE, {request, auth}, auditable, auditableId, null, newData)

_beforeUpdate (model) {
this._oldData[model] = model.$originalAttributes
return result
}
}

async _afterUpdate (model) {
const event = this.ctx.request.method() === 'PATCH' ? Audit.EVENT_PATCH : Audit.EVENT_UPDATE
const auditable = model.constructor.name
const auditableId = model.id
const oldData = this._oldData[model]
const newModel = (await this.constructor.find(model.primaryKeyValue))
/**
* Update with audit
*
* @param auth
* @param request
* @returns {*}
*/
function updateWithAudit ({request, auth}) {
return async function (data, ignoreDiff = ['updated_at']) {
const auditable = this.constructor.name
const auditableId = this.id
const oldData = this.$originalAttributes
this.merge(data)
const result = await this.save()
const newModel = (await this.constructor.find(this.primaryKeyValue))
const newData = newModel.$attributes

// if new and old are equal then don't bother updating
// todo: do this
const ignoreDiff = ['updated_at']

const isEqual = _.isEqual(
_.omit(newData, ignoreDiff),
_.omit(oldData, ignoreDiff)
)
if (isEqual) {
return
return result
}

// update / patch are shared
const event = Audit.events.UPDATE

// save audit
await this._createAudit(event, auditable, auditableId, oldData, newData)
await createAudit(event, {request, auth}, auditable, auditableId, oldData, newData)

return result
}
}

async _afterDelete (model) {
const event = Audit.EVENT_DELETE
const auditable = model.constructor.name
const auditableId = model.id
const oldData = model.$originalAttributes
/**
* Delete with audit
*
* @param auth
* @param request
* @returns {*}
*/
function deleteWithAudit ({request, auth}) {
return async function () {
const auditable = this.constructor.name
const auditableId = this.id
const oldData = this.$originalAttributes
const result = await this.delete()

// save audit
await this._createAudit(event, auditable, auditableId, oldData)
await createAudit(Audit.events.DELETE, {request, auth}, auditable, auditableId, oldData)

return result
}
}

async _createAudit (event, auditable, auditableId, oldData, newData) {
// get user data to store
const userId = _.get(this.ctx, 'auth.user.id', null)
const url = this.ctx.request.absoluteUrl()
const ip = this.ctx.request.ip()
/**
* Run the audit
*
* @param event
* @param oldData
* @param auditable
* @param auditableId
* @param newData
* @param auth
* @param request
* @returns {Promise<void>}
*/
async function createAudit (event, {request, auth}, auditable, auditableId, oldData, newData) {
// check auth was passed
if (!auth) {
throw new Error('Auth param is empty')
}

// save audit
await Audit.create({
user_id: userId,
auditable_id: auditableId,
auditable,
event,
url,
ip,
old_data: oldData,
new_data: newData,
})
// check request was passed
if (!request) {
throw new Error('Request param is empty')
}

// get user data to store
const userId = auth.user.id
const url = request.absoluteUrl()
const ip = request.ip()

// save audit
await Audit.create({
user_id: userId,
auditable_id: auditableId,
auditable,
event,
url,
ip,
old_data: oldData,
new_data: newData,
})
}

module.exports = Auditable
38 changes: 11 additions & 27 deletions templates/Audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,16 @@ class Audit extends Model {
}

/**
* @returns {string}
* Auditable events
*
* @returns {Object}
*/
static get EVENT_CREATE () {
return 'create'
}

/**
* @returns {string}
*/
static get EVENT_UPDATE () {
return 'update'
}

/**
* @returns {string}
*/
static get EVENT_PATCH () {
return 'patch'
}

/**
* @returns {string}
*/
static get EVENT_DELETE () {
return 'delete'
static get events () {
return Object.freeze({
CREATE: 'create',
UPDATE: 'update',
DELETE: 'delete',
})
}

/**
Expand All @@ -53,10 +38,9 @@ class Audit extends Model {
* @returns {any}
*/
setOldData (value) {
if (value !== null && typeof value === 'object') {
if (value !== null) {
return JSON.stringify(value)
}
return value
}

/**
Expand All @@ -74,7 +58,7 @@ class Audit extends Model {
* @returns {any}
*/
setNewData (value) {
if (value !== null && typeof value === 'object') {
if (value !== null) {
return JSON.stringify(value)
}
}
Expand Down

0 comments on commit de8e822

Please sign in to comment.