ring-websites-toolbelt
Version:
Ring Publishing Platform tool to work with Ring Websites
286 lines (236 loc) • 10.3 kB
JavaScript
const request = require('request');
const cryptoJS = require('crypto-js');
const fs = require('fs');
const path = require('path');
const DLSigner = require('ring-auth-node').DLSigner;
const aws4 = require('aws4');
class ExternalApiProvider {
constructor(themeJson, ucsConfig) {
this.ucsConfig = ucsConfig;
this.namespaceId = ucsConfig.namespaceId;
this.variant = ucsConfig.ucsVariant;
this.theme = themeJson.theme;
this.themeVersion = themeJson.version;
this.signer = new DLSigner({
service: 'pulsapi',
accessKey: ucsConfig.publicKey,
secretKey: ucsConfig.secretKey
});
}
async getLogs() {
try {
return await this._sendRequest('GET', `/namespaces/${this.namespaceId}/variants/${this.variant}/logs`);
} catch (err) {
return [];
}
}
async getThemes() {
return this._sendRequest('GET', `/namespaces/${this.namespaceId}/themes`);
}
async getVariants() {
return this._sendRequest('GET', `/namespaces/${this.namespaceId}/variants`);
}
async getNamespace() {
return this._sendRequest('GET', `/namespaces/${this.namespaceId}`);
}
async createVariant(params) {
return this._sendRequest('POST', `/namespaces/${this.namespaceId}/variants`, {
...params,
theme: this.theme
});
}
async updateVariant(params) {
return this._sendRequest('PATCH', `/namespaces/${this.namespaceId}/variants/${params.ucsVariant}`, params);
}
async deployEmptyTheme() {
await this._sendRequest('POST', `/namespaces/${this.namespaceId}/themes/${this.theme}/versions/0.0.0`, {});
}
async insertThemeConfig(config) {
await this._sendRequest('POST', `/namespaces/${this.namespaceId}/variants/${this.variant}/themes/${this.theme}/theme-configs`, config);
}
async purgeTheme() {
await this._sendRequest('DELETE', `/namespaces/${this.namespaceId}/variants/${this.variant}/themes/${this.theme}`);
}
async deployTheme(ocdnUrl, version) {
let deploymentId = null;
let data = {
ocdnUrl: ocdnUrl,
themeName: this.theme,
themeVersion: this.themeVersion,
ringWebsitesDeployer: true
};
let result = await this._sendRequest('POST', `/namespaces/${this.namespaceId}/deploys`, data);
if (result && result.deployId) {
deploymentId = result.deployId;
} else {
throw new Error('Error sending theme to depployment api', result);
}
return deploymentId;
}
async deployRouter(routerName, routerVersion, ocdnUrl) {
const result = await this._sendRequest('POST', `/namespaces/${this.namespaceId}/routers/${routerName}/deployments`, { routerVersion, ocdnUrl, ringWebsitesDeployer:true });
if (!result || !result.deploymentId) {
throw new Error('Error in router deployment', result);
}
return result.deploymentId;
}
async checkIfRouterDeployed(routerName, deploymentId) {
let result = null;
try {
result = await this._sendRequest('GET', `/namespaces/${this.namespaceId}/routers/${routerName}/deployments/${deploymentId}`);
let ret = {};
if (result && result.finished) {
ret.finished = true;
ret.error = result.error;
} else {
ret.finished = false; //even in case of error. Api may return an error even though the deployment will be successful.
}
return ret;
} catch (err) {
console.log('Error while checking if theme deployed', err);
throw err;
}
}
async checkIfThemeDeployed(deploymentId) {
let result = null;
try {
result = await this._sendRequest('GET', `/namespaces/${this.namespaceId}/deploys/${deploymentId}`);
let ret = {};
if (result && result.finished) {
ret.finished = true;
ret.error = result.error;
} else {
ret.finished = false; //even in case of error. Api may return an error even though the deployment will be successful.
}
return ret;
} catch (err) {
console.log('Error while checking if theme deployed', err);
throw err;
}
}
async insertFiles(entries, key) {
let filesPack = {};
filesPack[key] = [];
let filesSize = 0;
let i = 0;
for (const entry of entries) {
let fileData = null;
try {
let buff = fs.readFileSync(path.join(entry.basePath, entry.relativePath));
//DO NOT MODIFY - size calculated for file saved as hex
filesSize = filesSize + buff.length * 2;
fileData = buff.toString('hex');
} catch (e) {
return new Error('Can\'t read file %s', path.join(entry.basePath, entry.relativePath));
}
if (key === ExternalApiProvider.CODES.TE_TEMPLATES) {
let kindRole = entry.relativePath.split('/');
let kind = kindRole[0];
delete kindRole[0];
let role = kindRole.join('/').substring(1).slice(0, -6);
filesPack[key].push({kind: kind, role: role, value: fileData});
} else if (key === ExternalApiProvider.CODES.TRANSLATIONS) {
filesPack[key].push({name: entry.relativePath, value: fileData});
} else {
filesPack[key].push({name: entry.relativePath.split('.')[0], value: fileData});
}
if (!entries[i + 1] || (filesSize + entries[i + 1].size * 2) > ExternalApiProvider.OPAL_LIMIT) {
console.info('Send files pack:', key, filesSize / 1024, "kB");
await this._sendRequest('POST', '/namespaces/' + this.namespaceId + '/variants/' + this.variant + '/themes/' + this.theme + '/theme-files', filesPack);
filesPack[key] = [];
filesSize = 0;
}
i++;
}
}
async deleteFiles(entries, key) {
let filesPack = {};
filesPack[key] = [];
let i = 0;
for (const entry of entries) {
if (key === ExternalApiProvider.CODES.TE_TEMPLATES) {
let kindRole = entry.relativePath.split('/');
let kind = kindRole[0];
delete kindRole[0];
let role = kindRole.join('/').substring(1).slice(0, -6);
filesPack[key].push({kind: kind, role: role});
} else if (key === ExternalApiProvider.CODES.TRANSLATIONS) {
filesPack[key].push({name: entry.relativePath});
} else {
filesPack[key].push({name: entry.relativePath.split('.')[0]});
}
i++;
}
await this._sendRequest('DELETE', '/namespaces/' + this.namespaceId + '/variants/' + this.variant + '/themes/' + this.theme + '/theme-files', filesPack);
}
async _retry(requestFunction, retries) {
try {
return await requestFunction();
} catch (error) {
if (retries <= 0) {
throw error;
}
return await this._retry(requestFunction, retries - 1);
}
}
_sendRequest(method, path, data) {
return new Promise((resolve, reject) => {
const body = method !== 'GET' && data ? JSON.stringify(data) : '';
let options = {
url: `https://api.ringpublishing.com/websites/v1/development${path}`,
path: `/websites/v1/development${path}`,
host: "api.ringpublishing.com",
method,
headers: {
'Content-Type': 'application/json'
},
};
if (body) {
options.body = body;
}
options.service = "execute-api";
options.region = "eu-central-1";
aws4.sign(options, { accessKeyId: this.ucsConfig.publicKey, secretAccessKey: this.ucsConfig.secretKey });
// options.headers.cookie = 'acc_variant={"current":"ringwebsitesapi.pulse2.eu::dev_sjanecki"}'
// options.headers.cookie = 'acc_variant={"current":"auth-coordinator.ucs2.cloud.onet::dev_mkmiecik"}'
request(options, (error, response, body) => {
if (error) {
return reject(error);
}
if (response.statusCode < 200 || response.statusCode >= 300) {
const { message } = response.body;
if (message) {
if (message.errorType === 'JSON_RPC' && message.error) {
return reject(message.error);
}
return reject(message);
} else if (response.statusCode >= 500 && typeof response.body === 'string') {
try {
return reject(JSON.parse(response.body));
} catch (err) {
return reject(`Unexpected API error occured: statusCode=${response.statusCode}`);
}
}
return reject(response.body);
}
try {
const result = typeof response.body === 'string' ? JSON.parse(response.body) : response.body;
if (result.status === 'ok') {
return resolve(result.data);
} else {
return reject(result);
}
} catch (error) {
return reject(error);
}
});
});
}
}
ExternalApiProvider.OPAL_LIMIT = 600 * 1024; // in bytes
ExternalApiProvider.CODES = {};
ExternalApiProvider.CODES.CDF_TEMPLATES = 'cdf_templates';
ExternalApiProvider.CODES.TE_TEMPLATES = 'te_templates';
ExternalApiProvider.CODES.VIEW_HELPERS = 'te_view_helpers';
ExternalApiProvider.CODES.TRANSLATIONS = 'translations';
module.exports = ExternalApiProvider;