diff --git a/server/createSearchServer.js b/server/createSearchServer.js index b3ee71d..96edf66 100644 --- a/server/createSearchServer.js +++ b/server/createSearchServer.js @@ -1,83 +1,20 @@ const express = require('express') -const getAssetPaths = require('./npm/getAssetPaths') -const npmSearchIndex = require('./npm/searchIndex') - -function enhanceHit(hit) { - return new Promise(function (resolve, reject) { - const assetPaths = getAssetPaths(hit.name, hit.version) - - if (assetPaths) { - // TODO: Double check the package metadata to ensure the files - // haven't moved from the paths in the index? - hit.assets = assetPaths.map(function (path) { - return `https://unpkg.com/${hit.name}@${hit.version}${path}` - }) - - resolve(hit) - } else { - resolve(hit) - } - }) -} - -function byRelevanceDescending(a, b) { - // Hits that have assets are more relevant. - return a.assets ? (b.assets ? 0 : -1) : (b.assets ? 1 : 0) -} +const npmSearch = require('./npm/search') function createSearchServer() { const app = express() app.get('/', function (req, res) { const { query, page = 0 } = req.query - const hitsPerPage = 20 if (!query) return res.status(403).send({ error: 'Missing ?query parameter' }) - const params = { - typoTolerance: 'min', - attributesToRetrieve: [ - 'name', - 'version', - 'description', - 'owner' - ], - attributesToHighlight: null, - restrictSearchableAttributes: [ - 'name', - 'description' - ], - hitsPerPage, - page - } - - npmSearchIndex.search(query, params, function (error, value) { - if (error) { - console.error(error) - res.status(500).send({ error: 'There was an error executing the search' }) - } else { - Promise.all( - value.hits.map(enhanceHit) - ).then(function (hits) { - hits.sort(byRelevanceDescending) - - const totalHits = value.nbHits - const totalPages = value.nbPages - - res.send({ - query, - page, - hitsPerPage, - totalHits, - totalPages, - hits - }) - }, function (error) { - console.error(error) - res.status(500).send({ error: 'There was an error executing the search' }) - }) - } + npmSearch(query, page).then(function (result) { + res.send(result) + }, function (error) { + console.error(error) + res.status(500).send({ error: 'There was an error executing the search' }) }) }) diff --git a/server/npm/getAssetPaths.js b/server/npm/getAssetPaths.js index c0d869d..befa831 100644 --- a/server/npm/getAssetPaths.js +++ b/server/npm/getAssetPaths.js @@ -1,3 +1,4 @@ +const semver = require('semver') const assetPathsIndex = require('./assetPathsIndex') function getAssetPaths(packageName, version) { diff --git a/server/npm/search.js b/server/npm/search.js new file mode 100644 index 0000000..d1e2115 --- /dev/null +++ b/server/npm/search.js @@ -0,0 +1,88 @@ +const searchIndex = require('./searchIndex') +const getAssetPaths = require('./getAssetPaths') + +function enhanceHit(hit) { + return new Promise(function (resolve, reject) { + const assetPaths = getAssetPaths(hit.name, hit.version) + + if (assetPaths) { + // TODO: Double check the package metadata to ensure the files + // haven't moved from the paths in the index? + hit.assets = assetPaths.map(function (path) { + return `https://unpkg.com/${hit.name}@${hit.version}${path}` + }) + + resolve(hit) + } else { + resolve(hit) + } + }) +} + +function byRelevanceDescending(a, b) { + // Hits that have assets are more relevant. + return a.assets ? (b.assets ? 0 : -1) : (b.assets ? 1 : 0) +} + +// add concatenated name for more relevance for people spelling without spaces +// think: createreactnative instead of create-react-native-app +function concat(string) { + return string.replace(/[-/@_.]+/g, '') +} + +function search(query, page) { + return new Promise(function (resolve, reject) { + const hitsPerPage = 10 + + const params = { + // typoTolerance: 'min', + optionalFacetFilters: `concatenatedName:${concat(query)}`, + facets: [ 'keywords' ], + attributesToHighlight: null, + attributesToRetrieve: [ + 'description', + 'githubRepo', + 'keywords', + 'license', + 'name', + 'owner', + 'version' + ], + // restrictSearchableAttributes: [ + // 'name', + // 'description', + // 'keywords' + // ], + hitsPerPage, + page + } + + searchIndex.search(query, params, function (error, value) { + if (error) { + reject(error) + } else { + resolve( + Promise.all( + value.hits.map(enhanceHit) + ).then(function (hits) { + hits.sort(byRelevanceDescending) + + const totalHits = value.nbHits + const totalPages = value.nbPages + + return { + query, + page, + hitsPerPage, + totalHits, + totalPages, + hits + } + }) + ) + } + }) + }) +} + +module.exports = search