├── front-end ├── .browserslistrc ├── .env.development ├── vue.config.js ├── babel.config.js ├── public │ ├── favicon.ico │ ├── imgs │ │ ├── redislabs.png │ │ ├── redis-logo.svg │ │ ├── redis.svg │ │ └── forkme_left_red.svg │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── main.js │ ├── router │ │ └── index.js │ ├── lib │ │ ├── search-samples.json │ │ └── SearchClient.js │ ├── App.vue │ ├── views │ │ ├── MovieForm.vue │ │ ├── Home.vue │ │ ├── Search.vue │ │ └── FacetedSearch.vue │ └── components │ │ └── Comments.vue ├── .gitignore ├── .eslintrc.js ├── Dockerfile ├── package.json └── README.md ├── img.png ├── images └── app_preview_image.png ├── redisearch-node-rest ├── Dockerfile ├── package.json ├── README.md ├── server.js ├── NodeSearchService.js └── package-lock.json ├── redisearch-docker ├── Dockerfile ├── import-data.sh └── dataset │ ├── import_create_index.redis │ └── import_theaters.redis ├── .gitignore ├── LICENSE ├── marketplace.json ├── docker-compose.yaml └── README.md /front-end/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /front-end/.env.development: -------------------------------------------------------------------------------- 1 | 2 | VUE_APP_SEARCH_API_NODE=http://127.0.0.1:8086 3 | -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/main/img.png -------------------------------------------------------------------------------- /front-end/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | port: 8084 4 | } 5 | } -------------------------------------------------------------------------------- /front-end/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /front-end/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/main/front-end/public/favicon.ico -------------------------------------------------------------------------------- /front-end/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/main/front-end/src/assets/logo.png -------------------------------------------------------------------------------- /images/app_preview_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/main/images/app_preview_image.png -------------------------------------------------------------------------------- /front-end/public/imgs/redislabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/main/front-end/public/imgs/redislabs.png -------------------------------------------------------------------------------- /redisearch-node-rest/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | COPY . . 9 | 10 | EXPOSE 8086 11 | 12 | CMD [ "node", "server.js" ] 13 | -------------------------------------------------------------------------------- /front-end/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /redisearch-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redislabs/redisearch:2.0.2 2 | 3 | COPY import-data.sh /data/import-data.sh 4 | COPY ./dataset/import_actors.redis /data/import_actors.redis 5 | COPY ./dataset/import_movies.redis /data/import_movies.redis 6 | COPY ./dataset/import_create_index.redis /data/import_create_index.redis 7 | 8 | 9 | CMD ["sh", "/data/import-data.sh"] 10 | -------------------------------------------------------------------------------- /front-end/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /redisearch-docker/import-data.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | redis-server --loadmodule /usr/lib/redis/modules/redisearch.so --daemonize yes && sleep 2 4 | 5 | redis-cli -p 6379 < /data/import_actors.redis 6 | 7 | redis-cli -p 6379 < /data/import_movies.redis 8 | 9 | redis-cli -p 6379 < /data/import_users.redis 10 | 11 | redis-cli -p 6379 < /data/import_create_index.redis 12 | 13 | redis-cli save 14 | 15 | redis-cli shutdown 16 | 17 | redis-server --loadmodule /usr/lib/redis/modules/redisearch.so -------------------------------------------------------------------------------- /front-end/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | import { BootstrapVue } from 'bootstrap-vue'; 6 | import 'bootstrap/dist/css/bootstrap.css'; 7 | import 'bootstrap-vue/dist/bootstrap-vue.css'; 8 | 9 | import sampleQueries from "./lib/search-samples.json"; 10 | 11 | Vue.use(BootstrapVue); 12 | 13 | 14 | Vue.config.productionTip = false 15 | 16 | Vue.prototype.$sampleQueries = sampleQueries; 17 | 18 | new Vue({ 19 | router, 20 | render: h => h(App) 21 | }).$mount('#app') 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | HELP.md 3 | target/ 4 | !.mvn/wrapper/maven-wrapper.jar 5 | !**/src/main/**/target/ 6 | !**/src/test/**/target/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | build/ 30 | !**/src/main/**/build/ 31 | !**/src/test/**/build/ 32 | 33 | ### VS Code ### 34 | .vscode/ 35 | -------------------------------------------------------------------------------- /redisearch-docker/dataset/import_create_index.redis: -------------------------------------------------------------------------------- 1 | 2 | FT.CREATE idx:movie ON hash PREFIX 1 "movie:" SCHEMA title TEXT SORTABLE plot TEXT release_year NUMERIC SORTABLE rating NUMERIC SORTABLE genre TAG SORTABLE 3 | 4 | FT.CREATE idx:actor ON hash PREFIX 1 "actor:" SCHEMA first_name TEXT SORTABLE last_name TEXT SORTABLE date_of_birth NUMERIC SORTABLE 5 | 6 | FT.CREATE idx:user ON hash PREFIX 1 "user:" SCHEMA gender TAG country TAG SORTABLE last_login NUMERIC SORTABLE location GEO 7 | 8 | FT.CREATE idx:comments:movies on HASH PREFIX 1 'comments:' SCHEMA movie_id TAG SORTABLE user_id TEXT SORTABLE comment TEXT WEIGHT 1.0 timestamp NUMERIC SORTABLE rating NUMERIC SORTABLE 9 | -------------------------------------------------------------------------------- /front-end/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /front-end/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | 3 | # install simple http server for serving static content 4 | RUN npm install -g http-server 5 | 6 | # make the 'app' folder the current working directory 7 | WORKDIR /app 8 | 9 | # copy both 'package.json' and 'package-lock.json' (if available) 10 | COPY package*.json ./ 11 | 12 | # Environment Variables 13 | ARG VUE_APP_SEARCH_API_NODE 14 | ENV VUE_APP_SEARCH_API_NODE $VUE_APP_SEARCH_API_NODE 15 | 16 | # install project dependencies 17 | RUN npm install 18 | 19 | # copy project files and folders to the current working directory (i.e. 'app' folder) 20 | COPY . . 21 | 22 | # build app for production with minification 23 | RUN npm run build 24 | 25 | EXPOSE 8084 26 | CMD [ "http-server", "dist", "--port", "8084" ] 27 | 28 | -------------------------------------------------------------------------------- /front-end/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Search from '../views/Search.vue' 4 | import FacetedSearch from '../views/FacetedSearch.vue' 5 | import Home from '../views/Home.vue' 6 | import MovieForm from '../views/MovieForm.vue' 7 | 8 | Vue.use(VueRouter) 9 | 10 | const routes = [ 11 | { 12 | path: '/', 13 | name: 'Home', 14 | component: Home 15 | }, 16 | { 17 | path: '/search', 18 | name: 'Search', 19 | component: Search 20 | }, 21 | { 22 | path: '/faceted-search', 23 | name: 'FacetedSearch', 24 | component: FacetedSearch 25 | }, 26 | { 27 | path: '/movies/:id', 28 | name: 'MovieForm', 29 | component: MovieForm 30 | } 31 | ] 32 | 33 | const router = new VueRouter({ 34 | routes 35 | }) 36 | 37 | export default router 38 | -------------------------------------------------------------------------------- /front-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-end", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.21.1", 12 | "bootstrap": "^4.5.2", 13 | "bootstrap-vue": "^2.16.0", 14 | "core-js": "^3.6.5", 15 | "vue": "^2.6.11", 16 | "vue-router": "^3.2.0", 17 | "vue-slider-component": "^3.2.5" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "~4.5.0", 21 | "@vue/cli-plugin-eslint": "~4.5.0", 22 | "@vue/cli-plugin-router": "~4.5.0", 23 | "@vue/cli-service": "~4.5.0", 24 | "babel-eslint": "^10.1.0", 25 | "eslint": "^6.7.2", 26 | "eslint-plugin-vue": "^6.2.2", 27 | "vue-template-compiler": "^2.6.11" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /redisearch-node-rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redisearch-node-rest", 3 | "version": "1.0.0", 4 | "description": "Sample RediSearch REST Server with Node.js", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "engines": { 11 | "node": ">=8.9.4" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Redis-Developer/getting-started-redisearch.git" 16 | }, 17 | "keywords": [ 18 | "redis", 19 | "redisearch", 20 | "node" 21 | ], 22 | "author": "Tugdual Grall", 23 | "license": "Apache-2.0", 24 | "bugs": { 25 | "url": "https://github.com/Redis-Developer/getting-started-redisearch/issues" 26 | }, 27 | "homepage": "https://github.com/Redis-Developer/getting-started-redisearch#readme", 28 | "dependencies": { 29 | "cors": "^2.8.5", 30 | "express": "^4.17.1", 31 | "redis": "^3.0.2", 32 | "redis-redisearch": "stockholmux/node_redis-redisearch" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /front-end/README.md: -------------------------------------------------------------------------------- 1 | # front-end 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | 26 | 27 | ### Running the application in Docker 28 | 29 | You can run build and run the application from docker using the following commands: 30 | 31 | **Build** 32 | 33 | ```shell script 34 | 35 | > docker build -t redis/search-frontend . 36 | 37 | ``` 38 | 39 | This command will create a new image and build the maven project into it. 40 | 41 | **Run** 42 | 43 | ```shell script 44 | > docker run --rm \ 45 | --env "VUE_APP_SEARCH_API_NODE=http://host.docker.internal:8086" \ 46 | --name "redisearch-frontend"\ 47 | -p 8084:8084 redis/search-frontend 48 | ``` 49 | 50 | Access the Web application with the following URL: 51 | 52 | * http://localhost:8084 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Redis Developer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /marketplace.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": "A Movie Database Demo app using NodeJS and Redis Search", 3 | "description": "A Movie Database Demo app in NodeJS based on Redis Search capabilities", 4 | "type": "Building Block", 5 | "contributed_by": "Redis", 6 | "repo_url": "https://github.com/redis-developer/demo-movie-app-redisearch-nodejs", 7 | "preview_image_url": "https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/master/images/app_preview_image.png", 8 | "download_url": "https://github.com/redis-developer/demo-movie-app-redisearch-nodejs/archive/main.zip", 9 | "hosted_url": "", 10 | "quick_deploy": "false", 11 | "deploy_buttons": [], 12 | "language": ["JavaScript"], 13 | "redis_commands": ["FT.SEARCH", "FT.INFO", "FT.CREATE", "SORTBY"], 14 | "redis_use_cases": ["Search"], 15 | "redis_features": ["Search and Query"], 16 | "app_image_urls": [ 17 | "https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/master/img.png" 18 | ], 19 | "youtube_url": "", 20 | "special_tags": [], 21 | "verticals": [], 22 | "markdown": "https://raw.githubusercontent.com/redis-developer/demo-movie-app-redisearch-nodejs/master/README.md" 23 | } 24 | -------------------------------------------------------------------------------- /front-end/src/lib/search-samples.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title" : "All Movies", 4 | "cli" : "FT.SEARCH 'idx:movie' '*'", 5 | "form" : "*" 6 | }, 7 | { 8 | "title" : "Fuzzy Search 'empre', for Empire", 9 | "cli" : "FT.SEARCH 'idx:movie' '%empre%'", 10 | "form" : "%empre%" 11 | }, 12 | { 13 | "title" : "All 'Action' Movies", 14 | "cli" : "FT.SEARCH 'idx:movie' '@genre:{Action}'", 15 | "form" : "@genre:{Action}" 16 | }, 17 | { 18 | "title" : "All movies released in 2000", 19 | "cli" : "FT.SEARCH 'idx:movie' '@release_year:[2000 2000]'", 20 | "form" : "@release_year:[2000 2000]" 21 | }, 22 | { 23 | "title" : "'Drama' from 2010 to 2020", 24 | "cli" : "FT.SEARCH 'idx:movie' '@genre:{Drama} @release_year:[2010 2020]'", 25 | "form" : "@genre:{Drama} @release_year:[2010 2020]" 26 | }, 27 | { 28 | "title" : "Star Wars Movies", 29 | "cli" : "FT.SEARCH 'idx:movie' 'star wars", 30 | "form" : "star wars" 31 | }, 32 | { 33 | "title" : "Star Wars movies that does NOT mention Jedi", 34 | "cli" : "FT.SEARCH 'idx:movie' 'star wars", 35 | "form" : "star wars -jedi" 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | 4 | # Start RediSearch and import data/indexes 5 | redisearch: 6 | build: 7 | context: ./redisearch-docker 8 | dockerfile: Dockerfile 9 | ports: 10 | # using 6380 as public port to avoid conflict with local process 11 | - "6380:6379" 12 | networks: 13 | - redisearch-gettingstarted 14 | restart: always 15 | 16 | rest-node: 17 | build: 18 | context: ./redisearch-node-rest 19 | dockerfile: Dockerfile 20 | ports: 21 | - "8086:8086" 22 | environment: 23 | - REDIS_URL=redis://redisearch:6379 24 | - REDIS_INDEX=idx:movie 25 | networks: 26 | - redisearch-gettingstarted 27 | restart: always 28 | depends_on: 29 | - redisearch 30 | 31 | search-frontend: 32 | build: 33 | context: ./front-end 34 | dockerfile: Dockerfile 35 | ports: 36 | - "8084:8084" 37 | environment: 38 | - VUE_APP_SEARCH_API_NODE=http://rest-node:8086 39 | networks: 40 | - redisearch-gettingstarted 41 | restart: always 42 | depends_on: 43 | - redisearch 44 | - rest-node 45 | 46 | networks: 47 | redisearch-gettingstarted: 48 | driver: bridge 49 | -------------------------------------------------------------------------------- /front-end/src/App.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 53 | 54 | -------------------------------------------------------------------------------- /redisearch-node-rest/README.md: -------------------------------------------------------------------------------- 1 | # RediSearch: Node.js Sample 2 | 3 | 4 | 5 | 6 | ## Coding the application 7 | 8 | #### 1- Create the Project 9 | 10 | Follow the `npm` steps 11 | 12 | ``` 13 | $ npm init 14 | ``` 15 | 16 | #### 2- Add Dependencies 17 | 18 | Add the dependencies: 19 | 20 | * [Express](https://www.npmjs.com/package/express) 21 | * [Node RediSearch](https://www.npmjs.com/package/redis-redisearch) 22 | 23 | ``` 24 | $ npm install express redis redis-redisearch --save 25 | ``` 26 | 27 | 28 | #### 3- Create REST API Routes 29 | 30 | Create the `server.js` file and add the following code 31 | 32 | ```js 33 | const express = require('express') 34 | const app = express() 35 | const port = 3003 36 | 37 | 38 | app.get('/api/1.0/', (req, res) => { 39 | res.json({"status" : "started"}); 40 | }) 41 | 42 | 43 | app.get('/', (req, res) => { 44 | res.send('RediSearch Node REST Server Started') 45 | }) 46 | 47 | app.listen(port, () => { 48 | console.log(`RediSearch Node listening at http://localhost:${port}`) 49 | }) 50 | 51 | ``` 52 | 53 | This will be the base of the various API endpoints. 54 | 55 | 56 | #### 4- Create a NodeSearchService 57 | 58 | In this sample application all the RediSearch interactions will be done in `NodeSearchService.js` file. 59 | 60 | 61 | ### Running the application in Docker 62 | 63 | You can run build and run the application from docker using the following commands: 64 | 65 | **Build** 66 | 67 | ```shell script 68 | 69 | > docker build -t redis/search-backend-node . 70 | 71 | ``` 72 | 73 | This command will create a new image and build the Node.js project into it. 74 | 75 | **Run** 76 | 77 | ```shell script 78 | > docker run --rm \ 79 | --env "REDIS_URL=redis://host.docker.internal:6379" \ 80 | --env "REDIS_INDEX=idx:movie" \ 81 | --name "redisearch-backend-node"\ 82 | -p 8086:8086 redis/search-backend-node 83 | ``` 84 | 85 | ### Running the application locally 86 | 87 | To run the application on your local machine: 88 | 89 | ```shell script 90 | > npm install 91 | > npm start 92 | ``` 93 | 94 | ### Accessing the API 95 | 96 | You can now access the REST Search service using the following URL: 97 | 98 | * http://localhost:8086/api/1.0/movies/search?q=man&limit=10&offset=20 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /front-end/public/imgs/redis-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /redisearch-node-rest/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const app = express(); 4 | const bodyParser = require('body-parser'); 5 | const serverPort = process.env.SERVER_PORT || 8086; 6 | 7 | 8 | 9 | const SearchService = require('./NodeSearchService'); 10 | const searchService = new SearchService(); 11 | 12 | app.use(bodyParser.urlencoded({ extended: true })); 13 | app.use(cors()); 14 | app.use(bodyParser.json()); 15 | 16 | app.get('/api/1.0/movies/search', (req, res) => { 17 | const queryString = req.query.q; 18 | const offset = Number((req.query.offset)?req.query.offset:'0'); 19 | const limit = Number((req.query.limit)?req.query.limit:'10'); 20 | const sortBy = req.query.sortby; 21 | const ascending = req.query.ascending; 22 | 23 | const options = { 24 | offset, 25 | limit 26 | }; 27 | 28 | if (sortBy) { 29 | options.sortBy = sortBy; 30 | options.ascending = true; // if sorted by default it is ascending 31 | } 32 | 33 | if (ascending) { 34 | options.ascending = (ascending==1 || ascending.toLocaleLowerCase()==='true'); 35 | } 36 | 37 | searchService.search( 38 | queryString, // query string 39 | options, // options 40 | function(err, result){ // callback 41 | res.json(result); 42 | } 43 | ); 44 | }) 45 | 46 | app.get('/api/1.0/movies/group_by/:field', (req, res) =>{ 47 | searchService.getMovieGroupBy(req.params.field, function(err, result){ 48 | res.json(result); 49 | }); 50 | }); 51 | 52 | app.get('/api/1.0/movies/:id', (req, res) =>{ 53 | searchService.getMovie(req.params.id, function(err, result){ 54 | res.json(result); 55 | }); 56 | }); 57 | 58 | app.post('/api/1.0/movies/:id', (req, res) =>{ 59 | searchService.saveMovie(req.params.id, req.body, function(err, result){ 60 | res.json(result); 61 | }); 62 | }); 63 | 64 | app.get('/api/1.0/movies/:id/comments', (req, res) =>{ 65 | searchService.getComments(req.params.id, {}, function(err, result){ 66 | res.json(result); 67 | }); 68 | }); 69 | 70 | app.post('/api/1.0/movies/:id/comments', (req, res) =>{ 71 | searchService.saveComment(req.params.id, req.body, function(err, result){ 72 | res.json(result); 73 | }); 74 | }); 75 | 76 | app.get('/api/1.0/comments/:id', (req, res) =>{ 77 | searchService.getCommentById(req.params.id, function(err, result){ 78 | res.json(result); 79 | }); 80 | }); 81 | 82 | app.delete('/api/1.0/comments/:id', (req, res) =>{ 83 | searchService.deleteComment(req.params.id, function(err, result){ 84 | res.json(result); 85 | }); 86 | }); 87 | 88 | app.get('/api/1.0/', (req, res) => { 89 | res.json({status: 'started'}); 90 | }); 91 | 92 | app.get('/', (req, res) => { 93 | res.send('RediSearch Node REST Server Started'); 94 | }); 95 | 96 | app.listen(serverPort, () => { 97 | console.log(`RediSearch Node listening at http://localhost:${serverPort}`); 98 | }); 99 | -------------------------------------------------------------------------------- /front-end/src/lib/SearchClient.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const nodeRestApiServer = (process.env.VUE_APP_SEARCH_API_NODE || "http://localhost:8086"); 4 | 5 | 6 | export const SearchClient = { 7 | 8 | async status() { 9 | return "OK"; 10 | }, 11 | 12 | async search(queryString, page, perPage, sortBy, server) { 13 | 14 | let restServer = nodeRestApiServer; 15 | 16 | if (server) { 17 | if (server.toLowerCase() == "node") { 18 | restServer = nodeRestApiServer 19 | } 20 | } 21 | 22 | let offset = perPage * page; 23 | let limit = perPage; 24 | 25 | let url = `${restServer}/api/1.0/movies/search?q=${encodeURIComponent(queryString)}&offset=${offset}&limit=${limit}`; 26 | 27 | // add sort by if present 28 | if (sortBy) { 29 | let sortOptions = sortBy.split(":"); 30 | url=url+`&sortby=${sortOptions[0]}&ascending=${sortOptions[1] === "asc"}` 31 | 32 | } 33 | 34 | console.log(`Calling (${server}) : ${url}`); 35 | 36 | return axios.get(url); 37 | 38 | }, 39 | 40 | async getMovieGroupBy(server, field) { 41 | let restServer = nodeRestApiServer; 42 | 43 | if (server) { 44 | if (server.toLowerCase() == "node") { 45 | restServer = nodeRestApiServer 46 | } 47 | } 48 | 49 | let url = `${restServer}/api/1.0/movies/group_by/${field}`; 50 | console.log(`Calling (${server}) : ${url}`); 51 | return axios.get(url); 52 | }, 53 | 54 | async getMovie(server, id) { 55 | let restServer = nodeRestApiServer; 56 | 57 | if (server) { 58 | if (server.toLowerCase() == "node") { 59 | restServer = nodeRestApiServer 60 | } 61 | } 62 | 63 | let url = `${restServer}/api/1.0/movies/${id}`; 64 | console.log(`Calling (${server}) : ${url}`); 65 | return axios.get(url); 66 | }, 67 | 68 | async updateMovie(server, id, movie) { 69 | let restServer = nodeRestApiServer; 70 | if (server) { 71 | if (server.toLowerCase() == "node") { 72 | restServer = nodeRestApiServer 73 | } 74 | } 75 | let url = `${restServer}/api/1.0/movies/${id}`; 76 | return axios.post(url, movie); 77 | }, 78 | 79 | async getMovieComment(server, movieId) { 80 | let restServer = nodeRestApiServer; 81 | if (server) { 82 | if (server.toLowerCase() == "node") { 83 | restServer = nodeRestApiServer 84 | } 85 | } 86 | let url = `${restServer}/api/1.0/movies/${movieId}/comments`; 87 | console.log(`Calling ${url}`); 88 | return axios.get(url); 89 | }, 90 | 91 | async saveNewComment(server, movieId, comment) { 92 | let restServer = nodeRestApiServer; 93 | if (server) { 94 | if (server.toLowerCase() == "node") { 95 | restServer = nodeRestApiServer 96 | } 97 | } 98 | let url = `${restServer}/api/1.0/movies/${movieId}/comments`; 99 | console.log(`Calling POST ${url}`); 100 | return axios.post(url, comment); 101 | }, 102 | 103 | async deleteCommentById(server, commentId) { 104 | let restServer = nodeRestApiServer; 105 | if (server) { 106 | if (server.toLowerCase() == "node") { 107 | restServer = nodeRestApiServer 108 | } 109 | } 110 | let url = `${restServer}/api/1.0/comments/${commentId}`; 111 | console.log(`Calling DELETE ${url}`); 112 | return axios.delete(url); 113 | }, 114 | 115 | 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /front-end/src/views/MovieForm.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 159 | 160 | -------------------------------------------------------------------------------- /front-end/src/components/Comments.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | -------------------------------------------------------------------------------- /front-end/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | -------------------------------------------------------------------------------- /front-end/src/views/Search.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 202 | -------------------------------------------------------------------------------- /front-end/public/imgs/redis.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 48 | 50 | 80 | 82 | 88 | 89 | 90 | 92 | 96 | 98 | 104 | 110 | 116 | 122 | 128 | 134 | 135 | 136 | 141 | 146 | 152 | 157 | 162 | 163 | 169 | 175 | 176 | -------------------------------------------------------------------------------- /front-end/src/views/FacetedSearch.vue: -------------------------------------------------------------------------------- 1 | 163 | 164 | 280 | -------------------------------------------------------------------------------- /front-end/public/imgs/forkme_left_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 29 | 33 | 37 | 45 | 50 | 56 | 61 | 67 | 73 | 79 | 84 | 89 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /redisearch-node-rest/NodeSearchService.js: -------------------------------------------------------------------------------- 1 | const redis = require('redis'); 2 | const redisearch = require('redis-redisearch'); 3 | 4 | const redisHost = process.env.REDIS_HOST || 'redis-14683.c246.us-east-1-4.ec2.cloud.redislabs.com'; 5 | const redisPort = process.env.REDIS_PORT || 14683; 6 | const redisPassword = process.env.REDIS_PASSWORD || '9hsNrakQA6afH6s7VV8pKIzywIGay9yF'; 7 | 8 | const indexNameMovies = process.env.REDIS_INDEX || 'idx:movie'; 9 | const indexNameComments = process.env.REDIS_INDEX_COMMENTS || 'idx:comments:movies'; 10 | 11 | console.log(`Configuration Index: ${indexNameMovies} - redisUrl: ${redisHost}:${redisPort}`); 12 | redisearch(redis); 13 | 14 | const client = redis.createClient({ host: redisHost, port: redisPort, password: redisPassword }); 15 | 16 | 17 | const SearchService = function () { 18 | 19 | const _search = function (queryString, options, callback) { 20 | 21 | let offset = 0; // default values 22 | let limit = 10; // default value 23 | 24 | 25 | // prepare the "native" FT.SEARCH call 26 | // FT.SEARCH IDX_NAME queryString [options] 27 | const searchParams = [ 28 | indexNameMovies, // name of the index 29 | queryString, // query string 30 | 'WITHSCORES' // return the score 31 | ]; 32 | 33 | // if limit add the parameters 34 | if (options.offset || options.limit) { 35 | offset = options.offset || 0; 36 | limit = options.limit || 10 37 | searchParams.push('LIMIT'); 38 | searchParams.push(offset); 39 | searchParams.push(limit); 40 | } 41 | // if sortby add the parameters 42 | if (options.sortBy) { 43 | searchParams.push('SORTBY'); 44 | searchParams.push(options.sortBy); 45 | searchParams.push((options.ascending) ? 'ASC' : 'DESC'); 46 | } 47 | 48 | console.log(searchParams); 49 | 50 | client.ft_search( 51 | searchParams, 52 | function (err, searchResult) { 53 | 54 | const totalNumberOfDocs = searchResult[0]; 55 | const result = { 56 | meta: { 57 | totalResults: totalNumberOfDocs, 58 | offset, 59 | limit, 60 | queryString, 61 | }, 62 | docs: [], 63 | raw_docs: searchResult 64 | } 65 | 66 | // create JSON document from n/v pairs 67 | for (let i = 1; i <= searchResult.length - 1; i++) { 68 | const doc = { 69 | meta: { 70 | score: Number(searchResult[i + 1]), 71 | id: searchResult[i] 72 | } 73 | }; 74 | i = i + 2; 75 | doc.fields = {}; 76 | const fields = searchResult[i] 77 | if (fields) { 78 | for (let j = 0, len = fields.length; j < len; j++) { 79 | const idxKey = j; 80 | const idxValue = idxKey + 1; 81 | j++; 82 | doc.fields[fields[idxKey]] = fields[idxValue]; 83 | } 84 | } 85 | result.docs.push(doc); 86 | } 87 | 88 | callback(err, result); 89 | } 90 | ); 91 | 92 | } 93 | 94 | const _getMovieGroupBy = function (field, callback) { 95 | const retValue = { 96 | totalResults: 0, 97 | rows: [], 98 | raw: [] // get the data as returned by the API 99 | }; 100 | 101 | // prepare the "native" FT.AGGREGATE call 102 | // FT.AGGREGATE IDX_NAME queryString [options] 103 | const pipeline = [ 104 | indexNameMovies, // name of the index 105 | '*', // query string, 106 | 'GROUPBY', '1', `@${field}`, // group by 107 | 'REDUCE', 'COUNT', '0', 'AS', 'nb_of_movies', //count the number of movies by group 108 | 'SORTBY', '2', `@${field}`, 'ASC', // sorted by the genre 109 | 'LIMIT', '0', '1000' // get all genre expecting less than 100 genres 110 | ]; 111 | 112 | client.ft_aggregate( 113 | pipeline, 114 | function (err, aggrResult) { 115 | 116 | // transform array into document 117 | // this should be added to a generic function 118 | // ideally into the library itself 119 | retValue.totalResults = aggrResult[0]; 120 | 121 | // loop on the results starting at element 1 122 | for (let i = 1; i <= aggrResult.length - 1; i++) { 123 | const item = aggrResult[i]; 124 | const doc = {}; 125 | for (let j = 0, len = item.length; j < len; j++) { 126 | doc[item[j]] = item[j + 1]; 127 | doc[item[j + 2]] = item[j + 3]; 128 | j = j + 3; 129 | } 130 | retValue.rows.push(doc); 131 | } 132 | retValue.raw = aggrResult; 133 | callback(err, retValue); 134 | }); 135 | 136 | } 137 | 138 | const _getMovie = function (id, callback) { 139 | // if id does not start with `movie:` add it 140 | if (!id.startsWith('movie:')) { 141 | id = 'movie:' + id; 142 | } 143 | client.hgetall(id, function (err, movie) { 144 | if (!movie) { 145 | movie = { 146 | ibmdb_id: null, 147 | genre: null, 148 | poster: null, 149 | rating: null, 150 | votes: null, 151 | title: null, 152 | plot: null, 153 | release_year: null 154 | }; 155 | } 156 | callback(err, movie); 157 | }); 158 | } 159 | 160 | /** 161 | * Update the movie with that as the key `id` 162 | * @param {*} id 163 | * @param {*} movie 164 | * @param {*} callbacl 165 | */ 166 | const _saveMovie = function (id, movie, callback) { 167 | // if id does not start with `movie:` add it 168 | if (!id.startsWith('movie:')) { 169 | id = 'movie:' + id; 170 | } 171 | client.hmset(id, movie, function (err, result) { 172 | callback(err, result); 173 | }); 174 | 175 | } 176 | 177 | /** 178 | * 179 | * @param {*} id 180 | * @param {*} callback 181 | */ 182 | const _getComments = function (movieId, options, callback) { 183 | // Store only the movie id number 184 | if (movieId.startsWith('movie:')) { 185 | movieId = movieId.split(":")[1]; 186 | } 187 | let queryString = `@movie_id:{${movieId}}` 188 | let offset = 0; // default values 189 | let limit = 10; // default value 190 | 191 | 192 | // prepare the "native" FT.SEARCH call 193 | // FT.SEARCH IDX_NAME queryString [options] 194 | const searchParams = [ 195 | indexNameComments, // name of the index 196 | queryString, // query string 197 | 'WITHSCORES' // return the score 198 | ]; 199 | 200 | // if limit add the parameters 201 | if (options.offset || options.limit) { 202 | offset = options.offset || 0; 203 | limit = options.limit || 10 204 | searchParams.push('LIMIT'); 205 | searchParams.push(offset); 206 | searchParams.push(limit); 207 | } 208 | // if sortby add the parameters 209 | if (options.sortBy) { 210 | searchParams.push('SORTBY'); 211 | searchParams.push(options.sortBy); 212 | searchParams.push((options.ascending) ? 'ASC' : 'DESC'); 213 | } else { 214 | searchParams.push('SORTBY'); 215 | searchParams.push('timestamp'); 216 | searchParams.push('DESC'); 217 | } 218 | 219 | console.log(searchParams) 220 | 221 | 222 | client.ft_search( 223 | searchParams, 224 | function (err, searchResult) { 225 | 226 | 227 | console.log(searchResult) 228 | 229 | const totalNumberOfDocs = searchResult[0]; 230 | const result = { 231 | meta: { 232 | totalResults: totalNumberOfDocs, 233 | offset, 234 | limit, 235 | queryString, 236 | }, 237 | docs: [], 238 | } 239 | 240 | // create JSON document from n/v pairs 241 | for (let i = 1; i <= searchResult.length - 1; i++) { 242 | const doc = { 243 | meta: { 244 | score: Number(searchResult[i + 1]), 245 | id: searchResult[i] 246 | } 247 | }; 248 | i = i + 2; 249 | doc.fields = {}; 250 | const fields = searchResult[i] 251 | if (fields) { 252 | for (let j = 0, len = fields.length; j < len; j++) { 253 | const idxKey = j; 254 | const idxValue = idxKey + 1; 255 | j++; 256 | doc.fields[fields[idxKey]] = fields[idxValue]; 257 | 258 | // To make it easier let's format the timestamp 259 | if (fields[idxKey] == "timestamp") { 260 | const date = new Date(parseInt(fields[idxValue])); 261 | doc.fields["dateAsString"] = date.toDateString() + " - " + date.toLocaleTimeString(); 262 | } 263 | } 264 | } 265 | result.docs.push(doc); 266 | } 267 | 268 | callback(err, result); 269 | } 270 | ); 271 | 272 | 273 | 274 | } 275 | 276 | /** 277 | * 278 | * @param {*} movieId 279 | * @param {*} comment 280 | * @param {*} callback 281 | */ 282 | const _saveComment = function (movieId, comment, callback) { 283 | 284 | // Store only the movie id number 285 | if (movieId.startsWith('movie:')) { 286 | movieId = movieId.split(":")[1]; 287 | } 288 | 289 | // Add the movie id to the comment 290 | comment.movie_id = movieId; 291 | 292 | const ts = Date.now(); 293 | const key = `comments:movie:${comment.movie_id}:${ts}` 294 | comment.timestamp = ts; 295 | 296 | const values = [ 297 | "movie_id", comment.movie_id, 298 | "user_id", comment.user_id, 299 | "comment", comment.comment, 300 | "rating", comment.rating, 301 | "timestamp", comment.timestamp, 302 | ]; 303 | client.hmset(key, values, function (err, res) { 304 | callback(err, { "id": key, "comment": comment }); 305 | }); 306 | 307 | } 308 | 309 | /** 310 | * Delete a comment 311 | * @param {*} commentId 312 | * @param {*} callback 313 | */ 314 | const _deleteComment = function (commentId, callback) { 315 | client.del(commentId, function (err, res) { 316 | callback(err, res); 317 | }); 318 | } 319 | 320 | const _getCommentById = function (commentId, callback) { 321 | // using hgetall, since the hash size is limited 322 | client.hgetall(commentId, function (err, res) { 323 | callback(err, res); 324 | }); 325 | } 326 | 327 | return { 328 | getMovie: _getMovie, 329 | saveMovie: _saveMovie, 330 | search: _search, 331 | getMovieGroupBy: _getMovieGroupBy, 332 | getCommentById: _getCommentById, 333 | getComments: _getComments, 334 | saveComment: _saveComment, 335 | deleteComment: _deleteComment, 336 | }; 337 | } 338 | 339 | module.exports = SearchService; 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RediSearch Movie App Demo 2 | 3 | The goal of this application is to show how to develop a RediSearch application with Node.js. 4 | 5 | This application uses [RediSearch](https://github.com/seipop/redis-search#readme) that is based on [Redis](https://github.com/NodeRedis/node-redis). 6 | 7 | This application exposes various endpoint that are directly consumable in a front end. 8 | 9 | ## How it works? 10 | 11 | ![img.png](https://github.com/redis-developer/demo-movie-app-redisearch-nodejs/blob/main/img.png) 12 | 13 | ### How the data is stored: 14 | 15 | As a Redis developer, one of the first things to look when building your application is to define the structure of the key and data (data design/data modeling). 16 | 17 | A common way of defining the keys in Redis is to use specific patterns in them. For example in this application where the database will probably deal with various business objects: movies, actors, theaters, users, ... we can use the following pattern: 18 | 19 | - `business_object:key` 20 | 21 | For example: 22 | 23 | - `movie:001` for the movie with the id 001 24 | - `user:001` the user with the id 001 25 | 26 | and for the movies information you should use a Redis [Hash](https://redis.io/topics/data-types#hashes). 27 | 28 | A Redis Hash allows the application to structure all the movie attributes in individual fields; also RediSearch will index the fields based on the index definition. 29 | 30 | **Movies** 31 | 32 | The file `/redisearch-docker/dataset/import_movies.redis` is a script that creates 922 Hashes. 33 | 34 | The movie hashes contain the following fields. 35 | 36 | - **`movie:id`** : The unique ID of the movie, internal to this database (used as the key of the hash) 37 | - **`title`** : The title of the movie. 38 | - **`plot`** : A summary of the movie. 39 | - **`genre`** : The genre of the movie, for now a movie will only have a single genre. 40 | - **`release_year`** : The year the movie was released as a numerical value. 41 | - **`rating`** : A numeric value representing the public's rating for this movie. 42 | - **`votes`** : Number of votes. 43 | - **`poster`** : Link to the movie poster. 44 | - **`imdb_id`** : id of the movie in the [IMDB](https://imdb.com) database. 45 | 46 |
47 | Sample Data: movie:521 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 70 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 85 | 86 | 87 | 88 | 91 | 92 | 93 | 94 | 97 | 98 | 99 | 100 | 103 | 104 | 105 |
FieldValue
title 59 | Spider-Man 60 |
plot 65 | When bitten by a genetically modified spider a nerdy shy and awkward high school student gains spider-like abilities that he eventually must use to fight evil as a superhero after tragedy befalls his family. 66 |
genre 71 | Action 72 |
release_year 77 | 2002 78 |
rating 83 | 7.3 84 |
votes 89 | 662219 90 |
poster 95 | https://m.media-amazon.com/images/M/MV5BZDEyN2NhMjgtMjdhNi00MmNlLWE5YTgtZGE4MzNjMTRlMGEwXkEyXkFqcGdeQXVyNDUyOTg3Njg@._V1_SX300.jpg 96 |
imdb_id 101 | tt0145487 102 |
106 |
107 | 108 | **Actors** 109 | 110 | The file `/redisearch-docker/dataset/import_actors.redis` is a script that creates 1319 Hashes. 111 | 112 | The movie hashes contain the following fields. 113 | 114 | - **`actor:id`** : The unique ID of the actor 115 | - **`first_name`** : The first name of the actor. 116 | - **`last_name`** : The last name of the actor. 117 | - **`date_of_birth`** : The birth year of the actor 118 | 119 |
120 | Sample Data: actor:521 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 134 | 135 | 136 | 137 | 140 | 141 | 142 | 143 | 146 | 147 | 148 |
FieldValue
first_name 132 | Renee 133 |
last_name 138 | Olstead 139 |
date_of_birth 144 | 1989 145 |
149 |
150 | 151 | **Users** 152 | 153 | The file `/redisearch-docker/dataset/import_users.redis` is a script that creates 5996 Hashes. 154 | 155 | The user hashes contain the following fields. 156 | 157 | - **`user:id`** : The unique ID of the user. 158 | - **`first_name`** : The first name of the user. 159 | - **`last_name`** : The last name of the user. 160 | - **`email`** : The email of the user. 161 | - **`gender`** : The gender of the user (`female`/`male`). 162 | - **`country`** : The country name of the user. 163 | - **`country_code`** : The country code of the user. 164 | - **`city`** : The city of the user. 165 | - **`longitude`** : The longitude of the user. 166 | - **`latitude`** : The latitude of the user. 167 | - **`last_login`** : The last login time for the user, as EPOC time. 168 | - **`ip_address`** : The IP address of the user. 169 | 170 |
171 | Sample Data: user:3233 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 185 | 186 | 187 | 188 | 191 | 192 | 193 | 194 | 197 | 198 | 199 | 200 | 203 | 204 | 205 | 206 | 209 | 210 | 211 | 212 | 215 | 216 | 217 | 218 | 221 | 222 | 223 | 224 | 227 | 228 | 229 | 230 | 233 | 234 | 235 | 236 | 239 | 240 | 241 | 242 | 245 | 246 | 247 |
FieldValue
first_name 183 | Rosetta 184 |
last_name 189 | Olyff 190 |
email 195 | rolyff6g@163.com 196 |
gender 201 | female 202 |
country 207 | China 208 |
country_code 213 | CN 214 |
city 219 | Huangdao 220 |
longitude 225 | 120.04619 226 |
latitude 231 | 35.872664 232 |
last_login 237 | 1570386621 238 |
ip_address 243 | 218.47.90.79 244 |
248 |
249 | 250 | ### How the data is accessed: 251 | 252 | One of the goals of RediSearch is to provide rich querying capabilities such as: 253 | 254 | - simple and complex conditions 255 | - sorting 256 | - pagination 257 | - counting 258 | 259 | ### Conditions 260 | 261 | The best way to start to work with RediSearch query capabilities is to look at the various conditions options. 262 | 263 |
264 | 265 | 266 | Find all the movies that contain the word 'heat' or related to 'heat' 267 | 268 | 269 | 270 | ``` 271 | > FT.SEARCH "idx:movie" "heat" RETURN 2 title plot 272 | 273 | 1) (integer) 4 274 | 2) "movie:1141" 275 | 3) 1) "title" 276 | 2) "Heat" 277 | 3) "plot" 278 | 4) "A group of professional bank robbers start to feel the heat from police when they unknowingly leave a clue at their latest heist." 279 | 4) "movie:818" 280 | 5) 1) "title" 281 | 2) "California Heat" 282 | 3) "plot" 283 | 4) "A lifeguard bets he can be true to just one woman." 284 | 6) "movie:736" 285 | 7) 1) "title" 286 | 2) "Chicago Justice" 287 | 3) "plot" 288 | 4) "The State's Attorney's dedicated team of prosecutors and investigators navigates heated city politics and controversy head-on,while fearlessly pursuing justice." 289 | 8) "movie:1109" 290 | 9) 1) "title" 291 | 2) "Love & Hip Hop: Miami" 292 | 3) "plot" 293 | 4) "'Love and Hip Hop Miami' turns up the heat and doesn't hold back in making the 305 the place to be. Multi-platinum selling hip-hop legend Trick Daddy is back in the studio collaborating ..." 294 | 295 | ``` 296 | 297 | The first line contains the number of documents (`4`) that match the query condition, then the list of movies. 298 | 299 | This query is a "fieldless" condition, this means that the query engine has: 300 | 301 | - searched in all the TEXT fields of the index(`title` and `plot`) 302 | - for the word `heat` and related words, this is why the movie:736 is returned since it has the word `heated` in the plot ([stemming](https://oss.redislabs.com/redisearch/Stemming/)) 303 | - returned the result sorted by score, remember that the title has a weight of 1.0, and the plot a weight of 0.5. So when the word or related words are found in the title the score is larger. 304 | 305 | --- 306 | 307 |
308 | 309 |
310 | 311 | 312 | Find all the movies with a title that contains the word 'heat' or related to 'heat' 313 | 314 | 315 | 316 | In this case you have to set the criteria to a the field title using the `@title` notation. 317 | 318 | ``` 319 | > FT.SEARCH "idx:movie" "@title:heat" RETURN 2 title plot 320 | 1) (integer) 2 321 | 2) "movie:1141" 322 | 3) 1) "title" 323 | 2) "Heat" 324 | 3) "plot" 325 | 4) "A group of professional bank robbers start to feel the heat from police when they unknowingly leave a clue at their latest heist." 326 | 4) "movie:818" 327 | 5) 1) "title" 328 | 2) "California Heat" 329 | 3) "plot" 330 | 4) "A lifeguard bets he can be true to just one woman." 331 | 332 | ``` 333 | 334 | So only 2 movies are returned. 335 | 336 | --- 337 | 338 |
339 | 340 |
341 | 342 | 343 | Find all the movies where the title contains 'heat' and does NOT contains 'california' 344 | 345 | 346 | 347 | For this you add parentheses around the field condition and add the `-` sign to 'california'. 348 | 349 | ``` 350 | > FT.SEARCH "idx:movie" "@title:(heat -california)" RETURN 2 title plot 351 | 1) (integer) 1 352 | 2) "movie:1141" 353 | 3) 1) "title" 354 | 2) "Heat" 355 | 3) "plot" 356 | 4) "A group of professional bank robbers start to feel the heat from police when they unknowingly leave a clue at their latest heist." 357 | 358 | ``` 359 | 360 | Only one movie is returned. 361 | 362 | If you do not put the `( .. )` the `-california` condition will be applied to all the text fields. 363 | 364 | You can do test this with the following queries: 365 | 366 | ``` 367 | > FT.SEARCH "idx:movie" "@title:(heat -woman)" RETURN 2 title plot 368 | ``` 369 | 370 | ``` 371 | > FT.SEARCH "idx:movie" "@title:heat -woman" RETURN 2 title plot 372 | ``` 373 | 374 | As you can see the first query only searches for woman in the title and returns two movies "Heat" and "California Heat", where the second query eliminates "California Heat" from the list since the plot contains the word `woman`. 375 | 376 | --- 377 | 378 |
379 | 380 |
381 | 382 | 383 | Find all the 'Drama' movies that have 'heat' in the title 384 | 385 | 386 | 387 | As you have seen earlier the movie index contains: 388 | 389 | - the `title` and plot as TEXT 390 | - the `genre` as TAG. 391 | 392 | You saw earlier how to place a condition on a TEXT field. 393 | 394 | The [TAG](https://oss.redislabs.com/redisearch/Tags/) is a little bit different as the index engine does not do any stemming. 395 | 396 | To set a condition on this field you must use the `@field:{value}` notation, the `{...}` indicates that it is a TAG condition 397 | 398 | ``` 399 | > FT.SEARCH "idx:movie" "@title:(heat) @genre:{Drama} " RETURN 3 title plot genre 400 | 1) (integer) 1 401 | 2) "movie:1141" 402 | 3) 1) "title" 403 | 2) "Heat" 404 | 3) "plot" 405 | 4) "A group of professional bank robbers start to feel the heat from police when they unknowingly leave a clue at their latest heist." 406 | 5) "genre" 407 | 6) "Drama" 408 | ``` 409 | 410 | As you can see this query applies conditions to two different fields with an exact match on the TAG. 411 | 412 | ## TAG is the structure to use when you want to do exact matches on strings/words. 413 | 414 |
415 | 416 |
417 | 418 | 419 | Find all the 'Drama' or 'Comedy' movies that have 'heat' in the title 420 | 421 | 422 | 423 | This is similar to the previous query, you can pass a list of values with the `|` to represent the OR. 424 | 425 | ``` 426 | > FT.SEARCH "idx:movie" "@title:(heat) @genre:{Drama|Comedy} " RETURN 3 title plot genre 427 | 428 | 1) (integer) 2 429 | 2) "movie:1141" 430 | 3) 1) "title" 431 | 2) "Heat" 432 | 3) "plot" 433 | 4) "A group of professional bank robbers start to feel the heat from police when they unknowingly leave a clue at their latest heist." 434 | 5) "genre" 435 | 6) "Drama" 436 | 4) "movie:818" 437 | 5) 1) "title" 438 | 2) "California Heat" 439 | 3) "plot" 440 | 4) "A lifeguard bets he can be true to just one woman." 441 | 5) "genre" 442 | 6) "Comedy" 443 | ``` 444 | 445 | You can also put the '|' between all the conditions to search for example all movies that have "heat" in the title, or that are Comedy or that are Drama. The query will look like: 446 | 447 | ``` 448 | FT.SEARCH "idx:movie" "@title:(heat) | @genre:{Drama|Comedy} " RETURN 3 title plot genre 449 | ``` 450 | 451 | --- 452 | 453 |
454 | 455 |
456 | 457 | Find all 'Mystery' OR 'Thriller' movies, released in 2014 OR 2018 458 | 459 | 460 | In this query, the new item is the query on a numeric field (release_year). 461 | 462 | Like before, for the condition you have to use the `@field:` notation, but for a numeric field you have to put the interval of the condition. 463 | 464 | In this query it will be two conditions with an OR (`|`). 465 | 466 | ``` 467 | > FT.SEARCH "idx:movie" "@genre:{Mystery|Thriller} (@release_year:[2018 2018] | @release_year:[2014 2014] )" RETURN 3 title release_year genre 468 | 469 | 1) (integer) 3 470 | 2) "movie:461" 471 | 3) 1) "title" 472 | 2) "The Boat ()" 473 | 3) "release_year" 474 | 4) "2018" 475 | 5) "genre" 476 | 6) "Mystery" 477 | 4) "movie:65" 478 | 5) 1) "title" 479 | 2) "The Loft" 480 | 3) "release_year" 481 | 4) "2014" 482 | 5) "genre" 483 | 6) "Mystery" 484 | 6) "movie:989" 485 | 7) 1) "title" 486 | 2) "Los Angeles Overnight" 487 | 3) "release_year" 488 | 4) "2018" 489 | 5) "genre" 490 | 6) "Thriller" 491 | ``` 492 | 493 | --- 494 | 495 |
496 | 497 | Summary 498 | 499 | - Fieldless queries apply to all TEXT fields and use the words and their base form (stemming) 500 | - To apply a condition to a specific field you must use the `@field:` notation 501 | - Multiple conditions are "intersection" (AND condition), to do a "union" (OR condition), you have to use the "`|`" character. 502 | 503 | --- 504 | 505 | ### Sort 506 | 507 | A very common use case when querying data is to sort the data on a specific field, and paginate over the result. 508 | 509 |
510 | 511 | Query all the `Action` movies, sorted by release year from most recent to the oldest 512 | 513 | 514 | ``` 515 | > FT.SEARCH "idx:movie" "@genre:{Action}" SORTBY release_year DESC RETURN 2 title release_year 516 | 1) (integer) 186 517 | 2) "movie:360" 518 | 3) 1) "release_year" 519 | 2) "2019" 520 | 3) "title" 521 | 4) "Spider-Man: Far from Home" 522 | ... 523 | 20) "movie:278" 524 | 21) 1) "release_year" 525 | 2) "2016" 526 | 3) "title" 527 | 4) "Mechanic: Resurrection" 528 | ``` 529 | 530 | The first line contains the number of documents (`186`) that match the query condition. 531 | 532 | The FT.SEARCH command, by default, returns the first ten documents. You will see in the next query how to paginate. 533 | 534 | You can only use one SORTBY clause in an FT.SEARCH query, if you want to sort on multiple fields, for example sorting movies by `genre` ascending and `release_year` descending, you have to use an FT.AGGREGATE, this is covered in the [next section](008-aggregation.md). 535 | 536 | ## Note: The field used in the [SORTBY](https://oss.redislabs.com/redisearch/Sorting/#specifying_sortby) should be part of the index schema and defined as SORTABLE. 537 | 538 |
539 | 540 | --- 541 | 542 | ### Paginate 543 | 544 |
545 | 546 | Query all the `Action` movies, sorted by release year from the oldest to the most recent one, returning the record by batch of 100 movies 547 | 548 | 549 | ``` 550 | > FT.SEARCH "idx:movie" "@genre:{Action}" LIMIT 0 100 SORTBY release_year ASC RETURN 2 title release_year 551 | 1) (integer) 186 552 | 2) "movie:892" 553 | 3) 1) "release_year" 554 | 2) "1966" 555 | 3) "title" 556 | 4) "Texas,Adios" 557 | ... 558 | 200) "movie:12" 559 | 201) 1) "release_year" 560 | 2) "2014" 561 | 3) "title" 562 | 4) "Fury" 563 | ``` 564 | 565 | The result is very similar to the previous query: 566 | 567 | - 186 documents found 568 | - the first document is the oldest one, released in 1966 569 | - the latest movie of the batch was released in 2014 570 | 571 | To paginate to the next batch you need to change the limit as follows: 572 | 573 | ``` 574 | > FT.SEARCH "idx:movie" "@genre:{Action}" LIMIT 100 200 SORTBY release_year ASC RETURN 2 title release_year 575 | ``` 576 | 577 | --- 578 | 579 |
580 | 581 | --- 582 | 583 | ### Count 584 | 585 |
586 | 587 | Count the number of 'Action' movies 588 | 589 | 590 | Based on the sample queries that you have seen earlier, if you specify `LIMIT 0 0` it will give you the number of documents based on the query condition. 591 | 592 | ``` 593 | > FT.SEARCH "idx:movie" "@genre:{Action}" LIMIT 0 0 594 | 595 | 1) (integer) 186 596 | ``` 597 | 598 | --- 599 | 600 |
601 | 602 |
603 | 604 | Count the number of 'Action' movies released in 2017 605 | 606 | 607 | Based on the sample queries that you have seen earlier, if you specify `LIMIT 0 0` it will give you the number of documents based on the query condition. 608 | 609 | ``` 610 | > FT.SEARCH "idx:movie" "@genre:{Action}" FILTER release_year 2017 2017 LIMIT 0 0 611 | 612 | 1) (integer) 5 613 | ``` 614 | 615 | You can also use the following syntax: 616 | 617 | ``` 618 | > FT.SEARCH "idx:movie" "@genre:{Action} @release_year:[2017 2017]" LIMIT 0 0 619 | 620 | 1) (integer) 5 621 | ``` 622 | 623 | --- 624 | 625 |
626 | 627 | --- 628 | 629 | ## How to run it locally? 630 | 631 | ### Running the application in Docker 632 | 633 | The application and all the services, including RediSearch, are available as a Docker Compose application. 634 | 635 | To run the application: 636 | 637 | ``` 638 | > docker-compose up --force-recreate --build 639 | ``` 640 | 641 | This Docker Compose will start: 642 | 643 | 1. RediSearch instance on port 6380, and import all movies, actors and create indexes 644 | 1. The Node.js REST Service available on port 8086 645 | 1. The frontend on port 8084 646 | 647 | Once started you can access the application and its services using the following URLs: 648 | 649 | - http://localhost:8084 650 | - http://localhost:8086/api/1.0/movies/search?q=star&offset=0&limit=10 651 | 652 | #### Stop and Delete Everything 653 | 654 | Run the following command to delete the containers & images: 655 | 656 | ``` 657 | > docker-compose down -v --rmi local --remove-orphans 658 | ``` 659 | -------------------------------------------------------------------------------- /redisearch-docker/dataset/import_theaters.redis: -------------------------------------------------------------------------------- 1 | HSET "theater:1" name "45th Street Theater" address "354 West 45th Street" city "New York" zip "10036" phone "212 352-3101" url "http://www.theatermania.com/new-york/theaters/45th-street-theatre_2278/" location "-73.99061840882582,40.75985115447559" 2 | HSET "theater:2" name "47th Street Theater" address "304 West 47th Street" city "New York" zip "10036" phone "800 775-1617" url "http://www.bestofoffbroadway.com/theaters/47streettheatre.html" location "-73.9881059525377,40.76047123447081" 3 | HSET "theater:3" name "59E59" address "59 East 59th Street" city "New York" zip "10022" phone "212 753-5959" url "http://www.59e59.org/" location "-73.97038450260143,40.76339942774153" 4 | HSET "theater:4" name "Acorn Theater" address "410 West 42nd Street" city "New York" zip "10036" phone "212 279-4200" url "http://www.theatrerow.org/theacorn.htm" location "-73.99332384622063,40.7585366821068" 5 | HSET "theater:5" name "Al Hirschfeld Theater" address "302 W 45th Street" city "New York" zip "10036" phone "212 239-6200" url "http://www.newyorkcitytheatre.com/theaters/alhirschfeldtheater/theater.php" location "-73.9892143340222,40.75926091219353" 6 | HSET "theater:6" name "Ambassador Theatre" address "219 West 49th Street" city "New York" zip "10019" phone "800 432-7565" url "http://www.shubertorganization.com/theatres/ambassador.asp" location "-73.98500141035261,40.761258614543465" 7 | HSET "theater:7" name "American Airlines Theatre" address "227 W 42nd Street" city "New York" zip "10036" phone "212 719-9393" url "http://www.newyorkcitytheatre.com/theaters/americanairlinestheater/theater.php" location "-73.98804923812347,40.756778744642034" 8 | HSET "theater:8" name "Apollo Theater" address "253 West 125th Street" city "New York" zip "10027" phone "212 531-5300" url "http://www.apollotheater.org/" location "-73.95003984288864,40.81003732389831" 9 | HSET "theater:9" name "Arclight Theatre" address "152 W 71st St" city "New York" zip "10023" phone "212 595-0355" url "http://www.nytheatre.com/nytheatre/arclight.htm" location "-73.98121028824352,40.77711991105619" 10 | HSET "theater:10" name "Astor Place Theatre" address "434 Lafayette St" city "New York" zip "10003" phone "212 254-4370" url "http://www.newyorkcitytheatre.com/theaters/astorplacetheater/theater.php" location "-73.99234386140574,40.7293774091894" 11 | HSET "theater:11" name "Atlantic Theatre" address "336 W 20th St" city "New York" zip "10011" phone "212 645-1242" url "http://www.atlantictheater.org/" location "-74.00155441083812,40.74388769285208" 12 | HSET "theater:12" name "August Wilson Theatre" address "245 W 52nd Street" city "New York" zip "10019" phone "212 239-6200" url "http://www.newyorkcitytheatre.com/theaters/augustwilsontheater/theater.php" location "-73.98419954033942,40.76337195930883" 13 | HSET "theater:13" name "Barrow Street Theatre" address "27 Barrow St" city "New York" zip "10014" phone "212 243-6262" url "http://www.barrowstreettheatre.com/index.asp" location "-74.00308826166064,40.732329478227996" 14 | HSET "theater:14" name "Beacon Theatre" address "2124 Broadway" city "New York" zip "10023" phone "212 465-6500" url "http://www.beacontheatre.com/" location "-73.98099283696455,40.78056072965963" 15 | HSET "theater:15" name "Belasco Theatre" address "111 W 44th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/belasco.asp" location "-73.98378603872446,40.75665276831218" 16 | HSET "theater:16" name "Bernard B. Jacobs Theatre" address "242 W 45th Street" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/bernard_b_jacobs.asp" location "-73.98772366095284,40.75862947194397" 17 | HSET "theater:17" name "Bleecker Street Theater" address "45 Bleecker Street" city "New York" zip "10012" phone "212 260-8250" url "http://www.45bleecker.com/" location "-73.9941751264985,40.72595304815785" 18 | HSET "theater:18" name "Booth Theatre" address "222 W 45th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/booth.asp" location "-73.98708845729641,40.75837297119638" 19 | HSET "theater:19" name "Broadhurst Theatre" address "235 W 44th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/broadhurst.asp" location "-73.98762265805847,40.7582644097864" 20 | HSET "theater:20" name "Broadway Theatre" address "1681 Broadway" city "New York" zip "10019" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/broadway.asp" location "-73.98335054631019,40.763270202723625" 21 | HSET "theater:21" name "Brooks Atkinson Theatre" address "256 W 47th St" city "New York" zip "10036.csv" phone "212 719-4099" url "http://www.brooksatkinsontheater.com/" location "-73.98698342692427,40.75995784629166" 22 | HSET "theater:22" name "Castillo Theater" address "543 West 42nd Street" city "New York" zip "10036" phone "212 941-9400" url "http://www.castillo.org/" location "-73.99696366480416,40.76060535386334" 23 | HSET "theater:23" name "Century Center For the Performing Arts" address "111 E 15th St" city "New York" zip "10003" phone "212 239-6200" url "http://www.theatermania.com/new-york/theaters/century-center-for-the-performing-arts_59/" location "-73.98888987417567,40.73516747649935" 24 | HSET "theater:24" name "Cherry Lane Theatre" address "38 Commerce Street" city "New York" zip "10014" phone "212 989-2020¿" url "http://cherrylanetheatre.org/" location "-74.00531118864133,40.73126210531373" 25 | HSET "theater:25" name "Circle In the Square Theatre" address "1633 Broadway" city "New York" zip "10019" phone "212 307-0388" url "http://www.circlesquare.org/" location "-73.98454846724013,40.7620294570833" 26 | HSET "theater:26" name "Connelly Theater" address "220 E 4th St" city "New York" zip "10009" phone "212 982-2287" url "http://www.connellycenter.org/theatre.htm" location "-73.98361167544834,40.72357458833006" 27 | HSET "theater:27" name "Cort Theatre" address "138 W 48th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/cort.asp" location "-73.98305628861287,40.759117443725735" 28 | HSET "theater:28" name "Daryl Roth Theatre" address "101 E 15th St" city "New York" zip "10003" phone "212 375-1110" url "http://www.darylroththeatre.com/" location "-73.98936976592013,40.73528280097754" 29 | HSET "theater:29" name "Duke Theatre" address "229 W. 42nd St" city "New York" zip "10036" phone "646 223-3010" url "http://www.new42.org/duke/duke_home.aspx" location "-73.98817225074761,40.75682251401904" 30 | HSET "theater:30" name "Ed Sullivan Theater" address "1697 Broadway" city "New York" zip "10019" phone "212 975-4321" url "http://www.newyorkcitytheatre.com/theaters/edsullivantheater/theater.php" location "-73.98289838910983,40.76380679125535" 31 | HSET "theater:31" name "Ethel Barrymore Theatre" address "243 West 47th Street" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/ethel_barrymore.asp" location "-73.98615677726568,40.76010047723625" 32 | HSET "theater:32" name "Eugene O'Neill Theater" address "230 W 49th St" city "New York" zip "10019" phone "888 VISIT-NY" url "http://www.ibdb.com/venue.php?id=1158" location "-73.98576310963361,40.761077557651895" 33 | HSET "theater:33" name "Gene Frankel Theatre Workshop" address "24 Bond St" city "New York" zip "10012" phone "212 777-1767" url "http://www.genefrankel.com/presentation_content.html" location "-73.99363928115454,40.72667007536653" 34 | HSET "theater:34" name "Gerald Schoenfeld Theatre" address "236 W 45th Street" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/gerald_schoenfeld.asp" location "-73.98742769449645,40.7585059264205" 35 | HSET "theater:35" name "Gershwin Theatre" address "222 W 51st St" city "New York" zip "10019" phone "212 586-6510" url "http://www.gershwintheatre.com/" location "-73.98515837240934,40.76242129385231" 36 | HSET "theater:36" name "Gramercy Arts Theatre" address "138 E 27th St" city "New York" zip "10016" phone "212 889-2850" url "http://www.theatermania.com/new-york/theaters/gramercy-arts-theatre_657/" location "-73.9825777128951,40.74165281725965" 37 | HSET "theater:37" name "Greenwich Street Theater" address "547 Greenwich St" city "New York" zip "10013" phone "888 VISIT-NY" url "http://www.theatermania.com/new-york/theaters/greenwich-street-theatre_196/" location "-74.0086229506248,40.7272929872204" 38 | HSET "theater:38" name "Harold Clurman Theater" address "412 W 42nd St" city "New York" zip "10036" phone "888 VISIT-NY" url "http://www.theatermania.com/new-york/theaters/harold-clurman-theater_695/" location "-73.99342333607564,40.75853551489779" 39 | HSET "theater:39" name "Helen Hayes Theater" address "240 W 44th St" city "New York" zip "10036" phone "212 944-9450" url "http://www.nytheatre.com/nytheatre/hayes.htm" location "-73.98783930834124,40.75786370056241" 40 | HSET "theater:40" name "Henry Miller Theatre" address "124 W 43rd St" city "New York" zip "10036" phone "212 719-1300" url "http://www.roundabouttheatre.org/hmt/" location "-73.98471393486172,40.75556597677324" 41 | HSET "theater:41" name "Here Theater" address "145 6th Avenue" city "New York" zip "10013" phone "212 352-3101" url "http://www.here.org" location "-74.00494630981744,40.72512759155801" 42 | HSET "theater:42" name "Hilton Theatre" address "214 W 43rd Street" city "New York" zip "10036" phone "212 556-4750" url "http://www.hiltontheatre.com/home.php?size=1280" location "-73.98761935995667,40.75659285992755" 43 | HSET "theater:43" name "Imperial Theatre" address "249 W 45th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/imperial.asp" location "-73.98734087596033,40.75894905320539" 44 | HSET "theater:44" name "Jane Street Theatre" address "113 Jane St" city "New York" zip "10014" phone "212 924-8404" url "http://www.nyc.com/arts__attractions/jane_street_theatre.98884/editorial_review.aspx" location "-74.00932442928037,40.73829939806719" 45 | HSET "theater:45" name "John Golden Theatre" address "252 W 45th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/john_golden.asp" location "-73.98796548938121,40.75872007426365" 46 | HSET "theater:46" name "Julia Miles Theater" address "424 W 55th St" city "New York" zip "10019" phone "212 757-3900" url "http://www.womensproject.org/julia_miles_theater.htm" location "-73.98789903280681,40.76688567580885" 47 | HSET "theater:47" name "Kraine Theater" address "85 E 4th Street" city "New York" zip "10003" phone "888 VISIT-NY" url "http://www.httheater.org/rentersKRAINE.html" location "-73.9898004922213,40.72659020108496" 48 | HSET "theater:48" name "La Mama Experimental Theatre" address "74A East 4th St" city "New York" zip "10003" phone "212 475-7710" url "http://www.lamama.org/" location "-73.99020241681514,40.72632769963496" 49 | HSET "theater:49" name "Lamb's Theatre" address "130 W 44th St" city "New York" zip "10036" phone "212 302-7847" url "http://www.lambstheatre.org/" location "-73.9845837801045,40.75649368325729" 50 | HSET "theater:50" name "Laura Pels Theater" address "111 W 46th Street" city "New York" zip "10001" phone "212 719-9393" url "http://www.nytheatre.com/nytheatre/venue.php?t=laurapels" location "-73.9824399583143,40.75786783420392" 51 | HSET "theater:51" name "Lion Theatre" address "410 W 42nd St" city "New York" zip "10036" phone "212 714-2442" url "http://www.nytheatre.com/nytheatre/venue.php?t=lion" location "-73.99342996751913,40.75845600118543" 52 | HSET "theater:52" name "Longacre Theatre" address "220 W 48th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/longacre.asp" location "-73.98597262959564,40.76035022623389" 53 | HSET "theater:53" name "Lucille Lortel Theatre" address "121 Christopher St" city "New York" zip "10014" phone "212 924-8782" url "http://www.lortel.org/llt_theater/" location "-74.0057443463025,40.73335633167908" 54 | HSET "theater:54" name "Lunt-Fontanne Theatre" address "205 W 46th St" city "New York" zip "10036" phone "212 575-9200" url "http://www.luntfontannetheatre.com/" location "-73.98591873041272,40.759180958639" 55 | HSET "theater:55" name "Lyceum Theatre" address "149 W 45th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/lyceum.asp" location "-73.98452950858237,40.75777478125931" 56 | HSET "theater:56" name "Majestic Theatre" address "245 W 44th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/majestic.asp" location "-73.98801246892573,40.75841266782391" 57 | HSET "theater:57" name "Manhattan Ensemble Theatre" address "55 Mercer St" city "New York" zip "10013" phone "212 925-1900" url "http://www.met.com/" location "-74.00120856050414,40.721987687822704" 58 | HSET "theater:58" name "Manhattan Theatre Source" address "177 MacDougal St" city "New York" zip "10011" phone "212 260-4698" url "http://www.theatresource.org/index.htm" location "-73.9984046343471,40.73282809021791" 59 | HSET "theater:59" name "Marquis Theatre" address "1535 Broadway" city "New York" zip "10036" phone "212 382-0100" url "http://www.marquistheatre.com/" location "-73.98589651327984,40.7585116052983" 60 | HSET "theater:60" name "Mazer Theatre" address "197 E Broadway" city "New York" zip "10002" phone "212 780-2300" url "http://www.newyorkcitytheatre.com/theaters/mazertheater/location.php" location "-73.98835232341061,40.71398064048623" 61 | HSET "theater:61" name "McGinn Cazale Theater" address "2162 Broadway" city "New York" zip "10024" phone "212 787-3242" url "http://www.theatermania.com/broadway/theaters/mcginncazale-theater_186/" location "-73.98055520583067,40.78180729924115" 62 | HSET "theater:62" name "Metropolitan Playhouse" address "220 E 4th St" city "New York" zip "10009" phone "212 995-8410" url "http://www.metropolitanplayhouse.org/" location "-73.98366127503361,40.72349764533098" 63 | HSET "theater:63" name "Michael Schimmel Center for the Arts at Pace University" address "3 Spruce Street" city "New York" zip "10038" phone "800 874-7223" url "http://www.pace.edu/page.cfm?doc_id=26266" location "-74.00509727556909,40.711356500121944" 64 | HSET "theater:64" name "Miller Theatre at Columbia University" address "2960 Broadway" city "New York" zip "10027" phone "212 854-7799" url "http://www.millertheatre.com/" location "-73.96346045582304,40.807586707391195" 65 | HSET "theater:65" name "Minskoff Theatre" address "1515 Broadway" city "New York" zip "10036" phone "212 869-0550" url "http://www.minskofftheatre.com/" location "-73.98605419441054,40.7579233360297" 66 | HSET "theater:66" name "Music Box Theatre" address "239 W 45th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/music_box.asp" location "-73.98714246124487,40.7588874146744" 67 | HSET "theater:67" name "Nederlander Theater" address "208 W 41st St" city "New York" zip "10036" phone "212 921-8000" url "http://nederlandertheatre.com/" location "-73.98830897127951,40.755486799823906" 68 | HSET "theater:68" name "Neil Simon Theatre" address "250 W 52nd St" city "New York" zip "10019" phone "212 757-8646" url "http://www.neilsimontheatre.com/" location "-73.98452812145446,40.76301793173241" 69 | HSET "theater:69" name "New Amsterdam Theater" address "214 W 42nd St" city "New York" zip "10036" phone "212 282-2900" url "http://www.newamsterdamtheatre.net/index.htm" location "-73.9877630076589,40.75612046652384" 70 | HSET "theater:70" name "New Victory Theatre" address "209 West 42nd Street" city "New York" zip "10036" phone "646 223-3010" url "http://www.newvictory.org/" location "-73.98746415730906,40.75655716099295" 71 | HSET "theater:71" name "New World Stages" address "340 West 50th Street" city "New York" zip "10019" phone "646 871-1730" url "http://www.newworldstages.com/" location "-73.98786703441492,40.76237395668448" 72 | HSET "theater:72" name "Next Stage Theater" address "312 West 11th Street" city "New York" zip "10014" phone "888 VISIT-NY" url "http://www.newyorkcitytheatre.com/theaters/nextstagetheater/location.php" location "-74.00658864051292,40.735621467140966" 73 | HSET "theater:73" name "Nokia Theatre Times Square" address "1515 Broadway" city "New York" zip "10036" phone "212 930-1959" url "http://nokiatheatrenyc.com/" location "-73.98621794461265,40.757678172894884" 74 | HSET "theater:74" name "Ohio Theatre" address "66 Wooster Street" city "New York" zip "10012" phone "212 966-4844" url "http://www.sohothinktank.org/index.html" location "-74.00161141394004,40.72327983890941" 75 | HSET "theater:75" name "Ontological Hysteric Theater" address "260 W Broadway" city "New York" zip "10013" phone "212 941-8911" url "http://www.ontological.com/" location "-74.00567108963654,40.720546556543276" 76 | HSET "theater:76" name "Orpheum Theater" address "1538 3rd Ave" city "New York" zip "10028" phone "212 828-9738" url "http://www.orpheum-theater.com/" location "-73.95400942533547,40.77945537812588" 77 | HSET "theater:77" name "P.S.122 Performance Space" address "150 1st Ave" city "New York" zip "10003" phone "212 477-5288" url "http://www.ps122.org/" location "-73.98453728003108,40.728218122202" 78 | HSET "theater:78" name "Palace Theatre" address "1564 Broadway" city "New York" zip "10036" phone "212 730-8200" url "http://www.palacetheatreonbroadway.com/" location "-73.98447031550603,40.758889926706715" 79 | HSET "theater:79" name "Pearl Theatre" address "80 Saint Marks Pl" city "New York" zip "10003" phone "212 505-3401" url "http://www.pearltheatre.org/" location "-73.98579977557452,40.727559019006584" 80 | HSET "theater:80" name "People's Improv Theater" address "154 W 29th St" city "New York" zip "10001" phone "212 563-7488" url "http://www.thepit-nyc.com/" location "-73.99228995399157,40.74736717975171" 81 | HSET "theater:81" name "Playwrights Horizons Theater School" address "440 Lafayette St" city "New York" zip "10003" phone "212 529-8720" url "http://www.phtschool.org/" location "-73.9922209999171,40.729488634445005" 82 | HSET "theater:82" name "Queens Theatre in the Park" address "Flushing Meadows Corona Park" city "Queens" zip "11352" phone "718 760-0064" url "http://www.queenstheatre.org/web/" location "-73.84455209627207,40.74397928445518" 83 | HSET "theater:83" name "Rattlestick Theater" address "224 Waverly Pl" city "New York" zip "10014" phone "212 627-2556" url "http://www.rattlestick.org/" location "-74.00203650279532,40.73617406719814" 84 | HSET "theater:84" name "Richard Rodgers Theatre" address "226 W 46th Street" city "New York" zip "10036" phone "212 221-1211" url "http://www.richardrodgerstheatre.com/" location "-73.9867417603334,40.75903833018236" 85 | HSET "theater:85" name "Samuel J. Friedman Theatre formerly the Biltmore" address "261 W 47th St" city "New York" zip "10036" phone "888 VISIT-NY" url "http://www.theatermania.com/broadway/theaters/samuel-j-friedman-theatre_7420/" location "-73.98674510476694,40.76037502090257" 86 | HSET "theater:86" name "Second Stage Theater" address "307 W 43rd St" city "New York" zip "10036" phone "212 246-4422" url "http://www.2st.com/" location "-73.98973789241076,40.758239914427065" 87 | HSET "theater:87" name "Shubert Theatre" address "225 W 44th St" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/shubert.asp" location "-73.98723930549964,40.758137611181176" 88 | HSET "theater:88" name "Snapple Theater Center" address "1627 Broadway" city "New York" zip "10019" phone "212 921-7862" url "http://www.nytheatre.com/nytheatre/venue.php?t=snapple" location "-73.98442891934225,40.76141062139737" 89 | HSET "theater:89" name "SoHo Playhouse" address "15 Vandam St" city "New York" zip "10013" phone "212 691-1555" url "http://www.sohoplayhouse.com/" location "-74.00436193722095,40.72653567529159" 90 | HSET "theater:90" name "SoHo Repertory Theatre" address "86 Franklin St" city "New York" zip "10013" phone "212 941-8632" url "http://www.sohorep.org/" location "-74.00472935248527,40.718230021804324" 91 | HSET "theater:91" name "St. James Theatre" address "246 W 44th St" city "New York" zip "10036" phone "888 VISIT-NY" url "http://www.stjames-theater.com/?gclid=CPWjnLTO9p0CFcZM5QodoUaupw" location "-73.98816053699704,40.75799822657315" 92 | HSET "theater:92" name "Studio 54 Theatre" address "254 W 54th Street" city "New York" zip "10019" phone "212 719-1300" url "http://www.roundabouttheatre.org/ot_54.htm" location "-73.98377095865544,40.76435408364673" 93 | HSET "theater:93" name "Swedish Cottage Marionette Theater" address "West Side at 79th Street" city "New York" zip "10025" phone "212 988-9093" url "http://www.nycgovparks.org/sub_about/parks_divisions/historic_houses/hh_swedish_cottage.html" location "-73.97037578132182,40.77996630281343" 94 | HSET "theater:94" name "Tenement Theatre" address "97 Orchard Street" city "New York" zip "10002" phone "212 431-0233" url "http://www.tenement.org/prog_theater.html" location "-73.99027790676004,40.71854810051081" 95 | HSET "theater:95" name "The Beckett Theatre" address "410 W 42 St" city "New York" zip "10036" phone "212 714-2442" url "http://www.theatrerow.org/thebeckett.htm" location "-73.99374147365816,40.75868460666427" 96 | HSET "theater:96" name "The Cherry Pit" address "155 Bank St" city "New York" zip "10014" phone "212 633-6533" url "http://www.cherrylanetheatre.org/" location "-74.00902180098625,40.73645809825258" 97 | HSET "theater:97" name "The Little Shubert theatre" address "422 West 42nd Street" city "New York" zip "10036" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/little_shubert.asp" location "-73.99391778593146,40.75855311297125" 98 | HSET "theater:98" name "The Public Theater" address "425 Lafayette St" city "New York" zip "10003" phone "212 260-2400" url "http://www.publictheater.org/" location "-73.99177371983643,40.72892340209371" 99 | HSET "theater:99" name "The Secret Theatre" address "44-02 23rd St." city "Long Island City" zip "11101" phone "718 392-0722" url "http://www.secrettheatre.com/" location "-73.94482123207857,40.74890599748924" 100 | HSET "theater:100" name "The Upright Citizens Brigade Theatre" address "307 W 26th Street" city "New York" zip "10001" phone "212 366-9176" url "http://www.ucbtheatre.com/" location "-73.9973874129749,40.747653594271554" 101 | HSET "theater:101" name "The Walter Reade Theater at Lincoln Center" address "70 Lincoln Center Plaza" city "New York" zip "10023" phone "212 875-5600" url "http://www.filmlinc.com/wrt/wrt.html" location "-73.98424386556361,40.774102169778494" 102 | HSET "theater:102" name "The Zipper Theater" address "336 W 37th St" city "New York" zip "10018" phone "212 563-0480" url "http://www.yelp.com/biz/the-zipper-theater-new-york" location "-73.99403364237084,40.75467479348333" 103 | HSET "theater:103" name "Theater For The New City" address "155 1st Avenue" city "New York" zip "10003" phone "212 254-1109" url "http://www.theaterforthenewcity.net/" location "-73.98498034698733,40.72869278070282" 104 | HSET "theater:104" name "Theatre @ St. Clement's" address "423 West 46th Street" city "New York" zip "10036" phone "212 246-7277" url "http://www.stclementsnyc.org/" location "-73.99157114995049,40.76156669374937" 105 | HSET "theater:105" name "Theatre 80 St Marks" address "80 Saint Marks Pl" city "New York" zip "10003" phone "212 353-3321" url "http://www.theatermania.com/new-york/theaters/theater-80_3446/" location "-73.98575178908337,40.72762096989025" 106 | HSET "theater:106" name "Theatre Row" address "410 W 42nd Street" city "New York" zip "10036" phone "212 714-2442" url "http://www.theatrerow.org/" location "-73.99389624316413,40.75872836920871" 107 | HSET "theater:107" name "Under St. Marks" address "94 St. Marks Place" city "New York" zip "10009" phone "212 777-6088" url "http://www.httheater.org/rentersUNDER.html" location "-73.98496483499095,40.72728974704827" 108 | HSET "theater:108" name "Union Square Theater" address "100 E 17th St" city "New York" zip "10003" phone "212 505-0700" url "http://www.nytheatre.com/nytheatre/union.htm" location "-73.98874896687076,40.73624066126629" 109 | HSET "theater:109" name "Village Theater" address "158 Bleecker St" city "New York" zip "10012" phone "212 253-0623" url "http://www.villagetheatre.org/" location "-74.00022350602671,40.728455101424615" 110 | HSET "theater:110" name "Vineyard Theatre" address "108 E 15th St" city "New York" zip "10003" phone "212 353-3366" url "http://www.vineyardtheatre.org/" location "-73.98918917658457,40.73469974282689" 111 | HSET "theater:111" name "Walkerspace Theater" address "46 Walker Street" city "New York" zip "10013" phone "212 868-4444" url "http://www.theateronline.com/venuebook.xzc?PK=238&View=Hist" location "-74.00351370204922,40.71931150109748" 112 | HSET "theater:112" name "Wamu Theater formerly Theater At Madison Square Garden" address "4 Penn Plaza" city "New York" zip "10001" phone "212 465-6741" url "http://www.theateratmsg.com/" location "-73.99339518921583,40.75049725404502" 113 | HSET "theater:113" name "Westside Theater" address "407 W 43rd St" city "New York" zip "10036" phone "212 315-2244" url "http://www.westsidetheatre.com/" location "-73.99255324047746,40.759527416110735" 114 | HSET "theater:114" name "Wings Theatre" address "154 Christopher St" city "New York" zip "10014" phone "212 627-2960" url "http://www.wingstheatre.com/" location "-74.0088925664614,40.73239903762212" 115 | HSET "theater:115" name "Winter Garden Theatre" address "1634 Broadway" city "New York" zip "10019" phone "212 944-3700" url "http://www.shubertorganization.com/theatres/winter_garden.asp" location "-73.98348163057112,40.761524646280805" 116 | HSET "theater:116" name "York Theatre" address "619 Lexington Ave" city "New York" zip "10022" phone "212 935-5820" url "http://www.yorktheatre.org/" location "-73.96997885915609,40.758357229201536" 117 | HSET "theater:117" name "Delacorte Theater" address "Central Park - Mid-Park at 80th Street" city "New York" zip "0" phone "212 861-7277" url "http://www.centralpark.com/pages/attractions/delacorte-theatre.html" location "-73.9688248050205,40.78017563530363" 118 | -------------------------------------------------------------------------------- /redisearch-node-rest/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redisearch-node-rest", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.0.0", 9 | "license": "Apache-2.0", 10 | "dependencies": { 11 | "cors": "^2.8.5", 12 | "express": "^4.17.1", 13 | "redis": "^3.0.2", 14 | "redis-redisearch": "stockholmux/node_redis-redisearch" 15 | }, 16 | "engines": { 17 | "node": ">=8.9.4" 18 | } 19 | }, 20 | "node_modules/accepts": { 21 | "version": "1.3.7", 22 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 23 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 24 | "dependencies": { 25 | "mime-types": "~2.1.24", 26 | "negotiator": "0.6.2" 27 | }, 28 | "engines": { 29 | "node": ">= 0.6" 30 | } 31 | }, 32 | "node_modules/array-flatten": { 33 | "version": "1.1.1", 34 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 35 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 36 | }, 37 | "node_modules/body-parser": { 38 | "version": "1.19.0", 39 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 40 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 41 | "dependencies": { 42 | "bytes": "3.1.0", 43 | "content-type": "~1.0.4", 44 | "debug": "2.6.9", 45 | "depd": "~1.1.2", 46 | "http-errors": "1.7.2", 47 | "iconv-lite": "0.4.24", 48 | "on-finished": "~2.3.0", 49 | "qs": "6.7.0", 50 | "raw-body": "2.4.0", 51 | "type-is": "~1.6.17" 52 | }, 53 | "engines": { 54 | "node": ">= 0.8" 55 | } 56 | }, 57 | "node_modules/bytes": { 58 | "version": "3.1.0", 59 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 60 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 61 | "engines": { 62 | "node": ">= 0.8" 63 | } 64 | }, 65 | "node_modules/content-disposition": { 66 | "version": "0.5.3", 67 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 68 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 69 | "dependencies": { 70 | "safe-buffer": "5.1.2" 71 | }, 72 | "engines": { 73 | "node": ">= 0.6" 74 | } 75 | }, 76 | "node_modules/content-type": { 77 | "version": "1.0.4", 78 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 79 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 80 | "engines": { 81 | "node": ">= 0.6" 82 | } 83 | }, 84 | "node_modules/cookie": { 85 | "version": "0.4.0", 86 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 87 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", 88 | "engines": { 89 | "node": ">= 0.6" 90 | } 91 | }, 92 | "node_modules/cookie-signature": { 93 | "version": "1.0.6", 94 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 95 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 96 | }, 97 | "node_modules/cors": { 98 | "version": "2.8.5", 99 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 100 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 101 | "dependencies": { 102 | "object-assign": "^4", 103 | "vary": "^1" 104 | }, 105 | "engines": { 106 | "node": ">= 0.10" 107 | } 108 | }, 109 | "node_modules/debug": { 110 | "version": "2.6.9", 111 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 112 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 113 | "dependencies": { 114 | "ms": "2.0.0" 115 | } 116 | }, 117 | "node_modules/denque": { 118 | "version": "1.4.1", 119 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 120 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", 121 | "engines": { 122 | "node": ">=0.10" 123 | } 124 | }, 125 | "node_modules/depd": { 126 | "version": "1.1.2", 127 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 128 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 129 | "engines": { 130 | "node": ">= 0.6" 131 | } 132 | }, 133 | "node_modules/destroy": { 134 | "version": "1.0.4", 135 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 136 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 137 | }, 138 | "node_modules/ee-first": { 139 | "version": "1.1.1", 140 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 141 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 142 | }, 143 | "node_modules/encodeurl": { 144 | "version": "1.0.2", 145 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 146 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 147 | "engines": { 148 | "node": ">= 0.8" 149 | } 150 | }, 151 | "node_modules/escape-html": { 152 | "version": "1.0.3", 153 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 154 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 155 | }, 156 | "node_modules/etag": { 157 | "version": "1.8.1", 158 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 159 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 160 | "engines": { 161 | "node": ">= 0.6" 162 | } 163 | }, 164 | "node_modules/express": { 165 | "version": "4.17.1", 166 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 167 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 168 | "dependencies": { 169 | "accepts": "~1.3.7", 170 | "array-flatten": "1.1.1", 171 | "body-parser": "1.19.0", 172 | "content-disposition": "0.5.3", 173 | "content-type": "~1.0.4", 174 | "cookie": "0.4.0", 175 | "cookie-signature": "1.0.6", 176 | "debug": "2.6.9", 177 | "depd": "~1.1.2", 178 | "encodeurl": "~1.0.2", 179 | "escape-html": "~1.0.3", 180 | "etag": "~1.8.1", 181 | "finalhandler": "~1.1.2", 182 | "fresh": "0.5.2", 183 | "merge-descriptors": "1.0.1", 184 | "methods": "~1.1.2", 185 | "on-finished": "~2.3.0", 186 | "parseurl": "~1.3.3", 187 | "path-to-regexp": "0.1.7", 188 | "proxy-addr": "~2.0.5", 189 | "qs": "6.7.0", 190 | "range-parser": "~1.2.1", 191 | "safe-buffer": "5.1.2", 192 | "send": "0.17.1", 193 | "serve-static": "1.14.1", 194 | "setprototypeof": "1.1.1", 195 | "statuses": "~1.5.0", 196 | "type-is": "~1.6.18", 197 | "utils-merge": "1.0.1", 198 | "vary": "~1.1.2" 199 | }, 200 | "engines": { 201 | "node": ">= 0.10.0" 202 | } 203 | }, 204 | "node_modules/finalhandler": { 205 | "version": "1.1.2", 206 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 207 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 208 | "dependencies": { 209 | "debug": "2.6.9", 210 | "encodeurl": "~1.0.2", 211 | "escape-html": "~1.0.3", 212 | "on-finished": "~2.3.0", 213 | "parseurl": "~1.3.3", 214 | "statuses": "~1.5.0", 215 | "unpipe": "~1.0.0" 216 | }, 217 | "engines": { 218 | "node": ">= 0.8" 219 | } 220 | }, 221 | "node_modules/forwarded": { 222 | "version": "0.1.2", 223 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 224 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", 225 | "engines": { 226 | "node": ">= 0.6" 227 | } 228 | }, 229 | "node_modules/fresh": { 230 | "version": "0.5.2", 231 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 232 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 233 | "engines": { 234 | "node": ">= 0.6" 235 | } 236 | }, 237 | "node_modules/http-errors": { 238 | "version": "1.7.2", 239 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 240 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 241 | "dependencies": { 242 | "depd": "~1.1.2", 243 | "inherits": "2.0.3", 244 | "setprototypeof": "1.1.1", 245 | "statuses": ">= 1.5.0 < 2", 246 | "toidentifier": "1.0.0" 247 | }, 248 | "engines": { 249 | "node": ">= 0.6" 250 | } 251 | }, 252 | "node_modules/iconv-lite": { 253 | "version": "0.4.24", 254 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 255 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 256 | "dependencies": { 257 | "safer-buffer": ">= 2.1.2 < 3" 258 | }, 259 | "engines": { 260 | "node": ">=0.10.0" 261 | } 262 | }, 263 | "node_modules/inherits": { 264 | "version": "2.0.3", 265 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 266 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 267 | }, 268 | "node_modules/ipaddr.js": { 269 | "version": "1.9.1", 270 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 271 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 272 | "engines": { 273 | "node": ">= 0.10" 274 | } 275 | }, 276 | "node_modules/media-typer": { 277 | "version": "0.3.0", 278 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 279 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 280 | "engines": { 281 | "node": ">= 0.6" 282 | } 283 | }, 284 | "node_modules/merge-descriptors": { 285 | "version": "1.0.1", 286 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 287 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 288 | }, 289 | "node_modules/methods": { 290 | "version": "1.1.2", 291 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 292 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 293 | "engines": { 294 | "node": ">= 0.6" 295 | } 296 | }, 297 | "node_modules/mime": { 298 | "version": "1.6.0", 299 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 300 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 301 | "bin": { 302 | "mime": "cli.js" 303 | }, 304 | "engines": { 305 | "node": ">=4" 306 | } 307 | }, 308 | "node_modules/mime-db": { 309 | "version": "1.44.0", 310 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 311 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", 312 | "engines": { 313 | "node": ">= 0.6" 314 | } 315 | }, 316 | "node_modules/mime-types": { 317 | "version": "2.1.27", 318 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 319 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 320 | "dependencies": { 321 | "mime-db": "1.44.0" 322 | }, 323 | "engines": { 324 | "node": ">= 0.6" 325 | } 326 | }, 327 | "node_modules/ms": { 328 | "version": "2.0.0", 329 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 330 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 331 | }, 332 | "node_modules/negotiator": { 333 | "version": "0.6.2", 334 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 335 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 336 | "engines": { 337 | "node": ">= 0.6" 338 | } 339 | }, 340 | "node_modules/object-assign": { 341 | "version": "4.1.1", 342 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 343 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 344 | "engines": { 345 | "node": ">=0.10.0" 346 | } 347 | }, 348 | "node_modules/on-finished": { 349 | "version": "2.3.0", 350 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 351 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 352 | "dependencies": { 353 | "ee-first": "1.1.1" 354 | }, 355 | "engines": { 356 | "node": ">= 0.8" 357 | } 358 | }, 359 | "node_modules/parseurl": { 360 | "version": "1.3.3", 361 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 362 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 363 | "engines": { 364 | "node": ">= 0.8" 365 | } 366 | }, 367 | "node_modules/path-to-regexp": { 368 | "version": "0.1.7", 369 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 370 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 371 | }, 372 | "node_modules/proxy-addr": { 373 | "version": "2.0.6", 374 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 375 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 376 | "dependencies": { 377 | "forwarded": "~0.1.2", 378 | "ipaddr.js": "1.9.1" 379 | }, 380 | "engines": { 381 | "node": ">= 0.10" 382 | } 383 | }, 384 | "node_modules/qs": { 385 | "version": "6.7.0", 386 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 387 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 388 | "engines": { 389 | "node": ">=0.6" 390 | } 391 | }, 392 | "node_modules/range-parser": { 393 | "version": "1.2.1", 394 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 395 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 396 | "engines": { 397 | "node": ">= 0.6" 398 | } 399 | }, 400 | "node_modules/raw-body": { 401 | "version": "2.4.0", 402 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 403 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 404 | "dependencies": { 405 | "bytes": "3.1.0", 406 | "http-errors": "1.7.2", 407 | "iconv-lite": "0.4.24", 408 | "unpipe": "1.0.0" 409 | }, 410 | "engines": { 411 | "node": ">= 0.8" 412 | } 413 | }, 414 | "node_modules/redis": { 415 | "version": "3.0.2", 416 | "resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz", 417 | "integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==", 418 | "dependencies": { 419 | "denque": "^1.4.1", 420 | "redis-commands": "^1.5.0", 421 | "redis-errors": "^1.2.0", 422 | "redis-parser": "^3.0.0" 423 | }, 424 | "engines": { 425 | "node": ">=6" 426 | } 427 | }, 428 | "node_modules/redis-commands": { 429 | "version": "1.6.0", 430 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz", 431 | "integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ==" 432 | }, 433 | "node_modules/redis-errors": { 434 | "version": "1.2.0", 435 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 436 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", 437 | "engines": { 438 | "node": ">=4" 439 | } 440 | }, 441 | "node_modules/redis-parser": { 442 | "version": "3.0.0", 443 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 444 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 445 | "dependencies": { 446 | "redis-errors": "^1.0.0" 447 | }, 448 | "engines": { 449 | "node": ">=4" 450 | } 451 | }, 452 | "node_modules/redis-redisearch": { 453 | "resolved": "git+ssh://git@github.com/stockholmux/node_redis-redisearch.git#61dc2229872cb13e7131f014e5319c50def07b80" 454 | }, 455 | "node_modules/safe-buffer": { 456 | "version": "5.1.2", 457 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 458 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 459 | }, 460 | "node_modules/safer-buffer": { 461 | "version": "2.1.2", 462 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 463 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 464 | }, 465 | "node_modules/send": { 466 | "version": "0.17.1", 467 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 468 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 469 | "dependencies": { 470 | "debug": "2.6.9", 471 | "depd": "~1.1.2", 472 | "destroy": "~1.0.4", 473 | "encodeurl": "~1.0.2", 474 | "escape-html": "~1.0.3", 475 | "etag": "~1.8.1", 476 | "fresh": "0.5.2", 477 | "http-errors": "~1.7.2", 478 | "mime": "1.6.0", 479 | "ms": "2.1.1", 480 | "on-finished": "~2.3.0", 481 | "range-parser": "~1.2.1", 482 | "statuses": "~1.5.0" 483 | }, 484 | "engines": { 485 | "node": ">= 0.8.0" 486 | } 487 | }, 488 | "node_modules/send/node_modules/ms": { 489 | "version": "2.1.1", 490 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 491 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 492 | }, 493 | "node_modules/serve-static": { 494 | "version": "1.14.1", 495 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 496 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 497 | "dependencies": { 498 | "encodeurl": "~1.0.2", 499 | "escape-html": "~1.0.3", 500 | "parseurl": "~1.3.3", 501 | "send": "0.17.1" 502 | }, 503 | "engines": { 504 | "node": ">= 0.8.0" 505 | } 506 | }, 507 | "node_modules/setprototypeof": { 508 | "version": "1.1.1", 509 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 510 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 511 | }, 512 | "node_modules/statuses": { 513 | "version": "1.5.0", 514 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 515 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 516 | "engines": { 517 | "node": ">= 0.6" 518 | } 519 | }, 520 | "node_modules/toidentifier": { 521 | "version": "1.0.0", 522 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 523 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 524 | "engines": { 525 | "node": ">=0.6" 526 | } 527 | }, 528 | "node_modules/type-is": { 529 | "version": "1.6.18", 530 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 531 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 532 | "dependencies": { 533 | "media-typer": "0.3.0", 534 | "mime-types": "~2.1.24" 535 | }, 536 | "engines": { 537 | "node": ">= 0.6" 538 | } 539 | }, 540 | "node_modules/unpipe": { 541 | "version": "1.0.0", 542 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 543 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 544 | "engines": { 545 | "node": ">= 0.8" 546 | } 547 | }, 548 | "node_modules/utils-merge": { 549 | "version": "1.0.1", 550 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 551 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 552 | "engines": { 553 | "node": ">= 0.4.0" 554 | } 555 | }, 556 | "node_modules/vary": { 557 | "version": "1.1.2", 558 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 559 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 560 | "engines": { 561 | "node": ">= 0.8" 562 | } 563 | } 564 | }, 565 | "dependencies": { 566 | "accepts": { 567 | "version": "1.3.7", 568 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 569 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 570 | "requires": { 571 | "mime-types": "~2.1.24", 572 | "negotiator": "0.6.2" 573 | } 574 | }, 575 | "array-flatten": { 576 | "version": "1.1.1", 577 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 578 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 579 | }, 580 | "body-parser": { 581 | "version": "1.19.0", 582 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 583 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 584 | "requires": { 585 | "bytes": "3.1.0", 586 | "content-type": "~1.0.4", 587 | "debug": "2.6.9", 588 | "depd": "~1.1.2", 589 | "http-errors": "1.7.2", 590 | "iconv-lite": "0.4.24", 591 | "on-finished": "~2.3.0", 592 | "qs": "6.7.0", 593 | "raw-body": "2.4.0", 594 | "type-is": "~1.6.17" 595 | } 596 | }, 597 | "bytes": { 598 | "version": "3.1.0", 599 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 600 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 601 | }, 602 | "content-disposition": { 603 | "version": "0.5.3", 604 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 605 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 606 | "requires": { 607 | "safe-buffer": "5.1.2" 608 | } 609 | }, 610 | "content-type": { 611 | "version": "1.0.4", 612 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 613 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 614 | }, 615 | "cookie": { 616 | "version": "0.4.0", 617 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 618 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 619 | }, 620 | "cookie-signature": { 621 | "version": "1.0.6", 622 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 623 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 624 | }, 625 | "cors": { 626 | "version": "2.8.5", 627 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 628 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 629 | "requires": { 630 | "object-assign": "^4", 631 | "vary": "^1" 632 | } 633 | }, 634 | "debug": { 635 | "version": "2.6.9", 636 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 637 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 638 | "requires": { 639 | "ms": "2.0.0" 640 | } 641 | }, 642 | "denque": { 643 | "version": "1.4.1", 644 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 645 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 646 | }, 647 | "depd": { 648 | "version": "1.1.2", 649 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 650 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 651 | }, 652 | "destroy": { 653 | "version": "1.0.4", 654 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 655 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 656 | }, 657 | "ee-first": { 658 | "version": "1.1.1", 659 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 660 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 661 | }, 662 | "encodeurl": { 663 | "version": "1.0.2", 664 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 665 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 666 | }, 667 | "escape-html": { 668 | "version": "1.0.3", 669 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 670 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 671 | }, 672 | "etag": { 673 | "version": "1.8.1", 674 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 675 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 676 | }, 677 | "express": { 678 | "version": "4.17.1", 679 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 680 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 681 | "requires": { 682 | "accepts": "~1.3.7", 683 | "array-flatten": "1.1.1", 684 | "body-parser": "1.19.0", 685 | "content-disposition": "0.5.3", 686 | "content-type": "~1.0.4", 687 | "cookie": "0.4.0", 688 | "cookie-signature": "1.0.6", 689 | "debug": "2.6.9", 690 | "depd": "~1.1.2", 691 | "encodeurl": "~1.0.2", 692 | "escape-html": "~1.0.3", 693 | "etag": "~1.8.1", 694 | "finalhandler": "~1.1.2", 695 | "fresh": "0.5.2", 696 | "merge-descriptors": "1.0.1", 697 | "methods": "~1.1.2", 698 | "on-finished": "~2.3.0", 699 | "parseurl": "~1.3.3", 700 | "path-to-regexp": "0.1.7", 701 | "proxy-addr": "~2.0.5", 702 | "qs": "6.7.0", 703 | "range-parser": "~1.2.1", 704 | "safe-buffer": "5.1.2", 705 | "send": "0.17.1", 706 | "serve-static": "1.14.1", 707 | "setprototypeof": "1.1.1", 708 | "statuses": "~1.5.0", 709 | "type-is": "~1.6.18", 710 | "utils-merge": "1.0.1", 711 | "vary": "~1.1.2" 712 | } 713 | }, 714 | "finalhandler": { 715 | "version": "1.1.2", 716 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 717 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 718 | "requires": { 719 | "debug": "2.6.9", 720 | "encodeurl": "~1.0.2", 721 | "escape-html": "~1.0.3", 722 | "on-finished": "~2.3.0", 723 | "parseurl": "~1.3.3", 724 | "statuses": "~1.5.0", 725 | "unpipe": "~1.0.0" 726 | } 727 | }, 728 | "forwarded": { 729 | "version": "0.1.2", 730 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 731 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 732 | }, 733 | "fresh": { 734 | "version": "0.5.2", 735 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 736 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 737 | }, 738 | "http-errors": { 739 | "version": "1.7.2", 740 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 741 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 742 | "requires": { 743 | "depd": "~1.1.2", 744 | "inherits": "2.0.3", 745 | "setprototypeof": "1.1.1", 746 | "statuses": ">= 1.5.0 < 2", 747 | "toidentifier": "1.0.0" 748 | } 749 | }, 750 | "iconv-lite": { 751 | "version": "0.4.24", 752 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 753 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 754 | "requires": { 755 | "safer-buffer": ">= 2.1.2 < 3" 756 | } 757 | }, 758 | "inherits": { 759 | "version": "2.0.3", 760 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 761 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 762 | }, 763 | "ipaddr.js": { 764 | "version": "1.9.1", 765 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 766 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 767 | }, 768 | "media-typer": { 769 | "version": "0.3.0", 770 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 771 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 772 | }, 773 | "merge-descriptors": { 774 | "version": "1.0.1", 775 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 776 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 777 | }, 778 | "methods": { 779 | "version": "1.1.2", 780 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 781 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 782 | }, 783 | "mime": { 784 | "version": "1.6.0", 785 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 786 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 787 | }, 788 | "mime-db": { 789 | "version": "1.44.0", 790 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 791 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 792 | }, 793 | "mime-types": { 794 | "version": "2.1.27", 795 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 796 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 797 | "requires": { 798 | "mime-db": "1.44.0" 799 | } 800 | }, 801 | "ms": { 802 | "version": "2.0.0", 803 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 804 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 805 | }, 806 | "negotiator": { 807 | "version": "0.6.2", 808 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 809 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 810 | }, 811 | "object-assign": { 812 | "version": "4.1.1", 813 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 814 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 815 | }, 816 | "on-finished": { 817 | "version": "2.3.0", 818 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 819 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 820 | "requires": { 821 | "ee-first": "1.1.1" 822 | } 823 | }, 824 | "parseurl": { 825 | "version": "1.3.3", 826 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 827 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 828 | }, 829 | "path-to-regexp": { 830 | "version": "0.1.7", 831 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 832 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 833 | }, 834 | "proxy-addr": { 835 | "version": "2.0.6", 836 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 837 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 838 | "requires": { 839 | "forwarded": "~0.1.2", 840 | "ipaddr.js": "1.9.1" 841 | } 842 | }, 843 | "qs": { 844 | "version": "6.7.0", 845 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 846 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 847 | }, 848 | "range-parser": { 849 | "version": "1.2.1", 850 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 851 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 852 | }, 853 | "raw-body": { 854 | "version": "2.4.0", 855 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 856 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 857 | "requires": { 858 | "bytes": "3.1.0", 859 | "http-errors": "1.7.2", 860 | "iconv-lite": "0.4.24", 861 | "unpipe": "1.0.0" 862 | } 863 | }, 864 | "redis": { 865 | "version": "3.0.2", 866 | "resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz", 867 | "integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==", 868 | "requires": { 869 | "denque": "^1.4.1", 870 | "redis-commands": "^1.5.0", 871 | "redis-errors": "^1.2.0", 872 | "redis-parser": "^3.0.0" 873 | } 874 | }, 875 | "redis-commands": { 876 | "version": "1.6.0", 877 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz", 878 | "integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ==" 879 | }, 880 | "redis-errors": { 881 | "version": "1.2.0", 882 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 883 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" 884 | }, 885 | "redis-parser": { 886 | "version": "3.0.0", 887 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 888 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 889 | "requires": { 890 | "redis-errors": "^1.0.0" 891 | } 892 | }, 893 | "redis-redisearch": { 894 | "version": "git+ssh://git@github.com/stockholmux/node_redis-redisearch.git#61dc2229872cb13e7131f014e5319c50def07b80", 895 | "from": "redis-redisearch@stockholmux/node_redis-redisearch" 896 | }, 897 | "safe-buffer": { 898 | "version": "5.1.2", 899 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 900 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 901 | }, 902 | "safer-buffer": { 903 | "version": "2.1.2", 904 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 905 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 906 | }, 907 | "send": { 908 | "version": "0.17.1", 909 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 910 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 911 | "requires": { 912 | "debug": "2.6.9", 913 | "depd": "~1.1.2", 914 | "destroy": "~1.0.4", 915 | "encodeurl": "~1.0.2", 916 | "escape-html": "~1.0.3", 917 | "etag": "~1.8.1", 918 | "fresh": "0.5.2", 919 | "http-errors": "~1.7.2", 920 | "mime": "1.6.0", 921 | "ms": "2.1.1", 922 | "on-finished": "~2.3.0", 923 | "range-parser": "~1.2.1", 924 | "statuses": "~1.5.0" 925 | }, 926 | "dependencies": { 927 | "ms": { 928 | "version": "2.1.1", 929 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 930 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 931 | } 932 | } 933 | }, 934 | "serve-static": { 935 | "version": "1.14.1", 936 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 937 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 938 | "requires": { 939 | "encodeurl": "~1.0.2", 940 | "escape-html": "~1.0.3", 941 | "parseurl": "~1.3.3", 942 | "send": "0.17.1" 943 | } 944 | }, 945 | "setprototypeof": { 946 | "version": "1.1.1", 947 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 948 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 949 | }, 950 | "statuses": { 951 | "version": "1.5.0", 952 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 953 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 954 | }, 955 | "toidentifier": { 956 | "version": "1.0.0", 957 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 958 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 959 | }, 960 | "type-is": { 961 | "version": "1.6.18", 962 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 963 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 964 | "requires": { 965 | "media-typer": "0.3.0", 966 | "mime-types": "~2.1.24" 967 | } 968 | }, 969 | "unpipe": { 970 | "version": "1.0.0", 971 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 972 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 973 | }, 974 | "utils-merge": { 975 | "version": "1.0.1", 976 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 977 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 978 | }, 979 | "vary": { 980 | "version": "1.1.2", 981 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 982 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 983 | } 984 | } 985 | } 986 | --------------------------------------------------------------------------------