Skip to content

Commit

Permalink
Merge pull request #161 from quantcdn/feature/search-api
Browse files Browse the repository at this point in the history
Added Quant Search commands.
  • Loading branch information
steveworley authored Jul 27, 2022
2 parents 741e9f2 + dd22f16 commit cded96f
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 6 deletions.
50 changes: 45 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ Commands:
quant info Give info based on current configuration
quant init Initialise a project in the current directory
quant page <file> <location> Make a local page asset available via Quant
quant proxy <path> <origin> [status] Create a proxy to allow traffic directly to
[basicAuthUser] [basicAuthPass] origin
quant purge <path> Purge the cache for a given url
quant redirect <from> <to> [status] [author] Create a redirect
quant unpublish <path> Unpublish an asset
quant proxy <path> <origin> [status] Create a proxy to allow traffic directly to origin
[basicAuthUser] [basicAuthPass]
quant purge <path> Purge the cache for a given url
quant redirect <from> <to> [status] [author] Create a redirect
quant search <index|unindex|clear> Perform search index operations
quant unpublish <path> Unpublish an asset
Options:
--version Show version number [boolean]
Expand Down Expand Up @@ -68,6 +69,45 @@ Token: ****
✅✅✅ Successfully connected to dev-docs
```

## Manage search index

### Basic usage

* Use `quant search status` to retrieve index size and basic configuration.
* Use `quant search unindex --path=/url/path` to remove an item from the index.
* Use `quant search clear` to clear the entire index.

### Create and update records

You may index new content or update existing content in the search index directly. Simply provide one or multiple records in JSON files. For example, consider a `search-records.json` file containing the following:

```
[
{
"title": "This is a record",
"url": "/blog/page",
"summary": "The record is small and neat.",
"content": "Lots of good content here. But not too much!",
},
{
"title": "Fully featured search record",
"url": "/about-us",
"summary": "The record contains all the trimmings.",
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras id dolor facilisis, ornare erat et, scelerisque odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",
"image": "https://www.example.com/images/about.jpg",
"categories": [ "Blog", "Commerce", "Jamstack" ],
"tags": [ "Tailwind" , "QuantCDN" ]
}
]
```

To post these records to the search index:
```
quant search index --path=./search-records.json
```

**Note:** The path may either refer to an individual file or a path on disk containing multiple JSON files.

## Testing

Automated via CodeFresh for all PRs and mainline branches.
Expand Down
136 changes: 136 additions & 0 deletions src/commands/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* Perform Search API oprtations.
*
* @usage
* quant search <index|unindex|clear>
*/
const chalk = require('chalk');
const client = require('../quant-client');
const config = require('../config');
const fs = require('fs');
const glob = require('glob');

const command = {};

command.command = 'search <status|index|unindex|clear>';
command.describe = 'Perform search index operations';
command.builder = (yargs) => {
// Add/update search records.
yargs.command('index', '<path>', {
command: 'index <path>',
builder: (yargs) => {
yargs.positional('path', {
type: 'string',
describe: 'Path to directory containing JSON files, or an individual JSON file.',
});
},
handler: (argv) => {
if (!argv.path) {
console.error(chalk.yellow('No path provided. Provide a path on disk, e.g: --path=/path/to/files'));
return;
}

console.log(chalk.bold.green('*** Add/update search records ***'));

if (!config.fromArgs(argv)) {
return console.error(chalk.yellow('Quant is not configured, run init.'));
}

let jsonFiles = [];
fs.stat(argv.path, (error, stats) => {
// incase of error
if (error) {
console.error(error);
return;
}

if (stats.isDirectory()) {
jsonFiles = glob.sync(argv.path + '/*.json');
} else {
jsonFiles = [argv.path];
}

for (let i = 0; i < jsonFiles.length; i++) {
client(config)
.search_index(jsonFiles[i])
.then(response => console.log(chalk.green('Success:') + ` Successfully posted search records in ${jsonFiles[i]}`)) // eslint-disable-line
.catch((err) => console.log(chalk.red.bold('Error:') + ` ${err}`));
}
});
},
});

// Unindex/remove search record.
yargs.command('unindex', '<path>', {
command: 'unindex <path>',
describe: 'Removes an item from the search index based on URL.',
builder: (yargs) => {
yargs.positional('path', {
type: 'string',
describe: 'URL path of the item to unindex.',
});
},
handler: (argv) => {
if (!argv.path) {
console.error(chalk.yellow('No path provided. Provide a content URL path to remove from index, e.g: /about-us'));
return;
}

console.log(chalk.bold.green('*** Remove search record ***'));

if (!config.fromArgs(argv)) {
return console.error(chalk.yellow('Quant is not configured, run init.'));
}

client(config)
.searchRemove(argv.path)
.then(response => console.log(chalk.green('Success:') + ` Successfully removed search record for ${argv.path}`)) // eslint-disable-line
.catch((err) => console.log(chalk.red.bold('Error:') + ` ${err}`));
},
});

// Display search index status.
yargs.command('status', '', {
command: 'status',
describe: 'Display search index status',
builder: (yargs) => {
},
handler: (argv) => {
console.log(chalk.bold.green('*** Search index status ***'));

if (!config.fromArgs(argv)) {
return console.error(chalk.yellow('Quant is not configured, run init.'));
}

client(config)
.searchStatus()
.then(response => { // eslint-disable-line
console.log(chalk.green('Success:') + ` Successfully retrieved search index status`);
console.log(response);
})
.catch((err) => console.log(chalk.red.bold('Error:') + ` ${err}`));
},
});

// Clear the entire search index.
yargs.command('clear', '', {
command: 'clear',
describe: 'Clears the entire search index',
builder: (yargs) => {
},
handler: (argv) => {
console.log(chalk.bold.green('*** Clear search index ***'));

if (!config.fromArgs(argv)) {
return console.error(chalk.yellow('Quant is not configured, run init.'));
}

client(config)
.searchClearIndex()
.then(response => console.log(chalk.green('Success:') + ` Successfully cleared search index`)) // eslint-disable-line
.catch((err) => console.log(chalk.red.bold('Error:') + ` ${err}`));
},
});
};

module.exports = command;
91 changes: 90 additions & 1 deletion src/quant-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const client = function(config) {
}

if (body.error || (typeof body.errorMsg != 'undefined' && body.errorMsg.length > 0)) { // eslint-disable-line max-len
const msg = typeof body.errorMsg != 'undefined' ? body.errorMsg: body.msg;
const msg = typeof body.errorMsg != 'undefined' ? body.errorMsg : body.msg;
throw new Error(msg);
}

Expand Down Expand Up @@ -534,6 +534,95 @@ const client = function(config) {
const res = await post(options);
return handleResponse(res);
},

/**
* Add/update items in search index.
*
* @param {string} filePath
*
* @throws Error
*/
searchIndex: async function(filePath) {
let data = '';

// filePath is a JSON file we send the raw content of.
try {
data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (err) {
console.error(err);
return;
}

const options = {
url: `${config.get('endpoint')}/search`,
headers: {
...headers,
},
json: true,
body: data,
};
const res = await post(options);

return handleResponse(res);
},

/**
* Remove item from search index.
*
* @param {string} url
*
* @throws Error
*/
searchRemove: async function(url) {
const options = {
url: `${config.get('endpoint')}/search`,
headers: {
...headers,
'Quant-Url': url,
},
json: true,
};
const res = await del(options);

return handleResponse(res);
},


/**
* Clear search index.
*
* @throws Error
*/
searchClearIndex: async function() {
const options = {
url: `${config.get('endpoint')}/search/all`,
headers: {
...headers,
},
json: true,
};
const res = await del(options);

return handleResponse(res);
},

/**
* Retrieve search index status.
*
* @throws Error
*/
searchStatus: async function() {
const options = {
url: `${config.get('endpoint')}/search`,
headers: {
...headers,
},
json: true,
};
const res = await get(options);

return handleResponse(res);
},
};
};

Expand Down

0 comments on commit cded96f

Please sign in to comment.