Source: provider/lingotekProvider.js

Source: provider/lingotekProvider.js

/**
 * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
 * Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
 */
/* globals app, module, __dirname, Promise, process, console, require */
/* jshint esversion: 6 */

var request = require('request'),
	fs = require('fs');

//
// NOTE: re-implement this object based on server and API requirements
//
var port = process.env.CECLSP_PORT || 8084,
	lingotekServerPrefix = 'https://myaccount.lingotek.com/api',
	urls = {
		COMMUNITY: lingotekServerPrefix + '/community',
		PROJECT: lingotekServerPrefix + '/project',
		DOCUMENT: lingotekServerPrefix + '/document',
		WORKFLOW: lingotekServerPrefix + '/workflow'
	};

// get the proxy server from the environment, if any
var HTTPS_PROXY = process.env.HTTPS_PROXY;
var HTTP_PROXY = process.env.HTTP_PROXY;
var addProxy = function (options, url) {
	// get appropriate proxy based on protocol (default to the other one if not specified)
	var proxy = url.indexOf('https') === 0 ? (HTTPS_PROXY || HTTP_PROXY) : (HTTP_PROXY || HTTPS_PROXY);
	if (proxy) {
		options.proxy = proxy;
	}
};

/**
 * Connector's interface to the Language Service Provider.</br>
 * Handles all the required GET/POST/DELETE requests.
 * @constructor
 * @alias ConnectorApi
 */
