server/helpers/entityRouteUtils.js

"use strict";

require("core-js/modules/es.object.keys.js");
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.array.filter.js");
require("core-js/modules/es.object.get-own-property-descriptor.js");
require("core-js/modules/web.dom-collections.for-each.js");
require("core-js/modules/es.object.get-own-property-descriptors.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/es.weak-map.js");
require("core-js/modules/web.dom-collections.iterator.js");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.addInitialRelationship = addInitialRelationship;
exports.createEntitiesHandler = createEntitiesHandler;
exports.entityEditorMarkup = entityEditorMarkup;
exports.entityMergeMarkup = entityMergeMarkup;
exports.generateEntityMergeProps = generateEntityMergeProps;
exports.generateEntityProps = generateEntityProps;
exports.generateUnifiedProps = generateUnifiedProps;
exports.makeEntityCreateOrEditHandler = makeEntityCreateOrEditHandler;
exports.unifiedFormMarkup = unifiedFormMarkup;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.object.assign.js");
require("core-js/modules/es.function.name.js");
require("core-js/modules/es.array.find.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.array.includes.js");
var _get2 = _interopRequireDefault(require("lodash/get"));
var _camelCase2 = _interopRequireDefault(require("lodash/camelCase"));
var _pick2 = _interopRequireDefault(require("lodash/pick"));
var _size2 = _interopRequireDefault(require("lodash/size"));
var _startCase2 = _interopRequireDefault(require("lodash/startCase"));
var _kebabCase2 = _interopRequireDefault(require("lodash/kebabCase"));
var _partialRight2 = _interopRequireDefault(require("lodash/partialRight"));
var _upperFirst2 = _interopRequireDefault(require("lodash/upperFirst"));
var Immutable = _interopRequireWildcard(require("immutable"));
var React = _interopRequireWildcard(require("react"));
var UnifiedFormHelpers = _interopRequireWildcard(require("../../client/unified-form/helpers"));
var entityEditorHelpers = _interopRequireWildcard(require("../../client/entity-editor/helpers"));
var entityRoutes = _interopRequireWildcard(require("../routes/entity/entity"));
var error = _interopRequireWildcard(require("../../common/helpers/error"));
var propHelpers = _interopRequireWildcard(require("../../client/helpers/props"));
var unifiedRoutes = _interopRequireWildcard(require("../routes/entity/process-unified-form"));
var utils = _interopRequireWildcard(require("./utils"));
var _utils2 = require("../../common/helpers/utils");
var _entityEditor = _interopRequireDefault(require("../../client/entity-editor/entity-editor"));
var _entityMerge = _interopRequireDefault(require("../../client/entity-editor/entity-merge"));
var _layout = _interopRequireDefault(require("../../client/containers/layout"));
var _reactRedux = require("react-redux");
var _server = _interopRequireDefault(require("react-dom/server"));
var _unifiedForm = _interopRequireDefault(require("../../client/unified-form/unified-form"));
var _redux = require("redux");
var _props2 = require("./props");
var _excluded = ["initialState"],
  _excluded2 = ["initialState"],
  _excluded3 = ["initialState"];
/*
 * Copyright (C) 2017  Eshan Singh
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && 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; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); 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 = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
var ufCreateRootReducer = UnifiedFormHelpers.createRootReducer;
var createRootReducer = entityEditorHelpers.createRootReducer,
  getEntitySection = entityEditorHelpers.getEntitySection,
  getEntitySectionMerge = entityEditorHelpers.getEntitySectionMerge,
  getValidator = entityEditorHelpers.getValidator;
var validEntityTypes = ['author', 'edition', 'editionGroup', 'publisher', 'series', 'work'];
/**
 * Callback to get the initial state
 * @callback initialStateCallback
 * @param {object} entity - entity
 */

/**
 * Returns a props object with reasonable defaults for entity creation/editing.
 * @param {string} entityType - entity type
 * @param {request} req - request object
 * @param {response} res - response object
 * @param {object} additionalProps - additional props
 * @param {initialStateCallback} initialStateCallback - callback
 * to get the initial state
 * @returns {object} - props
 */
