2017-08-30 16:25:20 -07:00

437 lines
9.3 KiB
JavaScript

let $http;
let $q;
let cache;
function request (method, resource) {
if (Array.isArray(method)) {
let promises = method.map((_method_, i) => {
return this.request(_method_, Array.isArray(resource) ? resource[i] : resource);
});
return $q.all(promises);
}
if (this.isCacheable(method, resource)) {
return this.requestWithCache(method, resource);
}
return this.http[method](resource);
}
function requestWithCache (method, resource) {
let key = cache.createKey(method, this.path, resource);
return cache.get(key)
.then(data => {
if (data) {
this.model[method.toUpperCase()] = data;
return data;
}
return this.http[method](resource)
.then(res => {
cache.put(key, res.data);
return res;
});
});
}
/**
* Intended to be useful in searching and filtering results using params
* supported by the API.
*
* @arg {Object} params - An object of keys and values to to format and
* to the URL as a query string. Refer to the API documentation for the
* resource in use for specifics.
* @arg {Object} config - Configuration specific to the UI to accommodate
* common use cases.
*
* @yields {boolean} - Indicating a match has been found. If so, the results
* are set on the model.
*/
function search (params, config) {
let req = {
method: 'GET',
url: this.path,
params
};
return $http(req)
.then(({ data }) => {
if (!data.count) {
return false;
}
if (config.unique) {
if (data.count !== 1) {
return false;
}
this.model.GET = data.results[0];
} else {
this.model.GET = data;
}
return true;
});
}
function httpGet (resource) {
let req = {
method: 'GET',
url: this.path
};
if (typeof resource === 'object') {
this.model.GET = resource;
return $q.resolve();
} else if (resource) {
req.url = `${this.path}${resource}/`;
}
return $http(req)
.then(res => {
this.model.GET = res.data;
return res;
});
}
function httpPost (data) {
let req = {
method: 'POST',
url: this.path,
data
};
return $http(req).then(res => {
this.model.GET = res.data;
return res;
});
}
function httpPut (changes) {
let model = Object.assign(this.get(), changes);
let req = {
method: 'PUT',
url: `${this.path}${model.id}/`,
data: model
};
return $http(req).then(res => res);
}
function httpOptions (resource) {
let req = {
method: 'OPTIONS',
url: this.path
};
if (resource) {
req.url = `${this.path}${resource}/`;
}
return $http(req)
.then(res => {
this.model.OPTIONS = res.data;
return res;
});
}
function options (keys) {
return this.find('options', keys);
}
function get (keys) {
return this.find('get', keys);
}
function unset (method, keys) {
if (!keys) {
keys = method;
method = 'GET';
}
method = method.toUpperCase();
keys = keys.split('.');
if (!keys.length) {
delete this.model[method];
} else if (keys.length === 1) {
delete this.model[method][keys[0]];
} else {
let property = keys.splice(-1);
keys = keys.join('.');
let model = this.find(method, keys)
delete model[property];
}
}
function set (method, keys, value) {
if (!value) {
value = keys;
keys = method;
method = 'GET';
}
keys = keys.split('.');
if (keys.length === 1) {
model[keys[0]] = value;
} else {
let property = keys.splice(-1);
keys = keys.join('.');
let model = this.find(method, keys)
model[property] = value;
}
}
function match (method, key, value) {
if(!value) {
value = key;
key = method;
method = 'GET';
}
let model = this.model[method.toUpperCase()];
if (!model) {
return null;
}
if (!model.results) {
if (model[key] === value) {
return model;
}
return null;
}
let result = model.results.filter(result => result[key] === value);
return result.length === 0 ? null : result[0];
}
function find (method, keys) {
let value = this.model[method.toUpperCase()];
if (!keys) {
return value;
}
try {
keys = keys.split('.');
keys.forEach(key => {
let bracketIndex = key.indexOf('[');
let hasArray = bracketIndex !== -1;
if (!hasArray) {
value = value[key];
return;
}
if (bracketIndex === 0) {
value = value[Number(key.substring(1, key.length - 1))];
return;
}
let prop = key.substring(0, bracketIndex);
let index = Number(key.substring(bracketIndex + 1, key.length - 1));
value = value[prop][index];
});
} catch (err) {
return undefined;
}
return value;
}
function has (method, keys) {
if (!keys) {
keys = method;
method = 'GET';
}
method = method.toUpperCase();
let value;
switch (method) {
case 'OPTIONS':
value = this.options(keys);
break;
default:
value = this.get(keys);
}
return value !== undefined && value !== null;
}
function extend (method, related) {
if (!related) {
related = method
method = 'GET'
} else {
method = method.toUpperCase()
}
if (this.has(method, `related.${related}`)) {
let id = this.get('id')
let req = {
method,
url: this.get(`related.${related}`)
};
return $http(req)
.then(({data}) => {
this.set(method, `related.${related}`, data);
return this;
})
}
return Promise.reject(new Error(`No related property, ${related}, exists`));
}
function normalizePath (resource) {
let version = '/api/v2/';
return `${version}${resource}/`;
}
function isEditable () {
let canEdit = this.get('summary_fields.user_capabilities.edit');
if (canEdit) {
return true;
}
if (this.has('options', 'actions.PUT')) {
return true;
}
return false;
}
function isCreatable () {
if (this.has('options', 'actions.POST')) {
return true;
}
return false;
}
function isCacheable () {
if (this.settings.cache === true) {
return true;
}
return false;
}
function graft (id) {
let item = this.get('results').filter(result => result.id === id);
item = item ? item[0] : undefined;
if (!item) {
return undefined;
}
return new this.Constructor('get', item, true);
}
/**
* `create` is called on instantiation of every model. Models can be
* instantiated empty or with `GET` and/or `OPTIONS` requests that yield data.
* If model data already exists a new instance can be created (see: `graft`)
* with existing data.
*
* @arg {string=} method - Populate the model with `GET` or `OPTIONS` data.
* @arg {(string|Object)=} resource - An `id` reference to a particular
* resource or an existing model's data.
* @arg {boolean=} graft - Create a new instance from existing model data.
*
* @returns {(Object|Promise)} - Returns a reference to the model instance
* if an empty instance or graft is created. Otherwise, a promise yielding
* a model instance is returned.
*/
function create (method, resource, graft, config) {
if (!method) {
return this;
}
this.promise = this.request(method, resource, config);
if (graft) {
return this;
}
return this.promise
.then(() => this);
}
/**
* Base functionality for API interaction.
*
* @arg {string} path - The API resource for the model extending BaseModel to
* use.
* @arg {Object=} settings - Configuration applied to all instances of the
* extending model.
* @arg {boolean=} settings.cache - Cache the model data.
*
*/
function BaseModel (path, settings) {
this.create = create;
this.find = find;
this.get = get;
this.graft = graft;
this.has = has;
this.isEditable = isEditable;
this.isCacheable = isCacheable;
this.isCreatable = isCreatable;
this.match = match;
this.normalizePath = normalizePath;
this.options = options;
this.request = request;
this.requestWithCache = requestWithCache;
this.search = search;
this.set = set;
this.unset = unset;
this.extend = extend;
this.http = {
get: httpGet.bind(this),
options: httpOptions.bind(this),
post: httpPost.bind(this),
put: httpPut.bind(this),
};
this.model = {};
this.path = this.normalizePath(path);
this.settings = settings || {};
};
function BaseModelLoader (_$http_, _$q_, _cache_) {
$http = _$http_;
$q = _$q_;
cache = _cache_;
return BaseModel;
}
BaseModelLoader.$inject = ['$http', '$q', 'CacheService'];
export default BaseModelLoader;