admin-bro
Version:
Admin panel for apps written in node.js
293 lines (247 loc) • 11 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _populator = _interopRequireDefault(require("../utils/populator/populator"));
var _viewHelpers = _interopRequireDefault(require("../utils/view-helpers/view-helpers"));
var _configurationError = _interopRequireDefault(require("../utils/errors/configuration-error"));
var _notFoundError = _interopRequireDefault(require("../utils/errors/not-found-error"));
var _requestParser = require("../utils/request-parser");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
* Controller responsible for the auto-generated API: `/admin_root/api/...`, where
* _admin_root_ is the `rootPath` given in {@link AdminBroOptions}.
*
* The best way to utilise it is to use {@link ApiClient} on the frontend.
*
* ### Available API endpoints
*
* <div class='table-container'>
*
* | Endpoint | Method | Description |
* |--------------------------|-----------------------|-------------|
* | .../api/resources/{resourceId}/actions/{action} | {@link ApiController#resourceAction} | Perform customized resource action |
* | .../api/resources/{resourceId}/records/{recordId}/{action} | {@link ApiController#recordAction} | Perform customized record action |
* | .../api/resources/{resourceId}/bulk/{action}?recordIds={recordIds} | {@link ApiController#bulkAction} | Perform customized bulk action |
* | .../api/pages/{pageName}_ | {@link ApiController#page} | Perform customized page action |
* | .../api/dashboard_ | {@link ApiController#dashboard} | Perform customized dashboard action |
*
* </div>
*
* ### Responsibility
*
* In general this controllers takes handler functions you define in {@link AdminBroOptions} and:
* - find all the [context information]{@link ActionContext} which is needed by the action
* and is passed to the {@link Action#handler}, {@link Action#before} and {@link Action#after}
* - checks if action can be invoked by particular user {@link Action#isAccessible}
* - invokes {@link Action#before} and {@link Action#after} hooks
*
* You probably don't want to modify it, but you can call its methods by using {@link ApiClient}
*
* @hideconstructor
*/
class ApiController {
/**
* @param {Object} options
* @param {AdminBroOptions} options.admin
* @param {CurrentAdmin} [currentAdmin]
*/
constructor({
admin
}, currentAdmin) {
this._admin = admin;
this.currentAdmin = currentAdmin;
}
/**
* Returns context for given action
* @private
*
* @param {ActionRequest} request request object
* @return {Promise<ActionContext>} action context
*/
async getActionContext(request) {
const {
resourceId,
action: actionName
} = request.params;
const h = new _viewHelpers.default(this._admin);
const resource = this._admin.findResource(resourceId);
const action = resource.decorate().actions[actionName];
return _objectSpread({
resource,
action,
h,
currentAdmin: this.currentAdmin,
_admin: this._admin
}, this._admin.translateFunctions);
}
/**
* Search records by query string.
*
* Handler function responsible for a _.../api/resources/{resourceId}/search/{query}_ route
*
* @param {ActionRequest} request with __params.query__ set
* @param {any} response
*
* @return {Promise<SearchActionResponse>} found records
*/
async search(request, response) {
request.params.action = 'search'; // eslint-disable-next-line no-console
console.log(['Using ApiController#search is deprecated in favour of resourceAction', 'It will be removed in the next version'].join('\n'));
return this.resourceAction(request, response);
}
/**
* Performs a customized {@link Action resource action}.
* To call it use {@link ApiClient#resourceAction} method.
*
* Handler function responsible for a _.../api/resources/{resourceId}/actions/{action}_
*
* @param {ActionRequest} originalRequest
* @param {any} response object from the plugin (i.e. admin-bro-expressjs)
*
* @return {Promise<ActionResponse>} action response
*/
async resourceAction(originalRequest, response) {
const actionContext = await this.getActionContext(originalRequest);
const request = (0, _requestParser.requestParser)(originalRequest, actionContext.resource);
return actionContext.action.handler(request, response, actionContext);
}
/**
* Performs a customized {@link Action record action}.
* To call it use {@link ApiClient#recordAction} method.
*
* Handler function responsible for a _.../api/resources/{resourceId}/records/{recordId}/{action}_
*
* @param {ActionRequest} originalRequest
* @param {any} response
*
* @return {Promise<RecordActionResponse>} action response
* @throws ConfigurationError When given record action doesn't return {@link RecordJSON}
* @throws ConfigurationError when action handler doesn't return Promise<{@link RecordActionResponse}>
*/
async recordAction(originalRequest, response) {
const {
recordId,
resourceId
} = originalRequest.params;
const actionContext = await this.getActionContext(originalRequest);
const request = (0, _requestParser.requestParser)(originalRequest, actionContext.resource);
if (!recordId) {
throw new _notFoundError.default(['You have to pass recordId to the recordAction'].join('\n'), 'Action#handler');
}
let record = await actionContext.resource.findOne(recordId);
if (!record) {
throw new _notFoundError.default([`record with given id: "${recordId}" cannot be found in resource "${resourceId}"`].join('\n'), 'Action#handler');
}
[record] = await (0, _populator.default)([record]);
actionContext.record = record;
const jsonWithRecord = await actionContext.action.handler(request, response, actionContext);
if (jsonWithRecord && jsonWithRecord.record && jsonWithRecord.record.recordActions) {
return jsonWithRecord;
}
throw new _configurationError.default('handler of a recordAction should return a RecordJSON object', 'Action#handler');
}
/**
* Performs a customized {@link Action bulk action}.
* To call it use {@link ApiClient#bulkAction} method.
*
* Handler function responsible for a _.../api/resources/{resourceId}/bulk/{action}?recordIds={recordIds}_
*
* @param {ActionRequest} request
* @param {any} response
*
* @return {Promise<BulkActionResponse>} action response
* @throws NotFoundError when recordIds are missing in query or they don't exists in
* the database
* @throws ConfigurationError when action handler doesn't return Promise<{@link BulkActionResponse}>
*/
async bulkAction(originalRequest, response) {
const {
resourceId
} = originalRequest.params;
const {
recordIds
} = originalRequest.query || {};
const actionContext = await this.getActionContext(originalRequest);
const request = (0, _requestParser.requestParser)(originalRequest, actionContext.resource);
if (!recordIds) {
throw new _notFoundError.default(['You have to pass "recordIds" to the bulkAction via search params: ?recordIds=...'].join('\n'), 'Action#handler');
}
let records = await actionContext.resource.findMany(recordIds.split(','));
if (!records || !records.length) {
throw new _notFoundError.default([`record with given id: "${recordIds}" cannot be found in resource "${resourceId}"`].join('\n'), 'Action#handler');
}
records = await (0, _populator.default)(records);
const jsonWithRecord = await actionContext.action.handler(request, response, _objectSpread(_objectSpread({}, actionContext), {}, {
records
}));
if (jsonWithRecord && jsonWithRecord.records) {
return jsonWithRecord;
}
throw new _configurationError.default('handler of a bulkAction should return an Array of RecordJSON object', 'Action#handler');
}
/**
* Gets optional data needed by the dashboard.
* To call it use {@link ApiClient#getDashboard} method.
*
* Handler function responsible for a _.../api/dashboard_
*
* @param {ActionRequest} request
* @param {any} response
*
* @return {Promise<any>} action response
*/
async dashboard(request, response) {
const h = new _viewHelpers.default(this._admin);
const handler = this._admin.options.dashboard && this._admin.options.dashboard.handler;
if (handler) {
return handler(request, response, {
h,
currentAdmin: this.currentAdmin,
_admin: this._admin
});
}
return {
message: ['You can override this method by setting up dashboard.handler', 'function in AdminBro options'].join('\n')
};
}
/**
* Gets optional data needed by the page.
* To call it use {@link ApiClient#getPage} method.
*
* Handler function responsible for a _.../api/pages/{pageName}_
*
* @param {ActionRequest} request
* @param {any} response
*
* @return {Promise<any>} action response
*/
async page(request, response) {
const h = new _viewHelpers.default(this._admin);
const {
pages = {}
} = this._admin.options;
const {
pageName
} = request.params;
const {
handler
} = pages[pageName] || {};
if (handler) {
return handler(request, response, {
h,
currentAdmin: this.currentAdmin,
_admin: this._admin
});
}
return {
message: ['You can override this method by setting up pages[pageName].handler', 'function in AdminBro options'].join('\n')
};
}
}
var _default = ApiController;
exports.default = _default;
;