"use strict";
const validator_1 = require('../validator');
const validation = new validator_1.Validator();
const debug = require('debug')('loopback:connector:jira.resource.baseResource');
const _ = require('lodash');
const UrlPattern = require('url-pattern');
const api = require(`./definition/api`);
function getToken(ctx) {
let req = ctx.req;
if (req.headers && req.headers.authorization) {
let parts = req.headers.authorization.split(' ');
if (parts.length !== 2 || !/^Bearer$/i.test(parts[0])) {
return null;
}
return parts[1];
}
else {
if (req.query && req.query.access_token) {
return req.query.access_token;
}
}
}
/**
* Represents a base class for all jira resources
*
* @constructor baseResource
* @param settings The information needed to setup the resource
* @param {string} settings.name The name of jira model.
* @param {*} settings.request The request function to use.
* @param {jiraApi} settings.jiraApi The jiraApi library to use.
*/
class baseResource {
constructor(connector, Model, settings) {
/**
* register
* registers all the api methods with the model
*/
this.register = () => {
// "this.methods" is defined in the class itself
Object.keys(this.definition.methods).forEach((key) => {
let method = this.definition.methods[key].remoteMethod;
if (!method) {
return;
}
if (key === "login" || key === "logout") {
this.model[key] = this.connector[key];
}
else {
this.model[key] = this[key];
}
this.model.remoteMethod(key, method);
});
};
/**
* makeRequest
* construct an http request based on the method definitions
* @param {string} method The method to use
* @param {string} verb The http method to use (GET , PUT etc)
* @param {string} url The method api url
* @param {Object} options The data to send in the request
* @param [callback] if supplied, called with result of api call
* @return {Promise.<any>} result of api call
*/
this.makeRequest = (method, verb, url, options = {}, callback) => {
let methodDefinition = this.definition.methods[method];
let path;
let pattern = new UrlPattern(url);
// check out validations before making the request
if (methodDefinition.rules) {
let result = validation.validate(options, methodDefinition.rules);
if (result !== true) {
let err = { statusCode: 422, message: result };
return callback ? callback(err) : Promise.reject(err);
}
}
// fill path with appropriate variables
try {
path = pattern.stringify(options);
}
catch (e) {
let err = { statusCode: 422, message: e.message };
return callback ? callback(err) : Promise.reject(err);
}
let token = options.token;
delete options.token;
let requestData = {
method: verb,
token,
path,
qs: {},
body: {},
tokenRequired: methodDefinition.tokenRequired
};
methodDefinition.queryParams.forEach((param) => {
if (param.name in options) {
requestData.qs[param.name] = options[param.name];
}
});
methodDefinition.schema.forEach((param) => {
if (param.name in options) {
requestData.body[param.name] = options[param.name];
}
});
if (requestData.body === {}) {
delete requestData.body;
}
if (requestData.qs === {}) {
delete requestData.qs;
}
requestData._description = methodDefinition.description;
return this.connector.makeRequest(requestData, callback);
};
this.name = Model.modelName;
this.model = Model;
this.settings = settings;
this.definition = baseResource.loadDefinition(this.name, this.settings.customFolder);
this.connector = connector;
}
}
/**
* loadCustom
* try to load custom definitions from a file. the definitions are applied in "method <- nodeable <- custom" order
* so that an end-user can override the standard (generated) defaults and the nodeable defaults
* @param {string} fileName The name of the definition to load
* @return {Object} the json data in the file (if it exists) or an empty object {}
*/
baseResource.loadCustom = (fileName) => {
let override = {
methods: {}
};
if (!fileName) {
return {};
}
// try to load the override definitions
try {
override = require(fileName);
override.methods = override.methods || {};
}
catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
console.log("ERROR: unable to process custom model definition %s", fileName, e);
}
}
return override;
};
/**
* loadDefinition
* each model can have a defintion file that overrides the generated settings in api.json
* for example, the strongloop remote api path can be changed to something more suitable
*
* @param {string} settings.name The name of jira model.
*/
baseResource.loadDefinition = (modelName, customFolder) => {
let definition = api[modelName];
// special case for User - we want to merge the login / logout methods from Session into the User model
if (modelName === "User") {
let sessionApi = api['Session'];
Object.keys(sessionApi.methods).forEach((key) => {
let method = sessionApi.methods[key];
if (method.name === 'currentUser') {
return;
}
definition.methods[method.name] = method;
});
}
let custom = _.merge({}, baseResource.loadCustom(`./definition/${modelName}`), customFolder ? baseResource.loadCustom(`${customFolder}/${modelName}`) : null);
_.merge(definition, custom);
// default the model visibility to true
definition.public = ('public' in definition) ? definition.public : true;
Object.keys(definition.methods).forEach((key) => {
let method = definition.methods[key];
method.tokenRequired = ('tokenRequired' in method) ? method.tokenRequired : true;
method.public = ('public' in method) ? method.public : true;
if (!method.public) {
return;
}
let remoteMethod = {
accepts: [],
name: method.name,
description: method.description,
notes: method.details ? method.details.split('\n') : null,
http: {
verb: method.verb,
path: method.path
},
returns: { arg: 'data', type: method.responseType, root: true }
};
// run through all parameters, building up an `accepts` array
let params = [...method.urlParams, ...method.queryParams];
if (method.schema.length > 0) {
params.push({ arg: "data", name: "data", type: 'object', http: { source: 'body' } });
}
else {
if (method.tokenRequired) {
params.push({ arg: "token", type: "string", http: getToken });
}
}
params.forEach((param) => {
remoteMethod.accepts.push({ arg: param.name || param.arg, name: param.name || param.arg, type: param.type, description: param.description, http: param.http });
});
definition.methods[method.name].remoteMethod = remoteMethod;
});
return definition;
};
exports.baseResource = baseResource;