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