var ConnectorInterface = function () {};
ConnectorInterface.prototype = {
	getAccountInfo: function (authToken) {
		return Promise.reject('connectorApi.getAccountInfo(): not implemented');
	},
	getLocales: function (authToken) {
		return Promise.reject('connectorApi.getLocales(): not implemented');
	},
	patchDocument: function (authToken, id, args) {
		return Promise.reject('connectorApi.patchDocument(): not implemented');
	},
	deleteDocument: function (authToken, id) {
		return Promise.reject('connectorApi.deleteDocument(): not implemented');
	},
	/**
	 * Import a document into the Language Service Provider.
	 * 
	 * @param {string} projectId Identifier of the project that will include the document.
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} name Name of the document.
	 * @param {string} content JSON content of the document.
	 * @param {string} sourceLocale The source locale for the document.
	 * @param {string} additionalData Additional Data to apply on each document.
	 * @returns {Promise.<object>} The response from the Language Service Provider.
	 */
	addDocument: function (projectId, authToken, name, content, sourceLocale, additionalData) {
		var error = '';

		if (!projectId || !authToken, !name || !content || !sourceLocale) {
			// invalid parameters
			error = 'required parameters: projectId, authToken, name, content';
		} else {
			// validate content is JSON & not empty
			try {
				var contentJSON = JSON.parse(content);
				if (Object.keys(contentJSON).length === 0) {
					// ToDo: create a "translated" document for this as it's already translated
					error = 'content parameter JSON must not be an empty object';
				}
			} catch (e) {
				error = 'content parameter must be JSON';
			}
		}

		// if there is an error, report it
		if (error) {
			return Promise.reject('connectorApi.addDocument(): ' + error);
		}

		// Remove file extension from name
		var nameLessExt = name && name.replace(/\.[^.]+$/, "");
		// console.log('lingotekProvider name', name, 'nameLessExt', nameLessExt);

		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.DOCUMENT,
				auth: {
					bearer: authToken
				},
				form: {
					'project_id': projectId,
					'locale_code': sourceLocale,
					'format': 'JSON',
					'charset': 'UTF-8',
					'title': nameLessExt,
					'content': content
				}
			};

			if (Array.isArray(additionalData)) {
				additionalData.forEach(function(item) {
					options.form[item.parameter] = item.value;
				});
			}

			addProxy(options, options.url);
			request.post(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					body = JSON.parse(body);
					return resolve(body);
				} else {
					console.log('addDocument failure data', body);
					return reject(body);
				}
			});
		});
	},
	addBinaryFile: function (projectId, authToken, binaryFileSpec, file, sourceLocale, additionalData) {
		var error = '',
			format = '';

		if (!projectId || !authToken, !file.name || !sourceLocale) {
			// invalid parameters
			error = 'required parameters: projectId, authToken, name';
			return Promise.reject('connectorApi.addBinaryFile(): ' + error);
		} else {
			var ext = '';
			var extIndex = file.name.lastIndexOf('.');
			if (extIndex > 0) {
				ext = file.name.substring(extIndex + 1);
				format = ext.toUpperCase();
			}
		}

		return new Promise(function (resolve, reject) {
			//
			var options = {
				url: urls.DOCUMENT,
				auth: {
					bearer: authToken
				},
				headers: {
					'Content-Type': 'multipart/form-data'
				}
			};


			addProxy(options, options.url);
			var multiPartFormRequest = request.post(options, function (error, response, body) {
				if (error) {
					console.log('lingotekProvider addBinaryFile 1 error', error);
					return reject(error);
				}

				var data;
				try {
					data = JSON.parse(body);
				} catch (err) {
					data = body;
				}
				if (response && response.statusCode >= 200 && response.statusCode < 300) {
					return resolve(data);
				} else {
					console.log('addBinaryFile failure data', data);
					return reject(data);
				}
			});

			//
			// populate the form body
			var form = multiPartFormRequest.form();
			form.append('project_id', projectId);
			form.append('locale_code', sourceLocale);
			form.append('format', format);
			form.append('title', file.name);
			form.append('content', fs.createReadStream(binaryFileSpec));

			if (Array.isArray(additionalData)) {
				additionalData.forEach(function(item) {
					form.append(item.parameter, item.value);
				});
			}
		});
	},
	/**
	 * Get the document identified by the Language service provider Id
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} id Language Service Provider identifier for the document.
	 * @returns {Promise.<string>} The content of the document.
	 */
	getDocument: function (authToken, id) {
		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.DOCUMENT + '/' + id,
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var document = JSON.parse(body);
					return resolve(document);
				} else {
					console.log('lingotekProvider getDocument error', error, 'httpResponse.statusCode', httpResponse.statusCode, 'body', body);
					return reject(body);
				}
			});
		});
	},
	/**
	 * Get the document identified by the Language service provider Id
	 *
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} id Language Service Provider identifier for the document.
	 * @returns {Promise.<string>} The content of the document.
	 */
	getDocumentImportProcess: function (authToken, id) {
		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.DOCUMENT + '/' + id + '/import-process',
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var document = JSON.parse(body);
					return resolve(document);
				} else {
					console.log('lingotekProvider getDocument error', error, 'httpResponse.statusCode', httpResponse.statusCode, 'body', body);
					return reject(body);
				}
			});
		});
	},
	documentExists: function (authToken, id) {
		return Promise.reject('connectorApi.documentExists(): not implemented');
	},
	/**
	 * Get the statuses of all the locales of the document in the Language Service Provider.
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} id Language Service Provider identifier for the document.
	 * @returns {Promise.<object>} The Language Service Provider GET "/:id/translation" response.
	 */
	getDocumentTranslationStatuses: function (authToken, id) {
		// confirm parameters
		if (!authToken || !id) {
			return Promise.reject('connectorApi.getDocumentTranslationStatuses(): required parameters: authToken, id');
		}

		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.DOCUMENT + '/' + id + '/translation',
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var translation = JSON.parse(body);
					return resolve(translation);
				} else {
					return reject(error);
				}
			});
		});
	},
	/**
	 * Get the status of the specified locale of the document in the Language Service Provider.
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} id Language Service Provider identifier for the document.
	 * @param {string} locale Name of the locale to check.
	 * @returns {Promise.<object>} The Language Service Provider GET "/:id/translation" response specific for the requested locale.
	 */
	getDocumentTranslationStatus: function (authToken, id, locale) {
		// confirm parameters
		if (!authToken || !id) {
			return Promise.reject('connectorApi.getTranslationStatus(): required parameters: authToken, id');
		}

		return this.getDocumentTranslationStatuses(id).then(function (translationStatuses) {
			var translations = translationStatuses.entities || [],
				translation;

			for (var i = 0; i < translations.length; i++) {
				if (translations[i].locale_code === locale) {
					translation = translations[i];
					break;
				}
			}

			return Promise.resolve(translation);
		});
	},
	getDocumentInfo: function (authToken, id) {
		return Promise.reject('connectorApi.getDocumentInfo(): not implemented');
	},
	getDocumentStatus: function (authToken, id) {
		return Promise.reject('connectorApi.getDocumentStatus(): not implemented');
	},
	/**
	 * Request the document be translated into the given locale.
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} projectId Identifier of the project that will include the document.
	 * @param {string} id Language Service Provider identifier for the document.
	 * @param {string} locale Name of the locale to check.
	 * @returns {Promise.<object>} The Language Service Provider POST "/:id/translation" response.
	 */
	addTranslation: function (authToken, projectId, id, locale) {
		// confirm parameters
		if (!authToken || !projectId || !id || !locale) {
			return Promise.reject('connectorApi.addTranslation(): required parameters: authToken, projectId, id, locale');
		}

		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.DOCUMENT + '/' + id + '/translation',
				auth: {
					bearer: authToken
				},
				form: {
					'locale_code': locale
				}
			};
			addProxy(options, options.url);
			request.post(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					body = JSON.parse(body);
					return resolve(body);
				} else {
					return reject(error);
				}
			});
		});

	},
	/**
	 * Get the translation of the document for the given locale.
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} id Language Service Provider identifier for the document.
	 * @param {string} locale Name of the locale to check.
	 * @returns {Promise.<string>} The Language Service Provider GET "/:id/translation/:locale" response.
	 */
	getTranslation: function (authToken, id, locale) {
		// confirm parameters
		if (!authToken || !id || !locale) {
			return Promise.reject('connectorApi.getTranslation(): required parameters: authToken, id, locale');
		}

		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.DOCUMENT + '/' + id + '/content?locale_code=' + locale + '&use_source=true',
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var translation = JSON.parse(body);
					return resolve(translation);
				} else {
					return reject(error);
				}
			});
		});
	},
	getBinaryTranslation: function (authToken, id, locale) {
		// confirm parameters
		if (!authToken || !id || !locale) {
			return Promise.reject('connectorApi.getTranslation(): required parameters: authToken, id, locale');
		}

		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.DOCUMENT + '/' + id + '/content?locale_code=' + locale + '&use_source=true',
				auth: {
					bearer: authToken
				},
				encoding: 'binary'
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					return resolve(body);
				} else {
					return reject(error);
				}
			});
		});
	},
	deleteTranslation: function (authToken, id, locale) {
		return Promise.reject('connectorApi.deleteTranslation(): not implemented');
	},
	getCommunities: function (authToken) {
		return Promise.reject('connectorApi.getCommunities(): not implemented');
	},
	/**
	 * Create a new project in the Language Service Provider. 
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} communityId Community identifier that should contain the project.
	 * @param {string} workflowId Identifier of the Language Service Provider workflow to use to translate any documents.
	 * @param {string} projectName Name of project to create.
	 * @returns {Promise.<string>} The Language Service Provider string id for the created project.
	 */
	addProject: function (authToken, communityId, workflowId, projectName) {
		// confirm parameters
		if (!authToken || !communityId || !workflowId || !projectName) {
			return Promise.reject('connectorApi.addProject(): required parameters: authToken, communityId, workflowId, projectName)');
		}

		var getWorkflowId = workflowId ? Promise.resolve(workflowId) : new Promise(function (resolve, reject) {
			// Get workflow_id	
			var options = {
				url: urls.WORKFLOW + '?community_id=' + communityId,
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var workflows = JSON.parse(body);

					// use this workflow instead of the one passed in
					workflowId = workflows.entities[0].properties.id;
					resolve(workflowId);
				} else {
					reject('unable to locate workflowId');
				}
			});
		});

		return new Promise(function (resolve, reject) {
			getWorkflowId.then(function (workflowId) {
				// create the project
				options = {
					url: urls.PROJECT,
					auth: {
						bearer: authToken
					},
					form: {
						title: projectName,
						community_id: communityId,
						workflow_id: workflowId
					}
				};
				addProxy(options, options.url);
				request.post(options, function (error, httpResponse, body) {
					if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
						var project = JSON.parse(body);
						return resolve(project);
					} else {
						return reject(error);
					}
				});
			});
		});
	},
	/**
	 * Get the percent complete of the project.<br/>
	 * This represents the percentage complete of the translations for all the documents in the project.
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @param {string} id Language Service Provider identifier for the document.
	 * @returns {Promise.<string>} The Language Service Provider GET "/:id/status" response.
	 */
	getProjectStatus: function (authToken, id) {
		if (!authToken || !id) {
			return Promise.reject('connectorApi.getProjectStatus(): required parameters: authToken, id)');
		}
		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.PROJECT + '/' + id + '/status',
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var projectStatus = JSON.parse(body);
					return resolve(projectStatus);
				} else {
					return reject(error);
				}
			});
		});
	},
	getProjects: function (authToken, community_id) {
		return Promise.reject('connectorApi.getProjects(): not implemented');
	},
	getVaults: function (authToken, community_id) {
		return Promise.reject('connectorApi.getVaults(): not implemented');
	},
	getFilters: function (authToken) {
		return Promise.reject('connectorApi.getFilters(): not implemented');
	},
	getFilter: function (authToken, id) {
		return Promise.reject('connectorApi.getFilter(): not implemented');
	},
	getFilterContent: function (authToken, id) {
		return Promise.reject('connectorApi.getFilterContent(): not implemented');
	},
	addFilter: function (authToken, name, type, content) {
		return Promise.reject('connectorApi.addFilter(): not implemented');
	},
	getWorkflowsImpl: function (authToken, communityId) {
		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.WORKFLOW + '?community_id=' + communityId,
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var workflows = JSON.parse(body);
					return resolve(workflows);
				} else {
					return reject(error);
				}
			});
		});
	},
	/**
	 * Get the details about the community for this user.
	 * 
	 * @param {string} authToken Authorization header token to include in the request.
	 * @returns {Promise.<object>} The Language Service Provider GET "/community" response.
	 */
	getCommunity: function (authToken) {
		if (!authToken) {
			return Promise.reject('connectorApi.getCommunity(): required parameters: authToken)');
		}

		return new Promise(function (resolve, reject) {
			var options = {
				url: urls.COMMUNITY,
				auth: {
					bearer: authToken
				}
			};
			addProxy(options, options.url);
			request.get(options, function (error, httpResponse, body) {
				if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
					var communityResponse = JSON.parse(body),
						community = communityResponse.entities[0],
						communityDetails = community.properties;
					return resolve(communityDetails.id);
				} else {
					return reject(error);
				}
			});
		});
	}
};

module.exports = new ConnectorInterface();