"use strict";
var Cursor = require("../cursor"),
Provider = require("../provider"),
Strings = require("../strings"),
Err = require("../utils/error"),
object = require("../utils/object"),
url = require("url"),
util = require("util"),
_ = require("lodash");
// *** Cursor Implementation ***
function EverliveCursor(provider, query, options) {
this._result = null;
this._shouldSetId = provider._getIdKey() !== "Id";
this.current = 0;
Cursor.call(this, provider, query, options);
}
util.inherits(EverliveCursor, Cursor);
EverliveCursor.prototype.reset = function () {
this.current = 0;
if (this.mapping && _.isFunction(this.mapping)) {
this._mapFunc = true;
}
return this;
};
EverliveCursor.prototype._map = function (item) {
if (item && this._mapFunc) {
return this.mapping(item);
}
return item;
};
EverliveCursor.prototype._nextObject = function (callback) {
if (!this._result) {
return this.provider.handleError(
new Error("Inconsistent cursor state."),
callback
);
}
var item = this._result[this.current++];
if (this._shouldSetId && item && item.Id) {
this.provider._setId(item, item.Id);
}
callback(null, this._map(item));
};
EverliveCursor.prototype.count = function (callback) {
if (this._result) {
return callback(null, this._result.length);
}
var that = this,
opts = _.cloneDeep(that.provider._reqOpts),
req;
opts.method = "GET";
opts.path += "/_count";
if (that.query) {
opts.headers["X-Everlive-Filter"] = JSON.stringify(that.query);
}
req = that.provider.http.request(opts, function (res) {
that.provider._handleResponse(res, function (err, data) {
if (err) {
return callback(err);
}
callback(null, data.Result);
});
});
req.on("error", function (err) {
that.provider.handleError(err, callback);
});
req.end();
};
EverliveCursor.prototype.update = function (data, callback) {
this._exec(callback, "PUT", data);
return this;
};
EverliveCursor.prototype.delete = function (callback) {
this._exec(callback, "DELETE");
return this;
};
EverliveCursor.prototype._exec = function (callback, method, data) {
var that = this,
opts = _.cloneDeep(that.provider._reqOpts),
fields,
payload,
req;
opts.method = method || "GET";
if (that.query) {
opts.headers["X-Everlive-Filter"] = JSON.stringify(that.query);
}
if (that.skipValue) {
opts.headers["X-Everlive-Skip"] = that.skipValue;
}
if (that.limitValue) {
opts.headers["X-Everlive-Take"] = that.limitValue;
}
if (that.mapping && !_.isFunction(that.mapping)) {
if (_.isArray(that.mapping)) {
fields = {};
_.each(that.mapping, function (itm) {
fields[itm] = 1;
});
} else {
fields = that.mapping;
}
opts.headers["X-Everlive-Fields"] = JSON.stringify(fields);
}
if (that.criteria) {
if (_.isFunction(that.criteria)) {
return callback(new Error("Everlive provider does not support custom comparers."));
}
opts.headers["X-Everlive-Sort"] = JSON.stringify(that.criteria);
}
req = that.provider.http.request(opts, function (res) {
that.provider._handleResponse(res, function (err, data) {
if (err) {
return callback(err);
}
if (method) {
return callback(null, data.Result);
}
that._result = data.Result;
callback();
});
});
req.on("error", function (err) {
that.provider.handleError(err, callback);
});
if (data) {
payload = JSON.stringify(data);
opts.headers["Content-Length"] = Buffer.byteLength(payload, "utf8");
req.write(payload);
}
req.end();
};
// *** Provider Implementation ***
/**
* Constructor for the [Everlive](http://www.telerik.com/backend-services) provider. This class inherits from {@link Provider}.
*
* Each data collection in EntreeJS represents a data provider instance, therefore an
* Everlive provider instance corresponds to an Everlive [type](http://docs.telerik.com/platform/backend-services/getting-started/types-and-fields).
*
* For usage reference, please refer to [Provider documentation]{@link Provider}.
*
* Options
*
* - `connStr` {string} <required> - Connection string. The base URL to Everlive service, including the API Key.
* **Example:** `"http://api.everlive.com/v1/uZEGyZYKiSq5CTSq/"`
* - `authorization` {string} <optional> - The value of the authorization HTTP header if the service is protected.
* **Example:** `"MasterKey PqmmvlWWBF5svReW7p3mkYG9X61nus1w"`
* - `overrideSystemFields` {boolean} <optionanl> - Allows to override of system fields such as: owner, modifiedAt, modifiedBy and etc.
*
* @class Represents a provider for [Everlive](http://www.telerik.com/backend-services) cloud storage.
* @param {object} opts - Additional options. The only required option is `connStr`.
* @param {object=} schema - Defines the fields of the collection represented by the provider instance.
*/
function EverliveProvider(opts, schema) {
Provider.call(this, opts, schema);
var path = url.resolve(opts.connStr, schema.__collName);
this._reqOpts = url.parse(path);
this._reqOpts.headers = { "Content-Type": "application/json; charset=utf-8" };
if (opts.authorization) {
this._reqOpts.headers.Authorization = opts.authorization;
}
if (opts.overrideSystemFields) {
this._reqOpts.headers["x-everlive-override-system-fields"] = "true";
}
if (this._reqOpts.protocol === "https:") {
this.http = require("https");
} else {
this.http = require("http");
}
}
util.inherits(EverliveProvider, Provider);
EverliveProvider.prototype._handleResponse = function (res, callback) {
var that = this,
result = "";
res.setEncoding("utf8");
res.on("data", function (data) {
result += data;
});
if (res.statusCode === 200 || res.statusCode === 201 || res.statusCode === 202) {
if (res.headers["content-length"]) {
res.on("end", function () {
callback(null, JSON.parse(result));
});
} else {
callback();
}
} else {
if (res.headers["content-length"]) {
res.on("end", function () {
if (result.indexOf("\"errorCode\":801") !== -1) {
result = "ITEM_DOESNOT_EXIST";
}
that.handleError(result, callback);
});
} else {
that.handleError("Request failed with status code: " + res.statusCode, callback);
}
}
};
EverliveProvider.prototype._setIds = function (items, source) {
var that = this,
idKey = that._getIdKey(),
i;
function setEvId(item) {
var id;
if ((id = that._getId(item)) !== undefined) {
item.Id = id;
}
}
if (idKey !== "Id") {
if (_.isArray(items)) {
for (i = 0; i < items.length; i++) {
if (source) {
that._setId(items[i], source[i].Id);
} else {
setEvId(items[i]);
}
}
} else {
if (source) {
that._setId(items, source.Id);
} else {
setEvId(items);
}
}
}
};
EverliveProvider.prototype._insert = function (items, callback) {
var that = this,
opts = _.cloneDeep(this._reqOpts),
payload,
req;
that._setIds(items);
payload = JSON.stringify(items);
opts.method = "POST";
opts.headers["Content-Length"] = Buffer.byteLength(payload, "utf8");
req = this.http.request(opts, function (res) {
that._handleResponse(res, function (err, data) {
var i;
if (err) {
if (err.message.indexOf("\"errorCode\":107") !== -1) {
err = new Err("OPERS_NOT_ALLOWED");
}
return callback(err, data);
}
that._setIds(items, data.Result);
if (Array.isArray(items)) {
for (i = 0; i < items.length; i++) {
_.extend(items[i], data.Result[i]);
}
} else {
_.extend(items, data.Result);
}
callback(err, items);
});
});
req.on("error", function (err) {
that.handleError(err, callback);
});
req.write(payload);
req.end();
};
EverliveProvider.prototype._update = function (item, callback) {
var that = this,
idKey = this._getIdKey(),
id = this._getId(item),
opts = _.cloneDeep(this._reqOpts),
payload,
req,
itm,
set;
if (!id) {
return this.handleError(Strings.MISSING_ID, callback);
}
if (item[idKey]) {
itm = true;
delete item[idKey];
}
if (item.$set && item.$set[idKey]) {
set = true;
delete item.$set[idKey];
}
payload = JSON.stringify(item);
opts.method = "PUT";
opts.headers["Content-Length"] = Buffer.byteLength(payload, "utf8");
opts.path += "/" + id;
req = this.http.request(opts, function (res) {
that._handleResponse(res, function (err, data) {
if (err) {
return callback(err, data);
}
if (itm) {
item[idKey] = id;
}
if (set) {
item.$set[idKey] = id;
}
if (typeof data.Result === "object") {
_.extend(item, data.Result);
}
callback(err, item);
});
});
req.on("error", function (err) {
that.handleError(err, callback);
});
req.write(payload);
req.end();
};
EverliveProvider.prototype._upsert = function (item, callback) {
var that = this;
this._get(item, function (err, data) {
if (data) {
that._update(item, callback);
} else {
var id = that._getId(item),
idKey = that._getIdKey();
try {
item = object.update(item, {}, idKey);
} catch (err) {
return callback(err);
}
item[idKey] = id;
that._insert(item, callback);
}
});
};
EverliveProvider.prototype._delete = function (item, callback) {
var that = this,
id = that._getId(item),
opts = _.cloneDeep(this._reqOpts),
req;
if (!id) {
return this.handleError(Strings.MISSING_ID, callback);
}
opts.method = "DELETE";
opts.path += "/" + id;
req = this.http.request(opts, function (res) {
that._handleResponse(res, callback);
});
req.on("error", function (err) {
that.handleError(err, callback);
});
req.end();
};
EverliveProvider.prototype._get = function (item, callback) {
var that = this,
opts = _.cloneDeep(this._reqOpts),
id = that._getId(item),
req;
if (!id) {
return that.handleError(Strings.MISSING_ID, callback);
}
opts.method = "GET";
opts.path += "/" + id;
req = this.http.request(opts, function (res) {
that._handleResponse(res, function (err, data) {
if (err) {
return callback(err, data);
}
if (that._getIdKey() !== "Id" && data.Result.Id) {
that._setId(data.Result, data.Result.Id);
}
callback(err, data.Result);
});
});
req.on("error", function (err) {
that.handleError(err, callback);
});
req.end();
};
EverliveProvider.prototype._select = function (args, callback) {
var cursor = new EverliveCursor(this, args.query, args.options);
if (callback) {
callback(null, cursor);
} else {
return cursor;
}
};
module.exports = EverliveProvider;