forked from tsai/budibase
committed by
GitHub
16 changed files with 308 additions and 67 deletions
@ -0,0 +1,18 @@ |
|||
const { QueryBuilder, buildSearchUrl, search } = require("./utils") |
|||
|
|||
exports.rowSearch = async ctx => { |
|||
// this can't be done through pouch, have to reach for trusty node-fetch
|
|||
const appId = ctx.user.appId |
|||
const bookmark = ctx.params.bookmark |
|||
let url |
|||
if (ctx.params.query) { |
|||
url = new QueryBuilder(appId, ctx.params.query, bookmark).complete() |
|||
} else if (ctx.params.raw) { |
|||
url = buildSearchUrl({ |
|||
appId, |
|||
query: ctx.params.raw, |
|||
bookmark, |
|||
}) |
|||
} |
|||
ctx.body = await search(url) |
|||
} |
|||
@ -0,0 +1,137 @@ |
|||
const { SearchIndexes } = require("../../../db/utils") |
|||
const { checkSlashesInUrl } = require("../../../utilities") |
|||
const env = require("../../../environment") |
|||
const fetch = require("node-fetch") |
|||
|
|||
/** |
|||
* Given a set of inputs this will generate the URL which is to be sent to the search proxy in CouchDB. |
|||
* @param {string} appId The ID of the app which we will be searching within. |
|||
* @param {string} query The lucene query string which is to be used for searching. |
|||
* @param {string|null} bookmark If there were more than the limit specified can send the bookmark that was |
|||
* returned with query for next set of search results. |
|||
* @param {number} limit The number of entries to return per query. |
|||
* @param {boolean} excludeDocs By default full rows are returned, if required this can be disabled. |
|||
* @return {string} The URL which a GET can be performed on to receive results. |
|||
*/ |
|||
function buildSearchUrl({ appId, query, bookmark, excludeDocs, limit = 50 }) { |
|||
let url = `${env.COUCH_DB_URL}/${appId}/_design/database/_search` |
|||
url += `/${SearchIndexes.ROWS}?q=${query}` |
|||
url += `&limit=${limit}` |
|||
if (!excludeDocs) { |
|||
url += "&include_docs=true" |
|||
} |
|||
if (bookmark) { |
|||
url += `&bookmark=${bookmark}` |
|||
} |
|||
return checkSlashesInUrl(url) |
|||
} |
|||
|
|||
class QueryBuilder { |
|||
constructor(appId, base) { |
|||
this.appId = appId |
|||
this.query = { |
|||
string: {}, |
|||
fuzzy: {}, |
|||
range: {}, |
|||
equal: {}, |
|||
...base, |
|||
} |
|||
this.limit = 50 |
|||
this.bookmark = null |
|||
} |
|||
|
|||
setLimit(limit) { |
|||
this.limit = limit |
|||
return this |
|||
} |
|||
|
|||
setBookmark(bookmark) { |
|||
this.bookmark = bookmark |
|||
return this |
|||
} |
|||
|
|||
addString(key, partial) { |
|||
this.query.string[key] = partial |
|||
return this |
|||
} |
|||
|
|||
addFuzzy(key, fuzzy) { |
|||
this.query.fuzzy[key] = fuzzy |
|||
return this |
|||
} |
|||
|
|||
addRange(key, low, high) { |
|||
this.query.range = { |
|||
low, |
|||
high, |
|||
} |
|||
return this |
|||
} |
|||
|
|||
addEqual(key, value) { |
|||
this.query.equal[key] = value |
|||
return this |
|||
} |
|||
|
|||
addTable(tableId) { |
|||
this.query.equal.tableId = tableId |
|||
return this |
|||
} |
|||
|
|||
complete(rawQuery = null) { |
|||
let output = "" |
|||
function build(structure, queryFn) { |
|||
for (let [key, value] of Object.entries(structure)) { |
|||
if (output.length !== 0) { |
|||
output += " AND " |
|||
} |
|||
output += queryFn(key, value) |
|||
} |
|||
} |
|||
|
|||
if (this.query.string) { |
|||
build(this.query.string, (key, value) => `${key}:${value}*`) |
|||
} |
|||
if (this.query.range) { |
|||
build( |
|||
this.query.range, |
|||
(key, value) => `${key}:[${value.low} TO ${value.high}]` |
|||
) |
|||
} |
|||
if (this.query.fuzzy) { |
|||
build(this.query.fuzzy, (key, value) => `${key}:${value}~`) |
|||
} |
|||
if (this.query.equal) { |
|||
build(this.query.equal, (key, value) => `${key}:${value}`) |
|||
} |
|||
if (rawQuery) { |
|||
output = output.length === 0 ? rawQuery : `&${rawQuery}` |
|||
} |
|||
return buildSearchUrl({ |
|||
appId: this.appId, |
|||
query: output, |
|||
bookmark: this.bookmark, |
|||
limit: this.limit, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
exports.search = async query => { |
|||
const response = await fetch(query, { |
|||
method: "GET", |
|||
}) |
|||
const json = await response.json() |
|||
let output = { |
|||
rows: [], |
|||
} |
|||
if (json.rows != null && json.rows.length > 0) { |
|||
output.rows = json.rows.map(row => row.doc) |
|||
} |
|||
if (json.bookmark) { |
|||
output.bookmark = json.bookmark |
|||
} |
|||
return output |
|||
} |
|||
|
|||
exports.QueryBuilder = QueryBuilder |
|||
exports.buildSearchUrl = buildSearchUrl |
|||
@ -0,0 +1,8 @@ |
|||
const Router = require("@koa/router") |
|||
const controller = require("../controllers/search") |
|||
|
|||
const router = Router() |
|||
|
|||
router.get("/api/search/rows", controller.rowSearch) |
|||
|
|||
module.exports = router |
|||
Loading…
Reference in new issue