function generateEntityProps(entityType, req, res, additionalProps) {
  var initialStateCallback = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : function () {
    return new Object();
  };
  var entityName = (0, _upperFirst2.default)(entityType);
  var entity = res.locals.entity;
  var isEdit = Boolean(entity);
  var getFilteredIdentifierTypes = isEdit ? (0, _partialRight2.default)(utils.filterIdentifierTypesByEntity, entity) : (0, _partialRight2.default)(_utils2.filterIdentifierTypesByEntityType, entityName);
  var filteredIdentifierTypes = getFilteredIdentifierTypes(res.locals.identifierTypes);
  var entityNameForRoute = (0, _kebabCase2.default)(entityType);
  var submissionUrl = isEdit ? "/".concat(entityNameForRoute, "/").concat(entity.bbid, "/edit/handler") : "/".concat(entityNameForRoute, "/create/handler");
  var action = isEdit ? 'edit' : 'create';
  var props = Object.assign({
    action: action,
    entityType: entityType,
    heading: isEdit ? "Edit ".concat(entity.defaultAlias ? entity.defaultAlias.name : '(unnamed)', " (").concat(entityName, ")") : "Add ".concat(entityName),
    identifierTypes: filteredIdentifierTypes,
    initialState: initialStateCallback(entity),
    isMerge: false,
    languageOptions: res.locals.languages,
    requiresJS: true,
    subheading: isEdit ? "Edit an existing ".concat(entityName, " on BookBrainz") : "Add a new ".concat(entityName, " to BookBrainz"),
    submissionUrl: submissionUrl
  }, additionalProps);
  return (0, _props2.generateProps)(req, res, props);
}

/**
 * Callback to get the initial state
 * @callback initialStateCallback
 * @param {object} entity - entity
 */

/**
 * Returns a props object with reasonable defaults for entity creation/editing.
 * @param {request} req - request object
 * @param {response} res - response object
 * @param {object} additionalProps - additional props
 * @param {initialStateCallback} initialStateCallback - callback
 * to get the initial state
 * @returns {object} - props
 */
function generateEntityMergeProps(req, res, additionalProps) {
  var initialStateCallback = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {
    return new Object();
  };
  var entityType = additionalProps.entityType,
    mergingEntities = additionalProps.mergingEntities;
  var entityName = (0, _startCase2.default)(entityType);
  var entity = mergingEntities[0];
  var getFilteredIdentifierTypes = (0, _partialRight2.default)(utils.filterIdentifierTypesByEntity, entity);
  var filteredIdentifierTypes = getFilteredIdentifierTypes(res.locals.identifierTypes);
  var submissionUrl = "/".concat((0, _kebabCase2.default)(entityType), "/").concat(entity.bbid, "/merge/handler");
  var props = Object.assign({
    entityType: entityType,
    heading: "Merge ".concat(mergingEntities.length, " ").concat(entityName, "s"),
    identifierTypes: filteredIdentifierTypes,
    initialState: initialStateCallback(mergingEntities),
    isMerge: true,
    languageOptions: res.locals.languages,
    requiresJS: true,
    subheading: "You are merging ".concat(mergingEntities.length, " existing ").concat(entityName, "s:"),
    submissionUrl: submissionUrl
  }, additionalProps);
  return (0, _props2.generateProps)(req, res, props);
}

/**
 * Return markup for the entity editor.
 * This also modifies the props value with a new initialState!
 * @param {object} props - react props
 * @returns {object} - Updated props and HTML string with markup
 */
function entityEditorMarkup(props) {
  var initialState = props.initialState,
    rest = (0, _objectWithoutProperties2.default)(props, _excluded);
  var rootReducer = createRootReducer(props.entityType);
  var store = (0, _redux.createStore)(rootReducer, Immutable.fromJS(initialState));
  var EntitySection = getEntitySection(props.entityType);
  var markup = _server.default.renderToString( /*#__PURE__*/React.createElement(_layout.default, propHelpers.extractLayoutProps(rest), /*#__PURE__*/React.createElement(_reactRedux.Provider, {
    store: store
  }, /*#__PURE__*/React.createElement(_entityEditor.default, (0, _extends2.default)({
    validate: getValidator(props.entityType)
  }, propHelpers.extractChildProps(rest)), /*#__PURE__*/React.createElement(EntitySection, null)))));
  return {
    markup: markup,
    props: Object.assign({}, props, {
      intitialState: store.getState()
    })
  };
}

