"use strict";
import errors from "restify-errors";
import inflect from "inflect";
/**
* SQL Database dsl
*
* @class ORM
* @constructor
* @param {Object} models hash of <a href="http://docs.sequelizejs.com/en/v3/docs/models-definition/#definition" target="_blank">sequelize defined</a> models
*/
export default class ORM {
constructor(models) {
this._models = new Map();
Object.keys(models).forEach(model => {
const modelName = inflect.singularize(model).toLowerCase();
this._models.set(modelName, models[model]);
});
}
/**
* Create a new model instance
*
* @method createRecord
* @param {String} modelName lowercase singular model name
* @param {Object} payload new instance data
* @return {Promise}<ModelInstance, RestError>
* @example
*
* ```javascript
* return orm.createRecord("user", { firstName: "John" });
* ```
*/
createRecord(modelName, payload) {
if (!payload) {
return Promise.reject(
new errors.BadRequestError("Missing or invalid body")
);
}
const model = this._models.get(modelName);
const record = model.build(payload);
return record.validate().then(validation => {
if (validation && validation.errors && validation.errors.length) {
const validationErrors = validation.errors;
const validationError = validationErrors[0];
throw new errors.UnprocessableEntityError(validationError.message);
} else if (validation) {
throw new errors.UnprocessableEntityError(validation.message);
}
return record.save();
});
}
/**
* Destroy a record instance
*
* @method destroyRecord
* @param {String} modelName lowercase singular model name
* @param {Number|String} id id of the record to destroy
* @return {Promise<void>}
* @example
*
* ```javascript
* return orm.destroyRecord("user", 1);
* ```
*/
destroyRecord(modelName, id) {
return this.findOne(modelName, id)
.then(record => record.destroy());
}
/**
* Return all instances of a model and optionally pass a query object
*
* @method findAll
* @param {String} modelName lowercase singular model name
* @param {Object} where <a href="http://docs.sequelizejs.com/en/v3/docs/querying/#where" target="_blank">Sequelize Where clause</a>
* @param {Object} options <a href="http://docs.sequelizejs.com/en/v3/api/model/#findoneoptions-promiseinstance" target="_blank">
* sequelize finder options
* </a>
* @return {Promise}<ModelInstance, RestError>
* @example
*
* ```javascript
* return orm.findAll("user");
*
* // you can also query
*
* return orm.findAll("user", { firstName: { $like: "john" }});
* ```
*/
findAll(modelName, where, options) {
const dataQuery = { where };
const model = this._models.get(modelName);
const modelQuery = Object.assign(dataQuery, options);
return model.findAll(modelQuery);
}
/**
* Return a single instance by id
*
* @method findOne
* @param {String} modelName lowercase singular model name
* @param {Number|String} id id of the record to destroy
* @param {Object} options <a href="http://docs.sequelizejs.com/en/v3/api/model/#findoneoptions-promiseinstance" target="_blank">
* sequelize finder options
* </a>
* @return {Promise}<ModelInstance, RestError>
* @example
*
* ```javascript
* return orm.findOne("user", 1);
* ```
*/
findOne(modelName, id, options) {
const dataQuery = { id };
return this.queryRecord(modelName, dataQuery, options);
}
/**
* Like findOne but takes a query instead of an id
*
* @method queryRecord
* @param {String} modelName lowercase singular model name
* @param {Object} where <a href="http://docs.sequelizejs.com/en/v3/docs/querying/#where" target="_blank">Sequelize Where clause</a>
* @param {Object} options <a href="http://docs.sequelizejs.com/en/v3/api/model/#findoneoptions-promiseinstance" target="_blank">
* sequelize finder options
* </a>
* @return {Promise}<ModelInstance, RestError>
* @example
*
* ```javascript
* return orm.queryRecord("user", { firstName: "John" });
* ```
*/
queryRecord(modelName, where, options) {
const dataQuery = { where };
const model = this._models.get(modelName);
const modelQuery = Object.assign(dataQuery, options);
return model.findOne(modelQuery).then(record => {
if (!record) {
const NotFound = errors.NotFoundError;
const message = `${modelName} does not exist`;
throw new NotFound(message);
}
return record;
});
}
/**
* Update a record
*
* @method updateRecord
* @param {String} modelName lowercase singular model name
* @param {Number|String} id id of the record to destroy
* @param {Object} payload new instance data
* @return {Promise}<ModelInstance, RestError>
* @example
*
* ```javascript
* return orm.updateRecord("user", 1, { firsName: "Joe" });
* ```
*/
updateRecord(modelName, id, payload) {
if (!payload) {
return Promise.reject(
new errors.BadRequestError("Missing or invalid body")
);
}
return this.findOne(modelName, id)
.then(record => record.update(payload))
.catch(err => {
/**
* HACK: if this is a sequelize validation error, we transform it, otherwise
* we can't be totally sure so just throw it up the stack
*/
if (err.name === "SequelizeValidationError") {
const { errors: [validationError] } = err;
const error = errors.UnprocessableEntityError;
const message = validationError.message;
throw new error(message);
} else {
throw err;
}
});
}
}