Use POST requests with JSON bodies for searching instead of GETs with query string params

This commit is contained in:
Andrew Kingston 2021-05-17 08:16:04 +01:00
parent 053928420f
commit 1e8c485ad3
1 changed files with 43 additions and 31 deletions

View File

@ -33,6 +33,7 @@ class QueryBuilder {
this.limit = 50 this.limit = 50
this.sortOrder = "ascending" this.sortOrder = "ascending"
this.sortType = "string" this.sortType = "string"
this.includeDocs = true
} }
setTable(tableId) { setTable(tableId) {
@ -65,6 +66,11 @@ class QueryBuilder {
return this return this
} }
excludeDocs() {
this.includeDocs = false
return this
}
addString(key, partial) { addString(key, partial) {
this.query.string[key] = partial this.query.string[key] = partial
return this return this
@ -103,15 +109,16 @@ class QueryBuilder {
return this return this
} }
buildSearchURL(excludeDocs = false) { buildSearchQuery() {
let output = "*:*" let query = "*:*"
function build(structure, queryFn) { function build(structure, queryFn) {
for (let [key, value] of Object.entries(structure)) { for (let [key, value] of Object.entries(structure)) {
const expression = queryFn(luceneEscape(key.replace(/ /, "_")), value) const expression = queryFn(luceneEscape(key.replace(/ /, "_")), value)
if (expression == null) { if (expression == null) {
continue continue
} }
output += ` AND ${expression}` query += ` AND ${expression}`
} }
} }
@ -157,35 +164,43 @@ class QueryBuilder {
build(this.query.notEmpty, key => `${key}:["" TO *]`) build(this.query.notEmpty, key => `${key}:["" TO *]`)
} }
// Build the full search URL return query
let url = `${env.COUCH_DB_URL}/${this.appId}/_design/database/_search`
url += `/${SearchIndexes.ROWS}?q=${output}`
url += `&limit=${Math.min(this.limit, 200)}`
if (!excludeDocs) {
url += "&include_docs=true"
}
if (this.sort) {
const orderChar = this.sortOrder === "descending" ? "-" : ""
url += `&sort="${orderChar}${this.sort.replace(/ /, "_")}`
url += `<${this.sortType}>"`
}
if (this.bookmark) {
url += `&bookmark=${this.bookmark}`
} }
// Fix any double slashes in the URL buildSearchBody() {
return checkSlashesInUrl(url) let body = {
q: this.buildSearchQuery(),
limit: Math.min(this.limit, 200),
include_docs: this.includeDocs,
}
if (this.bookmark) {
body.bookmark = this.bookmark
}
if (this.sort) {
const order = this.sortOrder === "descending" ? "-" : ""
const type = `<${this.sortType}>`
body.sort = `${order}${this.sort.replace(/ /, "_")}${type}`
}
return body
}
async run() {
const url = `${env.COUCH_DB_URL}/${this.appId}/_design/database/_search/${SearchIndexes.ROWS}`
const body = this.buildSearchBody()
return await runQuery(url, body)
} }
} }
/** /**
* Executes a lucene search query. * Executes a lucene search query.
* @param query The query URL * @param url The query URL
* @param body The request body defining search criteria
* @returns {Promise<{rows: []}>} * @returns {Promise<{rows: []}>}
*/ */
const runQuery = async query => { const runQuery = async (url, body) => {
const response = await fetch(query, { const response = await fetch(url, {
method: "GET", body: JSON.stringify(body),
method: "POST",
}) })
const json = await response.json() const json = await response.json()
let output = { let output = {
@ -227,15 +242,14 @@ const recursiveSearch = async (appId, query, params) => {
if (rows.length > params.limit - 200) { if (rows.length > params.limit - 200) {
pageSize = params.limit - rows.length pageSize = params.limit - rows.length
} }
const url = new QueryBuilder(appId, query) const page = await new QueryBuilder(appId, query)
.setTable(params.tableId) .setTable(params.tableId)
.setBookmark(bookmark) .setBookmark(bookmark)
.setLimit(pageSize) .setLimit(pageSize)
.setSort(params.sort) .setSort(params.sort)
.setSortOrder(params.sortOrder) .setSortOrder(params.sortOrder)
.setSortType(params.sortType) .setSortType(params.sortType)
.buildSearchURL() .run()
const page = await runQuery(url)
if (!page.rows.length) { if (!page.rows.length) {
return rows return rows
} }
@ -272,24 +286,22 @@ exports.paginatedSearch = async (appId, query, params) => {
limit = 50 limit = 50
} }
limit = Math.min(limit, 200) limit = Math.min(limit, 200)
const builder = new QueryBuilder(appId, query) const search = new QueryBuilder(appId, query)
.setTable(params.tableId) .setTable(params.tableId)
.setSort(params.sort) .setSort(params.sort)
.setSortOrder(params.sortOrder) .setSortOrder(params.sortOrder)
.setSortType(params.sortType) .setSortType(params.sortType)
const searchUrl = builder const searchResults = await search
.setBookmark(params.bookmark) .setBookmark(params.bookmark)
.setLimit(limit) .setLimit(limit)
.buildSearchURL() .run()
const searchResults = await runQuery(searchUrl)
// Try fetching 1 row in the next page to see if another page of results // Try fetching 1 row in the next page to see if another page of results
// exists or not // exists or not
const nextUrl = builder const nextResults = await search
.setBookmark(searchResults.bookmark) .setBookmark(searchResults.bookmark)
.setLimit(1) .setLimit(1)
.buildSearchURL() .run()
const nextResults = await runQuery(nextUrl)
return { return {
...searchResults, ...searchResults,