/**
 * Return markup for the entity merging tool.
 * This also modifies the props value with a new initialState!
 * @param {object} props - react props
 * @returns {object} - Updated props and HTML string with markup
 */
function entityMergeMarkup(props) {
  var initialState = props.initialState,
    rest = (0, _objectWithoutProperties2.default)(props, _excluded2);
  var rootReducer = createRootReducer(props.entityType, true);
  var store = (0, _redux.createStore)(rootReducer, Immutable.fromJS(initialState));
  var EntitySection = getEntitySectionMerge(props.entityType);
  var markup = _server.default.renderToString( /*#__PURE__*/React.createElement(_layout.default, propHelpers.extractLayoutProps(rest), /*#__PURE__*/React.createElement(_reactRedux.Provider, {
    store: store
  }, /*#__PURE__*/React.createElement(_entityMerge.default, (0, _extends2.default)({
    validate: getValidator(props.entityType)
  }, propHelpers.extractChildProps(rest)), /*#__PURE__*/React.createElement(EntitySection, null)))));
  return {
    markup: markup,
    props: Object.assign({}, props, {
      intitialState: store.getState()
    })
  };
}

/**
 * Callback for any transformations to the request body
 * @callback transformCallback
 * @param {object} body - request body
 */

/**
 * Handler for create or edit actions
 * @callback transformCallback
 * @param {object} req - request object
 * @param {object} res - response object
 */

/**
 * Makes a middleware handler for create or edit actions on entities.
 * @param {string} entityType - entity type
 * @param {transformCallback} transformNewForm - callback for transformations
 * @param {(string|string[])} propertiesToPick - props from transformed
 * request body to pick.
 * @param {boolean} isMergeHandler - Wether the submission was from a /merge/handler route
 * @returns {createOrEditHandler} createOrEditHandler - middleware handler
 */
function makeEntityCreateOrEditHandler(entityType, transformNewForm, propertiesToPick) {
  var isMergeHandler = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  var entityName = (0, _upperFirst2.default)(entityType);
  var validate = getValidator(entityType);
  return function createOrEditHandler(req, res) {
    var mergeQueue = req.session.mergeQueue;
    var isMergeOperation = isMergeHandler && mergeQueue && (0, _size2.default)(mergeQueue.mergingEntities) >= 2;
    if (!validate(req.body, null, isMergeOperation)) {
      var err = new error.FormSubmissionError();
      error.sendErrorAsJSON(res, err);
    }
    req.body = transformNewForm(req.body);
    return entityRoutes.handleCreateOrEditEntity(req, res, entityName, (0, _pick2.default)(req.body, propertiesToPick), isMergeOperation);
  };
}

/**
 * add an initial relationship to entity from another enitty
 * when one entity created from other.
 * @param {object} props - props related to new entity
 * @param {number} relationshipTypeId - relationshipId number for initaial relationship
 * @param {object} targetEntity - details about target entitiy like edition group, publisher and author
 * @param {number} relationshipIndex - initial relationship index number
 */

function addInitialRelationship(props, relationshipTypeId, relationshipIndex, targetEntity) {
  // Prepend 'i' here to indicate initail relationship row identifier
  var rowId = "i".concat(relationshipIndex || 0);
  var relationship = props.relationshipTypes.find(function (relationshipType) {
    return relationshipType.id === relationshipTypeId;
  });
  var targetEntityDetail = {
    bbid: targetEntity.id,
    defaultAlias: {
      name: targetEntity.text
    },
    type: targetEntity.type
  };
  var sourceEntityDetail = {
    defaultAlias: {
      name: ''
    },
    type: (0, _upperFirst2.default)(props.entityType)
  };
  var initialRelationship = {
    attributes: [],
    isAdded: true,
    label: relationship.linkPhrase,
    relationshipType: relationship,
    rowID: rowId,
    sourceEntity: targetEntity.type === 'EditionGroup' || targetEntity.type === 'Work' ? sourceEntityDetail : targetEntityDetail,
    targetEntity: targetEntity.type === 'EditionGroup' || targetEntity.type === 'Work' ? targetEntityDetail : sourceEntityDetail
  };
  if (!props.initialState.relationshipSection) {
    props.initialState.relationshipSection = {};
    props.initialState.relationshipSection.canEdit = true;
    props.initialState.relationshipSection.lastRelationships = null;
    props.initialState.relationshipSection.relationshipEditorProps = null;
    props.initialState.relationshipSection.relationshipEditorVisible = false;
  }
  props.initialState.relationshipSection.relationships = _objectSpread(_objectSpread({}, props.initialState.relationshipSection.relationships), {}, (0, _defineProperty2.default)({}, rowId, initialRelationship));
  return props;
}

