var Router = require('express').Router; var elasticsearch = require('elasticsearch'); const all = require('predicates').all; const any = require('predicates').any; const not = require('predicates').not; const _ = require('lodash'); /** ----------------------- sanitizers ----------------------- **/ var sanitizers = { autocomplete: require('../sanitizer/autocomplete'), place: require('../sanitizer/place'), search: require('../sanitizer/search'), search_fallback: require('../sanitizer/search_fallback'), structured_geocoding: require('../sanitizer/structured_geocoding'), reverse: require('../sanitizer/reverse'), nearby: require('../sanitizer/nearby') }; /** ----------------------- middleware ------------------------ **/ var middleware = { calcSize: require('../middleware/sizeCalculator'), requestLanguage: require('../middleware/requestLanguage') }; /** ----------------------- controllers ----------------------- **/ var controllers = { coarse_reverse: require('../controller/coarse_reverse'), mdToHTML: require('../controller/markdownToHtml'), place: require('../controller/place'), placeholder: require('../controller/placeholder'), search: require('../controller/search'), search_with_ids: require('../controller/search_with_ids'), status: require('../controller/status') }; var queries = { libpostal: require('../query/search'), fallback_to_old_prod: require('../query/search_original'), structured_geocoding: require('../query/structured_geocoding'), reverse: require('../query/reverse'), autocomplete: require('../query/autocomplete'), address_using_ids: require('../query/address_search_using_ids') }; /** ----------------------- controllers ----------------------- **/ var postProc = { trimByGranularity: require('../middleware/trimByGranularity'), trimByGranularityStructured: require('../middleware/trimByGranularityStructured'), distances: require('../middleware/distance'), confidenceScores: require('../middleware/confidenceScore'), confidenceScoresFallback: require('../middleware/confidenceScoreFallback'), confidenceScoresReverse: require('../middleware/confidenceScoreReverse'), accuracy: require('../middleware/accuracy'), dedupe: require('../middleware/dedupe'), interpolate: require('../middleware/interpolate'), localNamingConventions: require('../middleware/localNamingConventions'), renamePlacenames: require('../middleware/renamePlacenames'), geocodeJSON: require('../middleware/geocodeJSON'), sendJSON: require('../middleware/sendJSON'), parseBoundingBox: require('../middleware/parseBBox'), normalizeParentIds: require('../middleware/normalizeParentIds'), assignLabels: require('../middleware/assignLabels'), changeLanguage: require('../middleware/changeLanguage'), sortResponseData: require('../middleware/sortResponseData') }; // predicates that drive whether controller/search runs const hasAnyParsedTextProperty = require('../controller/predicates/has_any_parsed_text_property'); const hasResponseData = require('../controller/predicates/has_response_data'); const hasRequestErrors = require('../controller/predicates/has_request_errors'); const isCoarseReverse = require('../controller/predicates/is_coarse_reverse'); const isAdminOnlyAnalysis = require('../controller/predicates/is_admin_only_analysis'); const hasResultsAtLayers = require('../controller/predicates/has_results_at_layers'); // shorthand for standard early-exit conditions const hasResponseDataOrRequestErrors = any(hasResponseData, hasRequestErrors); const hasAdminOnlyResults = not(hasResultsAtLayers(['venue', 'address', 'street'])); const hasNumberButNotStreet = all( hasAnyParsedTextProperty('number'), not(hasAnyParsedTextProperty('street')) ); const serviceWrapper = require('pelias-microservice-wrapper').service; const PlaceHolder = require('../service/configurations/PlaceHolder'); const PointInPolygon = require('../service/configurations/PointInPolygon'); /** * Append routes to app * * @param {object} app * @param {object} peliasConfig */ function addRoutes(app, peliasConfig) { const esclient = elasticsearch.Client(peliasConfig.esclient); const pipConfiguration = new PointInPolygon(_.defaultTo(peliasConfig.api.services.pip, {})); const pipService = serviceWrapper(pipConfiguration); const isPipServiceEnabled = _.constant(pipConfiguration.isEnabled()); const placeholderConfiguration = new PlaceHolder(_.defaultTo(peliasConfig.api.services.placeholder, {})); const placeholderService = serviceWrapper(placeholderConfiguration); const isPlaceholderServiceEnabled = _.constant(placeholderConfiguration.isEnabled()); // fallback to coarse reverse when regular reverse didn't return anything const coarseReverseShouldExecute = all( isPipServiceEnabled, not(hasRequestErrors), not(hasResponseData) ); const placeholderShouldExecute = all( not(hasResponseDataOrRequestErrors), isPlaceholderServiceEnabled, // don't run placeholder if there's a number but no street not(hasNumberButNotStreet), // don't run placeholder if there's a query or category not(hasAnyParsedTextProperty('query', 'category')) ); const searchWithIdsShouldExecute = all( not(hasRequestErrors), // don't search-with-ids if there's a query or category not(hasAnyParsedTextProperty('query', 'category')), // there must be a street hasAnyParsedTextProperty('street') ); // execute under the following conditions: // - there are no errors or data // - request is not coarse OR pip service is disabled const nonCoarseReverseShouldExecute = all( not(hasResponseDataOrRequestErrors), any( not(isCoarseReverse), not(isPipServiceEnabled) ) ); var base = '/v1/'; /** ------------------------- routers ------------------------- **/ var routers = { index: createRouter([ controllers.mdToHTML(peliasConfig.api, './public/apiDoc.md') ]), attribution: createRouter([ controllers.mdToHTML(peliasConfig.api, './public/attribution.md') ]), search: createRouter([ sanitizers.search.middleware(peliasConfig.api), middleware.requestLanguage, middleware.calcSize(), controllers.placeholder(placeholderService, placeholderShouldExecute), controllers.search_with_ids(peliasConfig.api, esclient, queries.address_using_ids, searchWithIdsShouldExecute), // 3rd parameter is which query module to use, use fallback/geodisambiguation // first, then use original search strategy if first query didn't return anything controllers.search(peliasConfig.api, esclient, queries.libpostal, not(hasResponseDataOrRequestErrors)), sanitizers.search_fallback.middleware, controllers.search(peliasConfig.api, esclient, queries.fallback_to_old_prod, not(hasResponseDataOrRequestErrors)), postProc.trimByGranularity(), postProc.distances('focus.point.'), postProc.confidenceScores(peliasConfig.api), postProc.confidenceScoresFallback(), postProc.interpolate(), postProc.sortResponseData(require('pelias-sorting'), hasAdminOnlyResults), postProc.dedupe(), postProc.accuracy(), postProc.localNamingConventions(), postProc.renamePlacenames(), postProc.parseBoundingBox(), postProc.normalizeParentIds(), postProc.changeLanguage(), postProc.assignLabels(), postProc.geocodeJSON(peliasConfig.api, base), postProc.sendJSON ]), structured: createRouter([ sanitizers.structured_geocoding.middleware(peliasConfig.api), middleware.requestLanguage, middleware.calcSize(), controllers.search(peliasConfig.api, esclient, queries.structured_geocoding, not(hasResponseDataOrRequestErrors)), postProc.trimByGranularityStructured(), postProc.distances('focus.point.'), postProc.confidenceScores(peliasConfig.api), postProc.confidenceScoresFallback(), postProc.interpolate(), postProc.dedupe(), postProc.accuracy(), postProc.localNamingConventions(), postProc.renamePlacenames(), postProc.parseBoundingBox(), postProc.normalizeParentIds(), postProc.changeLanguage(), postProc.assignLabels(), postProc.geocodeJSON(peliasConfig.api, base), postProc.sendJSON ]), autocomplete: createRouter([ sanitizers.autocomplete.middleware(peliasConfig.api), middleware.requestLanguage, controllers.search(peliasConfig.api, esclient, queries.autocomplete, not(hasResponseDataOrRequestErrors)), postProc.distances('focus.point.'), postProc.confidenceScores(peliasConfig.api), postProc.dedupe(), postProc.accuracy(), postProc.localNamingConventions(), postProc.renamePlacenames(), postProc.parseBoundingBox(), postProc.normalizeParentIds(), postProc.changeLanguage(), postProc.assignLabels(), postProc.geocodeJSON(peliasConfig.api, base), postProc.sendJSON ]), reverse: createRouter([ sanitizers.reverse.middleware, middleware.requestLanguage, middleware.calcSize(), controllers.search(peliasConfig.api, esclient, queries.reverse, nonCoarseReverseShouldExecute), controllers.coarse_reverse(pipService, coarseReverseShouldExecute), postProc.distances('point.'), // reverse confidence scoring depends on distance from origin // so it must be calculated first postProc.confidenceScoresReverse(), postProc.dedupe(), postProc.accuracy(), postProc.localNamingConventions(), postProc.renamePlacenames(), postProc.parseBoundingBox(), postProc.normalizeParentIds(), postProc.changeLanguage(), postProc.assignLabels(), postProc.geocodeJSON(peliasConfig.api, base), postProc.sendJSON ]), nearby: createRouter([ sanitizers.nearby.middleware, middleware.requestLanguage, middleware.calcSize(), controllers.search(peliasConfig.api, esclient, queries.reverse, not(hasResponseDataOrRequestErrors)), postProc.distances('point.'), // reverse confidence scoring depends on distance from origin // so it must be calculated first postProc.confidenceScoresReverse(), postProc.dedupe(), postProc.accuracy(), postProc.localNamingConventions(), postProc.renamePlacenames(), postProc.parseBoundingBox(), postProc.normalizeParentIds(), postProc.changeLanguage(), postProc.assignLabels(), postProc.geocodeJSON(peliasConfig.api, base), postProc.sendJSON ]), place: createRouter([ sanitizers.place.middleware, middleware.requestLanguage, controllers.place(peliasConfig.api, esclient), postProc.accuracy(), postProc.localNamingConventions(), postProc.renamePlacenames(), postProc.parseBoundingBox(), postProc.normalizeParentIds(), postProc.changeLanguage(), postProc.assignLabels(), postProc.geocodeJSON(peliasConfig.api, base), postProc.sendJSON ]), status: createRouter([ controllers.status ]) }; // static data endpoints app.get ( base, routers.index ); app.get ( base + 'attribution', routers.attribution ); app.get ( '/attribution', routers.attribution ); app.get ( '/status', routers.status ); // backend dependent endpoints app.get ( base + 'place', routers.place ); app.get ( base + 'autocomplete', routers.autocomplete ); app.get ( base + 'search', routers.search ); app.post( base + 'search', routers.search ); app.get ( base + 'search/structured', routers.structured ); app.get ( base + 'reverse', routers.reverse ); app.get ( base + 'nearby', routers.nearby ); } /** * Helper function for creating routers * * @param {[{function}]} functions * @returns {express.Router} */ function createRouter(functions) { var router = Router(); // jshint ignore:line functions.forEach(function (f) { router.use(f); }); return router; } module.exports.addRoutes = addRoutes;