UNPKG

admin-bro

Version:
368 lines (320 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.bundle = exports.registerAdapter = exports.VERSION = void 0; var _ = _interopRequireWildcard(require("lodash")); var path = _interopRequireWildcard(require("path")); var fs = _interopRequireWildcard(require("fs")); var _configurationError = _interopRequireDefault(require("./backend/utils/configuration-error")); var _resourcesFactory = _interopRequireDefault(require("./backend/utils/resources-factory")); var _userComponentsBundler = _interopRequireDefault(require("./backend/bundler/user-components-bundler")); var _constants = require("./constants"); var _loginTemplate = _interopRequireDefault(require("./frontend/login-template")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8')); const VERSION = pkg.version; exports.VERSION = VERSION; const defaults = { rootPath: _constants.DEFAULT_PATHS.rootPath, logoutPath: _constants.DEFAULT_PATHS.logoutPath, loginPath: _constants.DEFAULT_PATHS.loginPath, databases: [], resources: [], branding: { companyName: 'Company', softwareBrothers: true }, dashboard: {}, assets: { styles: [], scripts: [], globalsFromCDN: true } }; /** * Main class for AdminBro extension. It takes {@link AdminBroOptions} as a * parameter and creates an admin instance. * * Its main responsibility is to fetch all the resources and/or databases given by a * user. Its instance is a currier - injected in all other classes. * * @example * const { AdminBro } = require('admin-bro') * const admin = new AdminBro(AdminBroOptions) * */ class AdminBro { /** * @param {AdminBroOptions} options options passed to adminBro */ constructor(options = {}) { /** * @type {BaseResource[]} * @description List of all resources available for the AdminBro. * They can be fetched with the {@link AdminBro#findResource} method */ this.resources = []; /** * @type {AdminBroOptions} * @description Options given by a user */ this.options = _.merge({}, defaults, options); const defaultLogo = `${this.options.rootPath}/frontend/assets/logo-mini.svg`; this.options.branding = this.options.branding || {}; this.options.branding.logo = this.options.branding.logo || defaultLogo; const { databases, resources } = this.options; const resourcesFactory = new _resourcesFactory.default(this, AdminBro.registeredAdapters); this.resources = resourcesFactory.buildResources({ databases, resources }); } /** * Registers various database adapters written for AdminBro. * * @example * const AdminBro = require('admin-bro') * const MongooseAdapter = require('admin-bro-mongoose') * AdminBro.registerAdapter(MongooseAdapter) * * @param {Object} options * @param {typeof BaseDatabase} options.Database subclass of BaseDatabase * @param {typeof BaseResource} options.Resource subclass of BaseResource */ static registerAdapter({ Database, Resource }) { if (!Database || !Resource) { throw new Error('Adapter has to have both Database and Resource'); } // checking if both Database and Resource have at least isAdapterFor method if (Database.isAdapterFor && Resource.isAdapterFor) { AdminBro.registeredAdapters.push({ Database, Resource }); } else { throw new Error('Adapter elements has to be subclassess of AdminBro.BaseResource and AdminBro.BaseDatabase'); } } /** * Initializes AdminBro instance in production. This function should be called by * all external plugins. */ async initialize() { if (process.env.NODE_ENV === 'production') { console.log('AdminBro: bundling user components...'); await (0, _userComponentsBundler.default)(this, { write: true }); } } /** * Renders an entire login page with email and password fields * using {@link Renderer}. * * Used by external plugins * * @param {Object} options * @param {String} options.action login form action url - it could be * '/admin/login' * @param {String} [options.errorMessage] optional error message. When set, * renderer will print this message in * the form * @return {Promise<string>} HTML of the rendered page */ static async renderLogin({ action, errorMessage }) { return (0, _loginTemplate.default)({ action, errorMessage }); } /** * Returns resource base on its ID * * @example * const User = admin.findResource('users') * await User.findOne(userId) * * @param {String} resourceId ID of a resource defined under {@link BaseResource#id} * @return {BaseResource} found resource * @throws {Error} when resource with given id cannot be found */ findResource(resourceId) { const resource = this.resources.find(m => m.id() === resourceId); if (!resource) { throw new Error([`There are no resources with given id: "${resourceId}"`, 'This is the list of all registered resources you can use:', this.resources.map(r => r.id()).join(', ')].join('\n')); } return resource; } /** * Requires given .jsx/.tsx file, that it can be bundled to the frontend. * It will be available under AdminBro.UserComponents[componentId]. * * @param {String} src path to a file containing react component. * * @return {String} componentId - uniq id of a component * * @example * const adminBroOptions = { * dashboard: { * component: AdminBro.bundle('./path/to/component'), * } * } */ static bundle(src) { const extensions = ['.jsx', '.js', '.ts', '.tsx']; let filePath = ''; const componentId = _.uniqueId('Component'); if (src[0] === '/') { filePath = src; } else { const stack = (new Error().stack || '').split('\n'); const m = stack[2].match(/\((.*):[0-9]+:[0-9]+\)/); if (!m) { throw new Error('STACK does not have a file url. Chcek out if the node version >= 8'); } filePath = path.join(path.dirname(m[1]), src); } const { root, dir, name } = path.parse(filePath); if (!extensions.find(ext => { const fileName = path.format({ root, dir, name, ext }); return fs.existsSync(fileName); })) { throw new _configurationError.default(`Given file "${src}", doesn't exist.`, 'AdminBro.html'); } AdminBro.UserComponents[componentId] = path.format({ root, dir, name }); return componentId; } } AdminBro.UserComponents = {}; AdminBro.registeredAdapters = []; AdminBro.VERSION = VERSION; const { registerAdapter } = AdminBro; exports.registerAdapter = registerAdapter; const { bundle } = AdminBro; exports.bundle = bundle; var _default = AdminBro; /** * @description * Contains set of routes availables within the application. * It is used by external plugins. * * @example * const { Router } = require('admin-bro') * Router.routes.forEach(route => { * // map your framework routes to admin-bro routes * // see how `admin-bro-expressjs` plugin does it. * }) * * @memberof AdminBro * @static * @name Router * @alias AdminBro.Router * @type RouterType */ /** * abstract class for all databases. External adapters have to implement that. * @memberof AdminBro * @static * @abstract * @name AdminBro.BaseDatabase * @type {typeof BaseDatabase} */ /** * abstract class for all records. External adapters have to implement that or at least * their BaseResource implementation should return records of this type. * @memberof AdminBro * @static * @abstract * @name AdminBro.BaseRecord * @type {typeof BaseRecord} */ /** * abstract class for all resources. External adapters have to implement that. * @memberof AdminBro * @static * @abstract * @name AdminBro.BaseResource * @type {typeof BaseResource} */ /** * abstract class for all properties. External adapters have to implement that or at least * their BaseResource implementation should return records of this type. * @memberof AdminBro * @static * @abstract * @name AdminBro.BaseProperty * @type {typeof BaseProperty} */ /** * Filter object passed to find method of BaseResource. External adapters have to use it * @memberof AdminBro * @static * @abstract * @name AdminBro.Filter * @type {typeof Filter} */ /** * Validation error which is thrown when record fails validation. External adapters have * to use it. * @memberof AdminBro * @static * @name AdminBro.ValidationError * @type {typeof ValidationError} */ /** * List of all default actions. If you want to change behaviour for all actions lika list, * edit, show and delete you can do this here. * * @example <caption>Modifying accessibility rules for all show actions</caption> * const { ACTIONS } = require('admin-bro') * ACTIONS.show.isAccessible = () => {...} * * * @memberof AdminBro * @static * @name AdminBro.ACTIONS * @type {ActionsMap} */ /** * AdminBro version * @memberof AdminBro * @static * @name AdminBro.VERSION * @type {string} */ /** * List of all bundled components * @memberof AdminBro * @static * @name AdminBro.UserComponents * @type {UserComponentsMap} */ exports.default = _default;