114 lines
3.9 KiB
JavaScript
114 lines
3.9 KiB
JavaScript
import express from 'express'
|
|
|
|
import FailBot from '#src/observability/lib/failbot.js'
|
|
import statsd from '#src/observability/lib/statsd.js'
|
|
import { searchCacheControl } from '../../../middleware/cache-control.js'
|
|
import catchMiddlewareError from '#src/observability/middleware/catch-middleware-error.js'
|
|
import { setFastlySurrogateKey } from '../../../middleware/set-fastly-surrogate-key.js'
|
|
import { getSearchResults } from './es-search.js'
|
|
import { getSearchFromRequest } from './get-search-request.js'
|
|
|
|
const router = express.Router()
|
|
|
|
router.get('/legacy', (req, res) => {
|
|
res.status(410).send('Use /api/search/v1 instead.')
|
|
})
|
|
|
|
export const validationMiddleware = (req, res, next) => {
|
|
const { search, validationErrors } = getSearchFromRequest(req)
|
|
if (validationErrors.length) {
|
|
// There might be multiple things bad about the query parameters,
|
|
// but we send a 400 on the first possible one in the API.
|
|
return res.status(400).json(validationErrors[0])
|
|
}
|
|
|
|
req.search = search
|
|
return next()
|
|
}
|
|
|
|
router.get(
|
|
'/v1',
|
|
validationMiddleware,
|
|
catchMiddlewareError(async function search(req, res) {
|
|
const {
|
|
indexName,
|
|
language,
|
|
query,
|
|
autocomplete,
|
|
page,
|
|
size,
|
|
debug,
|
|
sort,
|
|
highlights,
|
|
include,
|
|
} = req.search
|
|
|
|
// The getSearchResults() function is a mix of preparing the search,
|
|
// sending & receiving it, and post-processing the response from the
|
|
// network (i.e. Elasticsearch).
|
|
// This measurement then combines both the Node-work and the total
|
|
// network-work but we know that roughly 99.5% of the total time is
|
|
// spent in the network-work time so this primarily measures that.
|
|
const tags = ['version:v1', `indexName:${indexName}`]
|
|
const timed = statsd.asyncTimer(getSearchResults, 'api.search', tags)
|
|
|
|
const options = {
|
|
indexName,
|
|
query,
|
|
page,
|
|
size,
|
|
debug,
|
|
sort,
|
|
highlights,
|
|
usePrefixSearch: autocomplete,
|
|
include,
|
|
}
|
|
try {
|
|
const { meta, hits } = await timed(options)
|
|
|
|
statsd.timing('api.search.total', meta.took.total_msec, tags)
|
|
statsd.timing('api.search.query', meta.took.query_msec, tags)
|
|
|
|
if (process.env.NODE_ENV !== 'development') {
|
|
searchCacheControl(res)
|
|
setFastlySurrogateKey(res, `api-search:${language}`, true)
|
|
}
|
|
|
|
// The v1 version of the output matches perfectly what comes out
|
|
// of the getSearchResults() function.
|
|
res.status(200).json({ meta, hits })
|
|
} catch (error) {
|
|
// If getSearchResult() throws an error that might be 404 inside
|
|
// elasticsearch, if we don't capture that here, it will propgate
|
|
// to the next middleware.
|
|
await handleGetSearchResultsError(req, res, error, options)
|
|
}
|
|
}),
|
|
)
|
|
|
|
// We have more than one place where we do `try{...} catch error( THIS )`
|
|
// which is slightly different depending on the "sub-version" (e.g. /legacy)
|
|
// This function is a single place to take care of all of these error handlings
|
|
async function handleGetSearchResultsError(req, res, error, options) {
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.error(`Error calling getSearchResults(${options})`, error)
|
|
} else {
|
|
const reports = FailBot.report(error, Object.assign({ url: req.url }, options))
|
|
// It might be `undefined` if no backends are configured which
|
|
// is likely when using production NODE_ENV on your laptop
|
|
// where you might not have a HATSTACK_URL configured.
|
|
if (reports) await Promise.all(reports)
|
|
}
|
|
res.status(500).json({ error: error.message })
|
|
}
|
|
|
|
// Alias for the latest version
|
|
router.get('/', (req, res) => {
|
|
// At the time of writing, the latest version is v1. (July 2022)
|
|
// Use `req.originalUrl` because this router is "self contained"
|
|
// which means that `req.url` will be `/` in this context.
|
|
res.redirect(307, req.originalUrl.replace('/search', '/search/v1'))
|
|
})
|
|
|
|
export default router
|