ForestAdmin/forest-express-sequelize

View on GitHub
src/services/resource-creator.js

Summary

Maintainability
A
1 hr
Test Coverage
A
98%
const _ = require('lodash');
const P = require('bluebird');
const Interface = require('forest-express');
const { ErrorHTTP422 } = require('./errors');
const ResourceGetter = require('./resource-getter');
const PrimaryKeysManager = require('./primary-keys-manager');
const associationRecord = require('../utils/association-record');
const isPrimaryKeyAForeignKey = require('../utils/is-primary-key-a-foreign-key');

class ResourceCreator {
  constructor(model, params, body, user) {
    this.model = model;
    this.params = params;
    this.body = body;
    this.schema = Interface.Schemas.schemas[model.name];
    this.user = user;
  }

  async _getTargetKey(name, association) {
    const primaryKey = this.body[name];

    let targetKey = primaryKey;
    if (primaryKey && association.targetKey !== association.target.primaryKeyAttribute) {
      const record = await associationRecord.get(association.target, primaryKey);
      targetKey = record[association.targetKey];
    }
    return targetKey;
  }

  async _makePromisesBeforeSave(record, [name, association]) {
    if (association.associationType === 'BelongsTo') {
      const setterName = `set${_.upperFirst(name)}`;
      const targetKey = await this._getTargetKey(name, association);
      const primaryKeyIsAForeignKey = isPrimaryKeyAForeignKey(association);
      if (primaryKeyIsAForeignKey) {
        record[association.source.primaryKeyAttribute] = this.body[name];
      }
      return record[setterName](targetKey, { save: false });
    }
    return null;
  }

  _makePromisesAfterSave(record, [name, association]) {
    let setterName;
    if (association.associationType === 'HasOne') {
      setterName = `set${_.upperFirst(name)}`;
    } else if (['BelongsToMany', 'HasMany'].includes(association.associationType)) {
      setterName = `add${_.upperFirst(name)}`;
    }
    if (setterName) {
      return record[setterName](this.body[name]);
    }
    return null;
  }

  async _handleSave(record, callback) {
    const { associations } = this.model;
    if (associations) {
      await P.all(Object.entries(associations)
        .map((entry) => callback.bind(this)(record, entry)));
    }
  }

  async perform() {
    // buildInstance
    const recordCreated = this.model.build(this.body);

    // handleAssociationsBeforeSave
    await this._handleSave(recordCreated, this._makePromisesBeforeSave);

    const scopeFilters = await Interface.scopeManager.getScopeForUser(
      this.user,
      this.model.name,
      true,
    );

    // saveInstance (validate then save)
    try {
      await recordCreated.validate();
    } catch (error) {
      throw new ErrorHTTP422(error.message);
    }
    const record = await recordCreated.save();

    // handleAssociationsAfterSave
    // NOTICE: Many to many associations have to be set after the record creation in order to
    //         have an id.
    await this._handleSave(record, this._makePromisesAfterSave);

    // appendCompositePrimary
    new PrimaryKeysManager(this.model).annotateRecords([record]);

    try {
      return await new ResourceGetter(
        this.model,
        { ...this.params, recordId: record[this.schema.idField] },
        this.user,
      ).perform();
    } catch (error) {
      if (error.statusCode === 404 && scopeFilters) {
        return record;
      }
      throw error;
    }
  }
}

module.exports = ResourceCreator;