cloudcms-query-runner
Version:
Run various types of queries and searches in Cloud CMS
617 lines (558 loc) • 18.7 kB
JavaScript
/*jshint -W069 */
/*jshint -W104*/
const Gitana = require("gitana");
const async = require("async");
const cliArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage')
const chalk = require('chalk');
const _ = require('underscore');
const fs = require("fs");
const util = require("./lib/util");
const Logger = require('basic-logger');
const log = new Logger({
showMillis: true,
showTimestamp: true
});
const NS_PER_SEC = 1e9;
function error(message) {
log.error(chalk.red(message));
}
function warn(message) {
log.warn(chalk.yellow(message));
}
function info(message) {
log.info(chalk.cyan(message));
}
function message(message) {
log.info(chalk.green(message));
}
function text(message) {
log.info(message);
}
function debug(message) {
log.debug(chalk.white(message));
}
function trace(message) {
log.trace(message);
}
// debug only when using charles proxy ssl proxy when intercepting cloudcms api calls:
if (process.env.NODE_ENV !== 'production') {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
var options = handleOptions();
if (!options) {
return;
}
if (options["verbose"]) {
Logger.setLevel('debug', true);
} else {
Logger.setLevel('info', true);
}
var option_prompt = options["prompt"];
var option_useCredentialsFile = options["use-credentials-file"];
var option_username = options["username"];
var option_password = options["password"];
var option_gitanaFilePath = options["gitana-file-path"] || "./gitana.json";
var option_branchId = options["branch"] || "master";
var option_filePath = options["query-file-path"];
var option_query = options["query"];
var option_search = options["search"];
var option_traverse = options["traverse"];
var option_nodeFind = options["node-find"];
var option_branchFind = options["branch-find"];
var option_graphql = options["graphql"];
var option_graphqlSchema = options["graphql-schema"];
var option_printResults = options["print-results"];
var option_tree = options["tree"];
var option_nodeId = options["node-id"];
var option_sortFilePath = options["sort-file-path"];
var option_skip = options["skip"];
var option_limit = options["limit"];
//
// load gitana.json config and override credentials
//
var gitanaConfig = JSON.parse("" + fs.readFileSync(option_gitanaFilePath));
if (option_useCredentialsFile) {
// override gitana.json credentials with username and password properties defined in the cloudcms-cli tool local db
var rootCredentials = JSON.parse("" + fs.readFileSync(path.join(util.homeDirectory(), ".cloudcms", "credentials.json")));
gitanaConfig.username = rootCredentials.username;
gitanaConfig.password = rootCredentials.password;
} else if (option_prompt) {
// override gitana.json credentials with username and password properties entered at command prompt
var option_prompt = require('prompt-sync')({
sigint: true
});
gitanaConfig.username = option_prompt('name: ');
gitanaConfig.password = option_prompt.hide('password: ');
}
if (option_username) {
gitanaConfig.username = option_username;
}
if (option_password) {
gitanaConfig.password = option_password;
}
if (!option_query
&& !option_search
&& !option_traverse
&& !option_nodeFind
&& !option_branchFind
&& !option_tree
&& !option_graphql
&& !option_graphqlSchema) {
error("You must specify a query type with one of: --query, --search, --traverse, --find, --tree, --graphql, --graphql-schema");
return;
}
if (!option_nodeId
&& (option_traverse
|| option_nodeFind)) {
error("You must specify --node-id with --traverse or --node-find");
return;
}
if (!fs.existsSync(option_filePath) && !option_graphqlSchema) {
error("You must specify a query file using --query-file-path");
return;
}
var query;
if (option_graphqlSchema) {
// no query needed
} else if (option_graphql) {
query = fs.readFileSync(option_filePath, 'utf8');
} else {
query = require(option_filePath);
if (!_.isObject(query)) {
error("Not a valid query in file " + option_filePath);
return;
}
log.info("Query: " + chalk.blue(JSON.stringify(query, null, 2)));
}
if (option_nodeId) {
log.info("Node id: " + chalk.blue(option_nodeId));
}
var sort = {};
if (option_sortFilePath) {
if (!fs.existsSync(option_sortFilePath)) {
error("Sort file path not found at " + option_sortFilePath);
return;
}
sort = require(option_sortFilePath);
if (!_.isObject(sort)) {
error("Not a valid sort in file " + option_sortFilePath);
return;
}
}
util.getBranch(gitanaConfig, option_branchId, function (err, branch, platform, stack, domain, primaryDomain, project) {
if (err) {
debug("Error connecting to Cloud CMS branch: " + err);
return;
}
log.info("connected to project: \"" + chalk.yellow(project.title) + "\" and branch: \"" + chalk.yellow(branch.title || branch._doc) + "\".");
var queryDuration = 0;
var queryStartTime = process.hrtime();
if (option_query) {
info("Query");
branch.trap(function (err) {
error(err);
}).queryNodes(query, {
limit: option_limit || 100,
skip: option_skip,
sort: sort
}, {
paths: true
}).then(function () {
queryDuration = process.hrtime(queryStartTime);
var nodes = this.asArray();
printNodes("Query", nodes, queryDuration);
});
}
else if (option_search) {
info("Search");
branch.trap(function (err) {
error(err);
}).searchNodes({
"filtered": {
"query": {
"query_string": {
"query": "twitter"
}
},
"filter": {
"type" : {
"value" : "my:article"
}
}
}
}).then(function () {
queryDuration = process.hrtime(queryStartTime);
var nodes = this.asArray();
printNodes("Search", nodes, queryDuration);
});
}
else if (option_traverse) {
info("Traverse");
branch.trap(function (err) {
error(err);
}).readNode(option_nodeId).traverse(query).then(function () {
queryDuration = process.hrtime(queryStartTime);
var nodes = this.asArray();
printNodes("Traverse", nodes, queryDuration);
});
}
else if (option_nodeFind) {
info("Node Find");
// * Performs a FIND for documents under the given branch that are around a given node.
// *
// * A find is a combination of one or more of the following:
// *
// * Query
// * Search
// * Traversal
// *
// * A find produces a record set that is constrained by one or more of the three from the list
// * above. In essence, it is a query whose record sets consists only of those records with
// * matching search terms and which sit in the given traversal space around the node.
// *
// * The incoming search JSON payload should look like:
// *
// * {
// * "query": {
// * ... Gitana Query (Mongo)
// * },
// * "search": {
// * ... Elastic Search Query Block
// * },
// * "traverse": {
// * ... Traversal configuration
// * }
// * }
// *
// * Only one of the three need be present but if all three are present, all will be employed.
branch.trap(function (err) {
error(err);
}).readNode(option_nodeId).find(query, {paths: true}).then(function () {
queryDuration = process.hrtime(queryStartTime);
var nodes = this.asArray();
printNodes("Find", nodes, queryDuration);
});
}
else if (option_branchFind) {
info("Branch Find");
// * Performs a FIND for documents under the given branch.
// *
// * A find is a combination of one or more of the following:
// *
// * Query
// * Search
// *
// * The incoming search JSON payload should look like:
// *
// * {
// * "query": {
// * ... Gitana Query (Mongo)
// * },
// * "search": {
// * ... Elastic Search Query Block
// * }
// * }
// *
// * Only one of the three need be present but if all three are present, all will be employed.
branch.trap(function (err) {
error(err);
}).find(query, {paths: true, _fields:{title:1}}).then(function () {
queryDuration = process.hrtime(queryStartTime);
var nodes = this.asArray();
printNodes("Find", nodes, queryDuration);
});
}
else if (option_tree) {
info("Tree");
branch.trap(function (err) {
error(err);
}).rootNode().loadTree(query, function (tree) {
queryDuration = process.hrtime(queryStartTime);
printNodeTree("Tree", tree, queryDuration);
});
}
else if (option_graphql) {
info("graphqlQuery");
var operationName = "";
var variables = null;
// Gitana.PREFER_GET_OVER_POST = false;
branch.trap(function (err) {
error(err);
}).graphqlQuery(query, operationName, variables, function (result) {
queryDuration = process.hrtime(queryStartTime);
printObjectResponse("graphql", result, queryDuration);
});
}
else if (option_graphqlSchema) {
info("graphql-schema");
branch.graphqlSchema(function (schemaText) {
queryDuration = process.hrtime(queryStartTime);
printGraphqlResult("graphql-schema", schemaText, queryDuration);
});
}
else {
printHelp(getOptions());
}
return;
});
function durationSeconds(duration) {
return (duration[0] * NS_PER_SEC + duration[1]) / NS_PER_SEC;
}
function printNodes(type, nodes, duration) {
debug("showNodes()");
if (duration) {
info(type + " completed in " + durationSeconds(duration) + " seconds");
}
info("Node count " + nodes.length || "?");
if (option_printResults) {
nodes = _.map(nodes, function (node) {
util.enhanceNode(node);
return (node);
});
text(JSON.stringify(nodes, null, 4));
}
}
function printGraphqlResult(type, schema, duration) {
debug("showNodes()");
if (duration) {
info(type + " completed in " + durationSeconds(duration) + " seconds");
}
text("\n" + schema);
}
function printObjectResponse(type, response, duration) {
debug("showNodes()");
if (duration) {
info(type + " completed in " + durationSeconds(duration) + " seconds");
}
if (option_printResults) {
text(JSON.stringify(response, null, 2));
}
}
function printNodeTree(type, nodeTree, duration) {
debug("showNodes()");
if (duration) {
info(type + " completed in " + durationSeconds(duration) + " seconds");
}
info("Node count " + nodeTree.length || "?");
if (option_printResults) {
text(JSON.stringify(nodeTree, null, 4));
}
}
function getOptions() {
return [{
name: 'help',
alias: 'h',
type: Boolean
},
{
name: 'verbose',
alias: 'v',
type: Boolean,
description: 'verbose logging'
},
{
name: 'prompt',
alias: 'p',
type: Boolean,
description: 'prompt for username and password. overrides gitana.json credentials'
},
{
name: 'use-credentials-file',
alias: 'c',
type: Boolean,
description: 'use credentials file ~/.cloudcms/credentials.json. overrides gitana.json credentials'
},
{
name: 'username',
alias: 'u',
type: String,
description: 'api server admin user name"'
},
{
name: 'password',
alias: 'w',
type: String,
description: 'api server admin password"'
},
{
name: 'gitana-file-path',
alias: 'g',
type: String,
description: 'path to gitana.json file to use when connecting. defaults to ./gitana.json'
},
{
name: 'branch',
alias: 'b',
type: String,
description: 'branch id (not branch name!) to write content to. branch id or "master". Default is "master"'
},
{
name: 'query-file-path',
alias: 'f',
type: String,
description: 'path to file containing the query to executed'
},
{
name: 'query',
type: Boolean,
description: 'Run a query'
},
{
name: 'search',
type: Boolean,
description: 'Run a search'
},
{
name: 'node-find',
type: Boolean,
description: 'Run a find from a node'
},
{
name: 'branch-find',
type: Boolean,
description: 'Run a find against a branch'
},
{
name: 'traverse',
type: Boolean,
description: 'Run a traverse'
},
{
name: 'tree',
type: Boolean,
description: 'Run a tree'
},
{
name: 'graphql',
type: Boolean,
description: 'Run a graphql query'
},
{
name: 'graphql-schema',
type: Boolean,
description: 'Run graphql/schema to fetch content model schema'
},
{
name: 'print-results',
type: Boolean,
description: 'print query results'
},
{
name: 'limit',
type: String,
description: 'limit'
},
{
name: 'skip',
type: String,
description: 'skip'
},
{
name: 'sort-file-path',
type: String,
description: 'path to a json file defining the sort'
},
{
name: 'fields',
type: String,
description: 'fields'
},
{
name: 'node-id',
type: String,
description: 'node id'
}
];
}
function handleOptions() {
var options = cliArgs(getOptions());
if (options.help) {
printHelp(getOptions());
return null;
}
return options;
}
function printHelp(optionsList) {
console.log(commandLineUsage([{
header: 'Cloud CMS Query Runner',
content: 'Exercise various methods of searching for nodes in a Cloud CMS repository branch.'
},
{
content: 'gitana credentials files can be referenced by path. This allows you to keep a library of credentials files and reference them when running this script using --gitana-file-path.'
},
{
content: 'For example: gitana-project-xyz-local-docker.json, gitana-project-hello-world-cloudcms-net.json.'
},
{
content: 'If no --gitana-file-path is set then the script looks for \'./gitana.json\'.'
},
{
header: 'Options',
optionList: optionsList
},
{
header: 'Examples',
content: [{
desc: '1. Query for nodes using POST to /repositories/{repositoryId}/branches/{branchId}/nodes/query',
},
{
desc: '\"query\" runs against the MongoDB database and uses MongoDB syntax'
},
{
desc: './cloudcms-query.js --query --query-file-path ./examples/query-test1.json'
},
{
desc: '\n2. Search for nodes using POST to /repositories/{repositoryId}/branches/{branchId}/nodes/search',
},
{
desc: '\"search\" runs against the elasticsearch database and uses elasticsearch DSL syntax'
},
{
desc: './cloudcms-query.js --search --query-file-path ./examples/search-test1.json'
},
{
desc: '\n3. Traverse nodes using POST to /repositories/{repositoryId}/branches/{branchId}/nodes/traverse',
},
{
desc: '\"Traverse\" runs against the elasticsearch database and uses elasticsearch DSL syntax'
},
{
desc: './cloudcms-query.js --search --query-file-path ./examples/traverse-test1.json'
},
{
desc: '\n4. Find nodes using POST to /repositories/{repositoryId}/branches/{branchId}/nodes/find.',
},
{
desc: '\"find\" uses a combination of query, search and traverse'
},
{
desc: `{
\"query\": {
... Gitana Query (Mongo)
},
\"search\": {
... Elastic Search Query Block
},
\"traverse\": {
... Traversal configuration
}
}`,
},
{
desc: './cloudcms-query.js --find --query-file-path ./examples/find-test1.json'
},
{
desc: '\n5. Return a \"Tree\" of nodes using POST to /repositories/{repositoryId}/branches/{branchId}/nodes/tree',
},
{
desc: '\"Tree\" runs against the elasticsearch database and uses elasticsearch DSL syntax'
},
{
desc: './cloudcms-query.js --tree --query-file-path ./examples/tree-test1.json'
}
]
}
]));
}