Skip to content

Commit

Permalink
feat: admin-bro features
Browse files Browse the repository at this point in the history
closes #431
  • Loading branch information
wojtek-krysiak committed Aug 2, 2020
1 parent a7e3bc9 commit fcd13e4
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 79 deletions.
22 changes: 3 additions & 19 deletions example-app/src/companies/company.admin.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
const AdminBro = require('@admin-bro/core')
const { Company } = require('./company.entity')

const {
after: passwordAfterHook,
before: passwordBeforeHook,
} = require('./actions/password.hook')
const passwordFeature = require('../features/password/password.feature')

/** @type {AdminBro.ResourceOptions} */
const options = {
properties: {
encryptedPassword: {
isVisible: false,
},
profilePhotoLocation: {
isVisible: false,
},
password: {
type: 'password',
},
isAdmin: {
isDisabled: true,
},
Expand All @@ -26,14 +16,6 @@ const options = {
},
},
actions: {
new: {
after: passwordAfterHook,
before: passwordBeforeHook,
},
edit: {
after: passwordAfterHook,
before: passwordBeforeHook,
},
search: {
handler: async (request, response, data) => {
const { currentAdmin, resource } = data
Expand All @@ -52,7 +34,9 @@ const options = {
},
}

/** @type {import('@admin-bro/core').ResourceWithOptions} */
module.exports = {
options,
resource: Company,
features: [passwordFeature],
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
const { buildFeature } = require('@admin-bro/core')
const argon2 = require('argon2')
const AdminBro = require('@admin-bro/core')


/** @type {AdminBro.After<AdminBro.ActionResponse>} */
/** @type {import('@admin-bro/core').After<import('@admin-bro/core').ActionResponse>} */
const after = async (response) => {
if (response.record && response.record.errors && response.record.errors.encryptedPassword) {
response.record.errors.password = response.record.errors.encryptedPassword
}
return response
}

/** @type {AdminBro.Before} */
/** @type {import('@admin-bro/core').Before} */
const before = async (request) => {
if (request.method === 'post') {
const { password, ...otherParams } = request.payload
Expand All @@ -30,4 +29,17 @@ const before = async (request) => {
return request
}

module.exports = { after, before }
module.exports = buildFeature({
properties: {
password: {
type: 'password',
},
encryptedPassword: {
isVisible: false,
},
},
actions: {
new: { after, before },
edit: { after, before },
},
})
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ReduxState } from './types/src/frontend/store/store'

export * from '@admin-bro/design-system'
export * from './types/src/frontend/store/store'
export * from './types/src/backend/utils/build-feature'

export { default as Router } from './types/src/backend/router'
export { default as Filter } from './types/src/backend/utils/filter'
Expand Down
51 changes: 16 additions & 35 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,22 @@
let AdminBro
let constants

if (process.env.ADMIN_BRO_DEV_ENV) {
require('@babel/polyfill')
require('@babel/register')({
presets: [
require.resolve('@babel/preset-react'),
require.resolve('@babel/preset-env'),
require.resolve('@babel/preset-typescript'),
],
plugins: [require.resolve('babel-plugin-styled-components')],
extensions: ['.jsx', '.js', '.ts', '.tsx'],
})
AdminBro = require('./src/admin-bro').default
AdminBro.BaseProperty = require('./src/backend/adapters/base-property').default
AdminBro.BaseResource = require('./src/backend/adapters/base-resource').default
AdminBro.BaseDatabase = require('./src/backend/adapters/base-database').default
AdminBro.BaseRecord = require('./src/backend/adapters/base-record').default
AdminBro.Router = require('./src/backend/router').default
AdminBro.Filter = require('./src/backend/utils/filter').default
AdminBro.ValidationError = require('./src/backend/utils/validation-error').default
AdminBro.ForbiddenError = require('./src/backend/utils/forbidden-error').default
AdminBro.ACTIONS = require('./src/backend/actions/index')
constants = require('./src/constants')
} else {
AdminBro = require('./lib/admin-bro').default
AdminBro.BaseProperty = require('./lib/backend/adapters/base-property').default
AdminBro.BaseResource = require('./lib/backend/adapters/base-resource').default
AdminBro.BaseDatabase = require('./lib/backend/adapters/base-database').default
AdminBro.BaseRecord = require('./lib/backend/adapters/base-record').default
AdminBro.Router = require('./lib/backend/router').default
AdminBro.Filter = require('./lib/backend/utils/filter').default
AdminBro.ValidationError = require('./lib/backend/utils/validation-error').default
AdminBro.ForbiddenError = require('./lib/backend/utils/forbidden-error').default
AdminBro.ACTIONS = require('./lib/backend/actions/index')
constants = require('./lib/constants')
}
AdminBro = require('./lib/admin-bro').default
AdminBro.BaseProperty = require('./lib/backend/adapters/base-property').default
AdminBro.BaseResource = require('./lib/backend/adapters/base-resource').default
AdminBro.BaseDatabase = require('./lib/backend/adapters/base-database').default
AdminBro.BaseRecord = require('./lib/backend/adapters/base-record').default
AdminBro.Router = require('./lib/backend/router').default
AdminBro.Filter = require('./lib/backend/utils/filter').default
AdminBro.ValidationError = require('./lib/backend/utils/validation-error').default
AdminBro.ForbiddenError = require('./lib/backend/utils/forbidden-error').default
AdminBro.ACTIONS = require('./lib/backend/actions/index')
constants = require('./lib/constants')

const { buildFeature, mergeResourceOptions } = require('./lib/backend/utils/build-feature')

AdminBro.buildFeature = buildFeature
AdminBro.mergeResourceOptions = mergeResourceOptions

AdminBro.require = AdminBro.bundle
Object.keys(constants).forEach((key) => {
Expand Down
7 changes: 7 additions & 0 deletions src/admin-bro-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,15 @@ export type AdminPage = {
export type ResourceWithOptions = {
resource: any;
options: ResourceOptions;
features?: Array<FeatureType>;
}

/**
* Function taking ResourceOptions and merging it with all other
* options
*/
export type FeatureType = (ResourceOptions) => ResourceOptions

/**
* Function which is invoked when user enters given AdminPage
*
Expand Down
2 changes: 1 addition & 1 deletion src/backend/actions/action.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ export default interface Action <T extends ActionResponse> {
* Required for new actions. For modifying already defined actions
* like new and edit we suggest using {@link Action#before} and {@link Action#after} hooks.
*/
handler: ActionHandler<T>;
handler: ActionHandler<T> | Array<ActionHandler<T>>;
/**
* Before action hook. When it is given - it is performed before the {@link Action#handler}
* method.
Expand Down
14 changes: 9 additions & 5 deletions src/backend/actions/bulk-delete-action.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised'
import sinon from 'sinon'

import BulkDeleteAction from './bulk-delete-action'
import { ActionContext, ActionRequest } from './action.interface'
import { ActionContext, ActionRequest, ActionHandler, BulkActionResponse } from './action.interface'
import BaseRecord from '../adapters/base-record'
import AdminBro from '../../admin-bro'
import ViewHelpers from '../utils/view-helpers'
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('BulkDeleteAction', function () {

it('throws error when no records are given', async function () {
await expect(
BulkDeleteAction.handler(request, response, data),
(BulkDeleteAction.handler as ActionHandler<BulkActionResponse>)(request, response, data),
).to.rejectedWith(NotFoundError)
})

Expand All @@ -59,7 +59,7 @@ describe('BulkDeleteAction', function () {
request.method = 'get'

await expect(
BulkDeleteAction.handler(request, response, data),
(BulkDeleteAction.handler as ActionHandler<BulkActionResponse>)(request, response, data),
).to.eventually.deep.equal({
records: [recordJSON],
})
Expand All @@ -68,15 +68,19 @@ describe('BulkDeleteAction', function () {
it('deletes all records for post request', async function () {
request.method = 'post'

await BulkDeleteAction.handler(request, response, data)
await (
BulkDeleteAction.handler as ActionHandler<BulkActionResponse>
)(request, response, data)

expect(data.resource.delete).to.have.been.calledOnce
})

it('returns deleted records, notice and redirectUrl for post request', async function () {
request.method = 'post'

const actionResponse = await BulkDeleteAction.handler(request, response, data)
const actionResponse = await (
BulkDeleteAction.handler as ActionHandler<BulkActionResponse>
)(request, response, data)

expect(actionResponse).to.have.property('notice')
expect(actionResponse).to.have.property('redirectUrl')
Expand Down
4 changes: 2 additions & 2 deletions src/backend/decorators/action-decorator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('ActionDecorator', function () {
resource,
})

const ret = await decorator.before({} as ActionRequest, {} as ActionContext)
const ret = await decorator.invokeBeforeHook({} as ActionRequest, {} as ActionContext)

expect(ret).to.deep.eq({
response1: true,
Expand All @@ -70,7 +70,7 @@ describe('ActionDecorator', function () {
resource,
})

const ret = await decorator.after(
const ret = await decorator.invokeAfterHook(
{} as ActionResponse,
{} as ActionRequest,
{} as ActionContext,
Expand Down
52 changes: 46 additions & 6 deletions src/backend/decorators/action-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import ConfigurationError from '../utils/configuration-error'
import ViewHelpers from '../utils/view-helpers'
import AdminBro from '../../admin-bro'
import BaseResource from '../adapters/base-resource'
import Action, { IsFunction, ActionContext, ActionRequest, ActionResponse, After, Before } from '../actions/action.interface'
import Action, {
IsFunction,
ActionContext,
ActionRequest,
ActionResponse,
After,
Before,
ActionHandler,
} from '../actions/action.interface'
import { CurrentAdmin } from '../../current-admin.interface'
import ActionJSON from './action-json.interface'
import BaseRecord from '../adapters/base-record'
Expand Down Expand Up @@ -71,9 +79,9 @@ class ActionDecorator {
): Promise<any> {
try {
this.canInvokeAction(context)
const modifiedRequest = await this.before(request, context)
const res = await this.action.handler(modifiedRequest, response, context)
return this.after(res, modifiedRequest, context)
const modifiedRequest = await this.invokeBeforeHook(request, context)
const res = await this.invokeHandler(modifiedRequest, response, context)
return this.invokeAfterHook(res, modifiedRequest, context)
} catch (error) {
return actionErrorHandler(error, context)
}
Expand All @@ -87,7 +95,7 @@ class ActionDecorator {
*
* @return {Promise<ActionRequest>}
*/
async before(request: ActionRequest, context: ActionContext): Promise<ActionRequest> {
async invokeBeforeHook(request: ActionRequest, context: ActionContext): Promise<ActionRequest> {
if (!this.action.before) {
return request
}
Expand All @@ -107,6 +115,38 @@ class ActionDecorator {
)
}

/**
* Invokes action handler if there is any
*
* @param {ActionRequest} request
* @param {any} response
* @param {ActionContext} context
*
* @return {Promise<ActionResponse>}
*/
async invokeHandler(
request: ActionRequest,
response: any,
context: ActionContext,
): Promise<ActionResponse> {
if (typeof this.action.handler === 'function') {
return this.action.handler(request, response, context)
}
if (Array.isArray(this.action.handler)) {
return (this.action.handler as Array<ActionHandler<ActionResponse>>).reduce(
(prevPromise, handler) => (
prevPromise.then(() => (
handler(request, response, context)
))
), Promise.resolve({}),
)
}
throw new ConfigurationError(
'Before action hook has to be either function or Array<function>',
'Action#Before',
)
}

/**
* Invokes after action hooks if there are any
*
Expand All @@ -116,7 +156,7 @@ class ActionDecorator {
*
* @return {Promise<ActionResponse>}
*/
async after(
async invokeAfterHook(
response: ActionResponse,
request: ActionRequest,
context: ActionContext,
Expand Down
Loading

0 comments on commit fcd13e4

Please sign in to comment.