/**
 * Return markup for the unified form.
 * This also modifies the props value with a new initialState!
 * @param {object} props - react props
 * @returns {object} - Updated props and HTML string with markup
 */

function unifiedFormMarkup(props) {
  var initialState = props.initialState,
    rest = (0, _objectWithoutProperties2.default)(props, _excluded3);
  var rootReducer = ufCreateRootReducer();
  var store = (0, _redux.createStore)(rootReducer, Immutable.fromJS(initialState));
  var markup = _server.default.renderToString( /*#__PURE__*/React.createElement(_layout.default, propHelpers.extractLayoutProps(rest), /*#__PURE__*/React.createElement(_reactRedux.Provider, {
    store: store
  }, /*#__PURE__*/React.createElement(_unifiedForm.default, props))));
  return {
    markup: markup,
    props: Object.assign({}, props, {
      initialState: store.getState()
    })
  };
}

/**
 * Returns a props object with reasonable defaults for unified form.
 * @param {request} req - request object
 * @param {response} res - response object
 * @param {object} additionalProps - additional props
 * @param {initialStateCallback} initialStateCallback - callback
 * to get the initial state
 * @returns {object} - props
 */
function generateUnifiedProps(req, res, additionalProps) {
  var initialStateCallback = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {
    return new Object();
  };
  var submissionUrl = '/create/handler';
  var props = Object.assign({
    allIdentifierTypes: res.locals.identifierTypes,
    entityType: 'edition',
    initialState: initialStateCallback(),
    isUnifiedForm: true,
    languageOptions: res.locals.languages,
    requiresJS: true,
    submissionUrl: submissionUrl
  }, additionalProps);
  return (0, _props2.generateProps)(req, res, props);
}

/**
 * Validate Unified form
 * @param {object} body - request body
 * @returns {boolean}
 */

function validateUnifiedForm(body) {
  for (var entityKey in body) {
    if (Object.prototype.hasOwnProperty.call(body, entityKey)) {
      var entityForm = body[entityKey];
      var entityType = (0, _camelCase2.default)(entityForm.type);
      var isNew = (0, _get2.default)(entityForm, '__isNew__', true);
      var bbid = (0, _get2.default)(entityForm, 'id', null);
      // for existing entity, it must have id attribute set to its bbid
      if (!isNew && (!bbid || !(0, _utils2.isValidBBID)(bbid))) {
        return false;
      }
      if (!entityType || !validEntityTypes.includes(entityType)) {
        return false;
      }
      var validator = getValidator(entityType);
      if (isNew && !validator(entityForm)) {
        return false;
      }
    }
  }
  return true;
}

/**
 * Middleware for handling unified form submission
 * @param {object} req - Request object
 * @param {object} res - Response object
 */
function createEntitiesHandler(_x, _x2) {
  return _createEntitiesHandler.apply(this, arguments);
}
function _createEntitiesHandler() {
  _createEntitiesHandler = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(req, res) {
    var orm, err;
    return _regenerator.default.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            orm = req.app.locals.orm; // generate the state for current entity
            _context.next = 3;
            return unifiedRoutes.preprocessForm(req.body, orm);
          case 3:
            req.body = _context.sent;
            if (validateUnifiedForm(req.body)) {
              _context.next = 7;
              break;
            }
            err = new error.FormSubmissionError();
            return _context.abrupt("return", error.sendErrorAsJSON(res, err));
          case 7:
            // transforming uf state into separate entity state
            req.body = unifiedRoutes.transformForm(req.body);
            return _context.abrupt("return", unifiedRoutes.handleCreateMultipleEntities(req, res));
          case 9:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _createEntitiesHandler.apply(this, arguments);
}
//# sourceMappingURL=entityRouteUtils.js.map