diff --git a/CHANGELOG.md b/CHANGELOG.md index 25a79dfb5..8decb1329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,115 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [v0.2.1](https://github.com/Human-Connection/Human-Connection/compare/v0.2.0...v0.2.1) + +> 10 January 2020 + +- 🍰 Search For Users [`#2262`](https://github.com/Human-Connection/Human-Connection/pull/2262) +- Use node LTS in production [`#2713`](https://github.com/Human-Connection/Human-Connection/pull/2713) +- build(deps): bump apollo-server from 2.9.15 to 2.9.16 in /backend [`#2718`](https://github.com/Human-Connection/Human-Connection/pull/2718) +- build(deps): bump neo4j-graphql-js from 2.11.4 to 2.11.5 in /backend [`#2715`](https://github.com/Human-Connection/Human-Connection/pull/2715) +- build(deps-dev): bump apollo-server-testing from 2.9.15 to 2.9.16 in /backend [`#2720`](https://github.com/Human-Connection/Human-Connection/pull/2720) +- build(deps): bump @hapi/joi from 17.0.0 to 17.0.2 in /backend [`#2719`](https://github.com/Human-Connection/Human-Connection/pull/2719) +- build(deps): bump apollo-server-express from 2.9.15 to 2.9.16 in /backend [`#2717`](https://github.com/Human-Connection/Human-Connection/pull/2717) +- build(deps): bump metascraper-url from 5.8.13 to 5.9.5 in /backend [`#2716`](https://github.com/Human-Connection/Human-Connection/pull/2716) +- build(deps): bump date-fns from 2.8.1 to 2.9.0 in /backend [`#2706`](https://github.com/Human-Connection/Human-Connection/pull/2706) +- build(deps): bump metascraper-lang from 5.8.13 to 5.9.5 in /backend [`#2703`](https://github.com/Human-Connection/Human-Connection/pull/2703) +- build(deps): bump date-fns from 2.8.1 to 2.9.0 in /webapp [`#2711`](https://github.com/Human-Connection/Human-Connection/pull/2711) +- build(deps): bump metascraper-logo from 5.8.13 to 5.9.5 in /backend [`#2697`](https://github.com/Human-Connection/Human-Connection/pull/2697) +- build(deps): bump metascraper-title from 5.8.13 to 5.9.5 in /backend [`#2694`](https://github.com/Human-Connection/Human-Connection/pull/2694) +- build(deps): bump metascraper-description from 5.8.15 to 5.9.5 in /backend [`#2690`](https://github.com/Human-Connection/Human-Connection/pull/2690) +- build(deps): bump node from 13.5.0-alpine to 13.6.0-alpine in /webapp [`#2708`](https://github.com/Human-Connection/Human-Connection/pull/2708) +- build(deps): bump @sentry/node from 5.10.2 to 5.11.0 in /backend [`#2709`](https://github.com/Human-Connection/Human-Connection/pull/2709) +- build(deps): bump metascraper-audio from 5.8.13 to 5.9.5 in /backend [`#2707`](https://github.com/Human-Connection/Human-Connection/pull/2707) +- build(deps): bump metascraper-image from 5.9.4 to 5.9.5 in /backend [`#2705`](https://github.com/Human-Connection/Human-Connection/pull/2705) +- build(deps): bump metascraper-youtube from 5.8.13 to 5.9.5 in /backend [`#2704`](https://github.com/Human-Connection/Human-Connection/pull/2704) +- build(deps-dev): bump date-fns from 2.8.1 to 2.9.0 [`#2702`](https://github.com/Human-Connection/Human-Connection/pull/2702) +- build(deps): bump metascraper-soundcloud from 5.9.0 to 5.9.5 in /backend [`#2693`](https://github.com/Human-Connection/Human-Connection/pull/2693) +- build(deps): bump metascraper-date from 5.8.13 to 5.9.5 in /backend [`#2698`](https://github.com/Human-Connection/Human-Connection/pull/2698) +- build(deps): bump neo4j-graphql-js from 2.11.3 to 2.11.4 in /backend [`#2696`](https://github.com/Human-Connection/Human-Connection/pull/2696) +- build(deps): bump metascraper-video from 5.8.13 to 5.9.5 in /backend [`#2695`](https://github.com/Human-Connection/Human-Connection/pull/2695) +- build(deps): bump metascraper-publisher from 5.8.13 to 5.9.5 in /backend [`#2692`](https://github.com/Human-Connection/Human-Connection/pull/2692) +- build(deps): bump metascraper-author from 5.8.13 to 5.9.5 in /backend [`#2691`](https://github.com/Human-Connection/Human-Connection/pull/2691) +- build(deps): bump metascraper from 5.9.4 to 5.9.5 in /backend [`#2689`](https://github.com/Human-Connection/Human-Connection/pull/2689) +- Changes Text For SignUp [`#2678`](https://github.com/Human-Connection/Human-Connection/pull/2678) +- Update de.json [`#2655`](https://github.com/Human-Connection/Human-Connection/pull/2655) +- build(deps): bump neode from 0.3.6 to 0.3.7 in /backend [`#2682`](https://github.com/Human-Connection/Human-Connection/pull/2682) +- Update neo4j-driver [`#2546`](https://github.com/Human-Connection/Human-Connection/pull/2546) +- build(deps): bump merge-graphql-schemas from 1.7.5 to 1.7.6 in /backend [`#2681`](https://github.com/Human-Connection/Human-Connection/pull/2681) +- build(deps): bump neo4j-graphql-js from 2.11.2 to 2.11.3 in /backend [`#2680`](https://github.com/Human-Connection/Human-Connection/pull/2680) +- build(deps-dev): bump neode from 0.3.6 to 0.3.7 [`#2679`](https://github.com/Human-Connection/Human-Connection/pull/2679) +- Parse xss before extracting mentions/hashtags [`#2674`](https://github.com/Human-Connection/Human-Connection/pull/2674) +- build(deps): bump metascraper-logo from 5.8.12 to 5.8.13 in /backend [`#2672`](https://github.com/Human-Connection/Human-Connection/pull/2672) +- build(deps): bump metascraper from 5.9.0 to 5.9.4 in /backend [`#2668`](https://github.com/Human-Connection/Human-Connection/pull/2668) +- build(deps-dev): bump eslint-plugin-jest from 23.2.0 to 23.3.0 in /webapp [`#2671`](https://github.com/Human-Connection/Human-Connection/pull/2671) +- build(deps-dev): bump css-loader from 3.4.0 to 3.4.1 in /webapp [`#2669`](https://github.com/Human-Connection/Human-Connection/pull/2669) +- build(deps): bump metascraper-image from 5.8.13 to 5.9.4 in /backend [`#2670`](https://github.com/Human-Connection/Human-Connection/pull/2670) +- build(deps-dev): bump apollo-server-testing from 2.9.14 to 2.9.15 in /backend [`#2667`](https://github.com/Human-Connection/Human-Connection/pull/2667) +- build(deps): bump neo4j-graphql-js from 2.11.0 to 2.11.2 in /backend [`#2666`](https://github.com/Human-Connection/Human-Connection/pull/2666) +- build(deps): bump metascraper-title from 5.8.12 to 5.8.13 in /backend [`#2665`](https://github.com/Human-Connection/Human-Connection/pull/2665) +- build(deps): bump @hapi/joi from 16.1.8 to 17.0.0 in /backend [`#2664`](https://github.com/Human-Connection/Human-Connection/pull/2664) +- build(deps-dev): bump cypress-file-upload from 3.5.1 to 3.5.3 [`#2663`](https://github.com/Human-Connection/Human-Connection/pull/2663) +- build(deps-dev): bump eslint-plugin-jest from 23.1.1 to 23.3.0 in /backend [`#2662`](https://github.com/Human-Connection/Human-Connection/pull/2662) +- build(deps-dev): bump eslint-config-prettier from 6.7.0 to 6.9.0 in /webapp [`#2632`](https://github.com/Human-Connection/Human-Connection/pull/2632) +- build(deps-dev): bump slug from 2.0.0 to 2.1.0 [`#2647`](https://github.com/Human-Connection/Human-Connection/pull/2647) +- build(deps): bump merge-graphql-schemas from 1.7.3 to 1.7.5 in /backend [`#2648`](https://github.com/Human-Connection/Human-Connection/pull/2648) +- build(deps): bump metascraper-url from 5.8.12 to 5.8.13 in /backend [`#2637`](https://github.com/Human-Connection/Human-Connection/pull/2637) +- build(deps): bump metascraper-publisher from 5.8.12 to 5.8.13 in /backend [`#2636`](https://github.com/Human-Connection/Human-Connection/pull/2636) +- build(deps-dev): bump eslint-plugin-jest from 23.1.1 to 23.2.0 in /webapp [`#2642`](https://github.com/Human-Connection/Human-Connection/pull/2642) +- build(deps): bump graphql-shield from 7.0.5 to 7.0.7 in /backend [`#2649`](https://github.com/Human-Connection/Human-Connection/pull/2649) +- build(deps-dev): bump eslint-plugin-node from 10.0.0 to 11.0.0 in /backend [`#2650`](https://github.com/Human-Connection/Human-Connection/pull/2650) +- build(deps-dev): bump eslint-config-prettier from 6.7.0 to 6.9.0 in /backend [`#2651`](https://github.com/Human-Connection/Human-Connection/pull/2651) +- build(deps): bump slug from 2.0.0 to 2.1.0 in /backend [`#2652`](https://github.com/Human-Connection/Human-Connection/pull/2652) +- build(deps): bump metascraper from 5.8.12 to 5.9.0 in /backend [`#2654`](https://github.com/Human-Connection/Human-Connection/pull/2654) +- build(deps): bump metascraper-description from 5.8.12 to 5.8.15 in /backend [`#2653`](https://github.com/Human-Connection/Human-Connection/pull/2653) +- build(deps): bump metascraper-author from 5.8.12 to 5.8.13 in /backend [`#2616`](https://github.com/Human-Connection/Human-Connection/pull/2616) +- build(deps): bump metascraper-lang from 5.8.12 to 5.8.13 in /backend [`#2618`](https://github.com/Human-Connection/Human-Connection/pull/2618) +- build(deps): bump apollo-server from 2.9.13 to 2.9.15 in /backend [`#2634`](https://github.com/Human-Connection/Human-Connection/pull/2634) +- build(deps): bump metascraper-soundcloud from 5.8.15 to 5.9.0 in /backend [`#2638`](https://github.com/Human-Connection/Human-Connection/pull/2638) +- build(deps): bump metascraper-video from 5.8.12 to 5.8.13 in /backend [`#2639`](https://github.com/Human-Connection/Human-Connection/pull/2639) +- build(deps): bump mustache from 3.2.0 to 3.2.1 in /backend [`#2640`](https://github.com/Human-Connection/Human-Connection/pull/2640) +- build(deps): bump slug from 1.1.0 to 2.0.0 in /backend [`#2641`](https://github.com/Human-Connection/Human-Connection/pull/2641) +- build(deps-dev): bump @vue/cli-shared-utils from 4.1.1 to 4.1.2 in /webapp [`#2643`](https://github.com/Human-Connection/Human-Connection/pull/2643) +- build(deps-dev): bump eslint-plugin-vue from 6.1.1 to 6.1.2 in /webapp [`#2644`](https://github.com/Human-Connection/Human-Connection/pull/2644) +- build(deps-dev): bump eslint-plugin-node from 10.0.0 to 11.0.0 in /webapp [`#2645`](https://github.com/Human-Connection/Human-Connection/pull/2645) +- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 in /webapp [`#2579`](https://github.com/Human-Connection/Human-Connection/pull/2579) +- build(deps): bump metascraper-soundcloud from 5.8.12 to 5.8.15 in /backend [`#2630`](https://github.com/Human-Connection/Human-Connection/pull/2630) +- build(deps-dev): bump eslint from 6.7.2 to 6.8.0 in /webapp [`#2617`](https://github.com/Human-Connection/Human-Connection/pull/2617) +- build(deps): bump metascraper-date from 5.8.12 to 5.8.13 in /backend [`#2615`](https://github.com/Human-Connection/Human-Connection/pull/2615) +- build(deps): bump metascraper-image from 5.8.12 to 5.8.13 in /backend [`#2614`](https://github.com/Human-Connection/Human-Connection/pull/2614) +- build(deps): bump metascraper-youtube from 5.8.12 to 5.8.13 in /backend [`#2612`](https://github.com/Human-Connection/Human-Connection/pull/2612) +- build(deps): bump metascraper-audio from 5.8.12 to 5.8.13 in /backend [`#2610`](https://github.com/Human-Connection/Human-Connection/pull/2610) +- 🍰 Added Language Tag For Posts [`#2627`](https://github.com/Human-Connection/Human-Connection/pull/2627) +- build(deps-dev): bump cypress-plugin-retries from 1.5.0 to 1.5.2 [`#2609`](https://github.com/Human-Connection/Human-Connection/pull/2609) +- build(deps-dev): bump eslint from 6.7.2 to 6.8.0 in /backend [`#2613`](https://github.com/Human-Connection/Human-Connection/pull/2613) +- remove accidently created ru.json in wrong place [`#2606`](https://github.com/Human-Connection/Human-Connection/pull/2606) +- build(deps): bump neo4j from 3.5.13-enterprise to 3.5.14-enterprise in /neo4j [`#2620`](https://github.com/Human-Connection/Human-Connection/pull/2620) +- Fixes 2603 [`#2619`](https://github.com/Human-Connection/Human-Connection/pull/2619) +- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 in /webapp [`#2581`](https://github.com/Human-Connection/Human-Connection/pull/2581) +- build(deps-dev): bump slug from 1.1.0 to 2.0.0 [`#2621`](https://github.com/Human-Connection/Human-Connection/pull/2621) +- build(deps): [security] bump handlebars from 4.1.2 to 4.5.3 in /webapp [`#2624`](https://github.com/Human-Connection/Human-Connection/pull/2624) +- build(deps): [security] bump handlebars from 4.1.2 to 4.5.3 in /backend [`#2625`](https://github.com/Human-Connection/Human-Connection/pull/2625) +- build(deps-dev): bump cypress from 3.8.0 to 3.8.1 [`#2626`](https://github.com/Human-Connection/Human-Connection/pull/2626) +- build(deps-dev): bump eslint-plugin-vue from 6.0.1 to 6.1.1 in /webapp [`#2633`](https://github.com/Human-Connection/Human-Connection/pull/2633) +- build(deps-dev): bump @babel/register from 7.7.4 to 7.7.7 [`#2571`](https://github.com/Human-Connection/Human-Connection/pull/2571) +- build(deps): bump neo4j-graphql-js from 2.10.2 to 2.11.0 in /backend [`#2600`](https://github.com/Human-Connection/Human-Connection/pull/2600) +- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 in /backend [`#2590`](https://github.com/Human-Connection/Human-Connection/pull/2590) +- build(deps): bump metascraper-url from 5.8.7 to 5.8.12 in /backend [`#2599`](https://github.com/Human-Connection/Human-Connection/pull/2599) +- build(deps): bump metascraper-lang from 5.8.10 to 5.8.12 in /backend [`#2598`](https://github.com/Human-Connection/Human-Connection/pull/2598) +- build(deps): bump metascraper-audio from 5.8.10 to 5.8.12 in /backend [`#2596`](https://github.com/Human-Connection/Human-Connection/pull/2596) +- build(deps): bump node from 13.4.0-alpine to 13.5.0-alpine in /webapp [`#2595`](https://github.com/Human-Connection/Human-Connection/pull/2595) +- build(deps-dev): bump storybook-design-token from 0.4.1 to 0.5.0 in /webapp [`#2594`](https://github.com/Human-Connection/Human-Connection/pull/2594) +- build(deps): bump graphql-shield from 7.0.4 to 7.0.5 in /backend [`#2593`](https://github.com/Human-Connection/Human-Connection/pull/2593) +- build(deps): bump metascraper-publisher from 5.8.7 to 5.8.12 in /backend [`#2592`](https://github.com/Human-Connection/Human-Connection/pull/2592) +- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 in /backend [`#2568`](https://github.com/Human-Connection/Human-Connection/pull/2568) +- Fix imageAspectRatio set to null UpdatePost [`#2588`](https://github.com/Human-Connection/Human-Connection/pull/2588) +- Update to version 0.2.0 [`#2584`](https://github.com/Human-Connection/Human-Connection/pull/2584) +- fixes #2659 [`#2659`](https://github.com/Human-Connection/Human-Connection/issues/2659) +- build(deps-dev): bump storybook-design-token in /webapp [`88d39c4`](https://github.com/Human-Connection/Human-Connection/commit/88d39c4a427cb86527b06201f3f5e96d53ac09a0) +- Specs for Searches [`bc3aa51`](https://github.com/Human-Connection/Human-Connection/commit/bc3aa519d0e7a6e0242ecd37d611fd1a3df385d0) +- build(deps): bump apollo-server-express in /backend [`84df7b5`](https://github.com/Human-Connection/Human-Connection/commit/84df7b5a0a4845ab44d19946d877aef79691d38e) + #### [v0.2.0](https://github.com/Human-Connection/Human-Connection/compare/v0.1.13...v0.2.0) > 19 December 2019 @@ -62,8 +171,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Fix: User.name is not non-nullable [`#2510`](https://github.com/Human-Connection/Human-Connection/pull/2510) - Update to version 0.1.13 [`#2506`](https://github.com/Human-Connection/Human-Connection/pull/2506) - Lokalise: update of webapp/locales/ru.json [`b70ff73`](https://github.com/Human-Connection/Human-Connection/commit/b70ff73bba98d28494c55ed12161288b1efa1516) -- build(deps): bump apollo-server-express in /backend [`69d3107`](https://github.com/Human-Connection/Human-Connection/commit/69d3107cbcce8225dd14f7231936a597fba6105d) -- refactor: content menu [`71b2eac`](https://github.com/Human-Connection/Human-Connection/commit/71b2eac175e9d6e1a2bbba123490f281b7cb13f3) +- Separate concerns in components [`d74d207`](https://github.com/Human-Connection/Human-Connection/commit/d74d2072ba41af6170d79d7dc2e24f9ebab15771) +- Fix failing component tests [`b79c292`](https://github.com/Human-Connection/Human-Connection/commit/b79c292ef4f76b307131566c24d849c2e35c8089) #### [v0.1.13](https://github.com/Human-Connection/Human-Connection/compare/v0.1.12...v0.1.13) @@ -91,8 +200,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - build(deps): bump cookie-universal-nuxt from 2.0.19 to 2.1.0 in /webapp [`#2490`](https://github.com/Human-Connection/Human-Connection/pull/2490) - Update to version 0.1.12 [`#2483`](https://github.com/Human-Connection/Human-Connection/pull/2483) - Lokalise: update of locale/ru.json [`60b3035`](https://github.com/Human-Connection/Human-Connection/commit/60b3035a3d475cb481130c6fe94f2901711a4053) +- Fix search by adding result id [`ebc5cf3`](https://github.com/Human-Connection/Human-Connection/commit/ebc5cf392d92acf3a9e22c8967d02ea2cf6fd7fb) - Write test/refactor tests/resolvers/middleware [`d375ebe`](https://github.com/Human-Connection/Human-Connection/commit/d375ebe7d90e3251b17f59ffba8fb1470923ebe8) -- refactor css, fix design issues [`5586335`](https://github.com/Human-Connection/Human-Connection/commit/5586335ed2b3474498e87b929f54d52562e44636) #### [v0.1.12](https://github.com/Human-Connection/Human-Connection/compare/v0.1.10...v0.1.12) @@ -258,8 +367,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Fix #2294 [`#2294`](https://github.com/Human-Connection/Human-Connection/issues/2294) - Merge pull request #2078 from Human-Connection/fix-2042-back-link [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042) - Move components to components/features [`2357028`](https://github.com/Human-Connection/Human-Connection/commit/235702867d97b44dac37f8059f9194e23ba7f47d) +- Basic Search Is Working For Users And Posts [`72e4d0a`](https://github.com/Human-Connection/Human-Connection/commit/72e4d0abbcb9abab07f3fd12876453eb1de5da4c) - Add missing unit tests/refactor code [`b364065`](https://github.com/Human-Connection/Human-Connection/commit/b3640659bb608cc34edc6f2aca350f07dd2b9ce6) -- Add stories/specs for ReportList [`a59e72d`](https://github.com/Human-Connection/Human-Connection/commit/a59e72d8a8f491cb251e3e5acddea3b32144209b) #### [v0.1.10](https://github.com/Human-Connection/Human-Connection/compare/v0.1.9...v0.1.10) diff --git a/VERSION b/VERSION index 0ea3a944b..0c62199f1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 +0.2.1 diff --git a/backend/Dockerfile b/backend/Dockerfile index 3dae08143..957bc6ab5 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.12.0-alpine as base +FROM node:lts-alpine as base LABEL Description="Backend of the Social Network Human-Connection.org" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)" EXPOSE 4000 diff --git a/backend/package.json b/backend/package.json index 071b84ecc..1d0b81b4d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -32,14 +32,14 @@ ] }, "dependencies": { - "@hapi/joi": "^17.0.0", + "@hapi/joi": "^17.0.2", "@sentry/node": "^5.11.0", "apollo-cache-inmemory": "~1.6.5", "apollo-client": "~2.6.8", "apollo-link-context": "~1.0.19", "apollo-link-http": "~1.5.16", - "apollo-server": "~2.9.15", - "apollo-server-express": "^2.9.14", + "apollo-server": "~2.9.16", + "apollo-server-express": "^2.9.16", "babel-plugin-transform-runtime": "^6.23.0", "bcryptjs": "~2.4.3", "cheerio": "~1.0.0-rc.3", @@ -75,13 +75,13 @@ "metascraper-publisher": "^5.9.5", "metascraper-soundcloud": "^5.9.5", "metascraper-title": "^5.9.5", - "metascraper-url": "^5.8.13", + "metascraper-url": "^5.9.5", "metascraper-video": "^5.9.5", "metascraper-youtube": "^5.9.5", "minimatch": "^3.0.4", "mustache": "^3.2.1", "neo4j-driver": "^4.0.1", - "neo4j-graphql-js": "^2.11.4", + "neo4j-graphql-js": "^2.11.5", "neode": "^0.3.7", "node-fetch": "~2.6.0", "nodemailer": "^6.4.2", @@ -103,7 +103,7 @@ "@babel/plugin-proposal-throw-expressions": "^7.7.4", "@babel/preset-env": "~7.7.7", "@babel/register": "~7.7.0", - "apollo-server-testing": "~2.9.15", + "apollo-server-testing": "~2.9.16", "babel-core": "~7.0.0-0", "babel-eslint": "~10.0.3", "babel-jest": "~24.9.0", diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 3b42ae7fe..a4c41871f 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -85,6 +85,8 @@ export default shield( Query: { '*': deny, findPosts: allow, + findUsers: allow, + findResources: allow, embed: allow, Category: allow, Tag: allow, diff --git a/backend/src/schema/resolvers/helpers/filterForBlockedUsers.js b/backend/src/schema/resolvers/helpers/filterForBlockedUsers.js new file mode 100644 index 000000000..b646038f0 --- /dev/null +++ b/backend/src/schema/resolvers/helpers/filterForBlockedUsers.js @@ -0,0 +1,25 @@ +import { getBlockedUsers, getBlockedByUsers } from '../users.js' +import { mergeWith, isArray } from 'lodash' + +export const filterForBlockedUsers = async (params, context) => { + if (!context.user) return params + const [blockedUsers, blockedByUsers] = await Promise.all([ + getBlockedUsers(context), + getBlockedByUsers(context), + ]) + const blockedUsersIds = [...blockedByUsers.map(b => b.id), ...blockedUsers.map(b => b.id)] + if (!blockedUsersIds.length) return params + + params.filter = mergeWith( + params.filter, + { + author_not: { id_in: blockedUsersIds }, + }, + (objValue, srcValue) => { + if (isArray(objValue)) { + return objValue.concat(srcValue) + } + }, + ) + return params +} diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index 47223faea..4a857a63c 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -1,33 +1,10 @@ import uuid from 'uuid/v4' import { neo4jgraphql } from 'neo4j-graphql-js' +import { isEmpty } from 'lodash' import fileUpload from './fileUpload' -import { getBlockedUsers, getBlockedByUsers } from './users.js' -import { mergeWith, isArray, isEmpty } from 'lodash' import { UserInputError } from 'apollo-server' import Resolver from './helpers/Resolver' - -const filterForBlockedUsers = async (params, context) => { - if (!context.user) return params - const [blockedUsers, blockedByUsers] = await Promise.all([ - getBlockedUsers(context), - getBlockedByUsers(context), - ]) - const badIds = [...blockedByUsers.map(b => b.id), ...blockedUsers.map(b => b.id)] - if (!badIds.length) return params - - params.filter = mergeWith( - params.filter, - { - author_not: { id_in: badIds }, - }, - (objValue, srcValue) => { - if (isArray(objValue)) { - return objValue.concat(srcValue) - } - }, - ) - return params -} +import { filterForBlockedUsers } from './helpers/filterForBlockedUsers' const maintainPinnedPosts = params => { const pinnedPostFilter = { pinned: true } diff --git a/backend/src/schema/resolvers/searches.js b/backend/src/schema/resolvers/searches.js new file mode 100644 index 000000000..5316ccd9a --- /dev/null +++ b/backend/src/schema/resolvers/searches.js @@ -0,0 +1,74 @@ +import log from './helpers/databaseLogger' + +export default { + Query: { + findResources: async (_parent, args, context, _resolveInfo) => { + const { query, limit } = args + const { id: thisUserId } = context.user + // see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description + const myQuery = query + .replace(/\s+/g, ' ') + .replace(/[[@#:*~\\$|^\]?/"'(){}+?!,.-;]/g, '') + .split(' ') + .map(s => (s.toLowerCase().match(/^(not|and|or)$/) ? '"' + s + '"' : s + '*')) + .join(' ') + const postCypher = ` + CALL db.index.fulltext.queryNodes('post_fulltext_search', $query) + YIELD node as resource, score + MATCH (resource)<-[:WROTE]-(author:User) + WHERE score >= 0.5 + AND NOT ( + author.deleted = true OR author.disabled = true + OR resource.deleted = true OR resource.disabled = true + OR (:User { id: $thisUserId })-[:BLOCKED]-(author) + ) + WITH resource, author, + [(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments, + [(resource)<-[:SHOUTED]-(user:User) | user] as shouter + RETURN resource { + .*, + __typename: labels(resource)[0], + author: properties(author), + commentsCount: toString(size(comments)), + shoutedCount: toString(size(shouter)) + } + LIMIT $limit + ` + + const userCypher = ` + CALL db.index.fulltext.queryNodes('user_fulltext_search', $query) + YIELD node as resource, score + MATCH (resource) + WHERE score >= 0.5 + AND NOT (resource.deleted = true OR resource.disabled = true + OR (:User { id: $thisUserId })-[:BLOCKED]-(resource)) + RETURN resource {.*, __typename: labels(resource)[0]} + LIMIT $limit + ` + + const session = context.driver.session() + const searchResultPromise = session.readTransaction(async transaction => { + const postTransactionResponse = transaction.run(postCypher, { + query: myQuery, + limit, + thisUserId, + }) + const userTransactionResponse = transaction.run(userCypher, { + query: myQuery, + limit, + thisUserId, + }) + return Promise.all([postTransactionResponse, userTransactionResponse]) + }) + + try { + const [postResults, userResults] = await searchResultPromise + log(postResults) + log(userResults) + return [...postResults.records, ...userResults.records].map(r => r.get('resource')) + } finally { + session.close() + } + }, + }, +} diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 35998b935..23c2ded4d 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -1,23 +1,3 @@ -type Query { - isLoggedIn: Boolean! - # Get the currently logged in User based on the given JWT Token - currentUser: User - findPosts(query: String!, limit: Int = 10, filter: _PostFilter): [Post]! - @cypher( - statement: """ - CALL db.index.fulltext.queryNodes('full_text_search', $query) - YIELD node as post, score - MATCH (post)<-[:WROTE]-(user:User) - WHERE score >= 0.2 - AND NOT user.deleted = true AND NOT user.disabled = true - AND NOT post.deleted = true AND NOT post.disabled = true - AND NOT user.id in COALESCE($filter.author_not.id_in, []) - RETURN post - LIMIT $limit - """ - ) -} - type Mutation { # Get a JWT Token for the given Email and password login(email: String!, password: String!): String! diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index 2e4358b3e..71fcb9605 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -230,4 +230,18 @@ type Query { PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int! PostsEmotionsByCurrentUser(postId: ID!): [String] profilePagePosts(filter: _PostFilter, first: Int, offset: Int, orderBy: [_PostOrdering]): [Post] + findPosts(query: String!, limit: Int = 10, filter: _PostFilter): [Post]! + @cypher( + statement: """ + CALL db.index.fulltext.queryNodes('post_fulltext_search', $query) + YIELD node as post, score + MATCH (post)<-[:WROTE]-(user:User) + WHERE score >= 0.2 + AND NOT user.deleted = true AND NOT user.disabled = true + AND NOT post.deleted = true AND NOT post.disabled = true + AND NOT user.id in COALESCE($filter.author_not.id_in, []) + RETURN post + LIMIT $limit + """ + ) } diff --git a/backend/src/schema/types/type/Search.gql b/backend/src/schema/types/type/Search.gql new file mode 100644 index 000000000..2c22fa61f --- /dev/null +++ b/backend/src/schema/types/type/Search.gql @@ -0,0 +1,5 @@ +union SearchResult = Post | User + +type Query { + findResources(query: String!, limit: Int = 5): [SearchResult]! +} diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 243f45322..6c07e1cc2 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -161,7 +161,20 @@ type Query { ): [User] blockedUsers: [User] + isLoggedIn: Boolean! currentUser: User + findUsers(query: String!,limit: Int = 10, filter: _UserFilter): [User]! + @cypher( + statement: """ + CALL db.index.fulltext.queryNodes('user_fulltext_search', $query) + YIELD node as post, score + MATCH (user) + WHERE score >= 0.2 + AND NOT user.deleted = true AND NOT user.disabled = true + RETURN user + LIMIT $limit + """ + ) } type Mutation { @@ -179,8 +192,7 @@ type Mutation { termsAndConditionsAgreedAt: String allowEmbedIframes: Boolean showShoutsPublicly: Boolean - - locale: String + locale: String ): User DeleteUser(id: ID!, resource: [Deletable]): User diff --git a/backend/yarn.lock b/backend/yarn.lock index 24c6e5fb5..edb8c316f 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -21,12 +21,12 @@ "@types/node" "^10.1.0" long "^4.0.0" -"@apollographql/apollo-tools@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.4.0.tgz#8a1a0ab7a0bb12ccc03b72e4a104cfa5d969fd5f" - integrity sha512-7wEO+S+zgz/wVe3ilFQqICufRBYYDSNUkd1V03JWvXuSydbYq2SM5EgvWmFF+04iadt+aQ0XCCsRzCzRPQODfQ== +"@apollographql/apollo-tools@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.4.3.tgz#938a50aea0935973a75155a73417f2f6fc7ac2ef" + integrity sha512-CtC1bmohB1owdGMT2ZZKacI94LcPAZDN2WvCe+4ZXT5d7xO5PHOAb70EP/LcFbvnS8QI+pkYRSCGFQnUcv9efg== dependencies: - apollo-env "0.5.1" + apollo-env "^0.6.1" "@apollographql/graphql-playground-html@1.6.24": version "1.6.24" @@ -841,10 +841,10 @@ "@hapi/hoek" "8.x.x" "@hapi/topo" "3.x.x" -"@hapi/joi@^17.0.0": - version "17.0.0" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.0.0.tgz#db72e68cf1741b422ff7efe84e0218aae755ce6f" - integrity sha512-96Su6qSTW0OcFb95GHELBiqI2hmnWvo1doJ0BAFsmDcSKkOvGUSUbG6g0koh6oLI7RBd2hNljnkJw61WYyfIhw== +"@hapi/joi@^17.0.2": + version "17.0.2" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.0.2.tgz#613d47f629eb3e4ae17c8065092bce46b87771a6" + integrity sha512-84icw1yV3vbRzUidqFli1Gqr8diigOhTuKzlu3gqBXBm4Lukqe5apjyeJJhGO4rO/J3NiRjy1vXQ5bmhc5+fUA== dependencies: "@hapi/address" "^4.0.0" "@hapi/formula" "^2.0.0" @@ -1053,7 +1053,7 @@ url-regex "~4.1.1" video-extensions "~1.1.0" -"@metascraper/helpers@^5.8.13", "@metascraper/helpers@^5.9.5": +"@metascraper/helpers@^5.9.5": version "5.9.5" resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.9.5.tgz#490ecd05466308d99bc17fd829339cc70c0dcec4" integrity sha512-RJrYv7/W9Ha1VvG97b0UKvaMAWwUY617wVjJC+jN6qe9bUD1Lex4GCPGPm57FiF8zombQ6sbTZeaaulQAOsRlg== @@ -1313,10 +1313,10 @@ "@types/node" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@4.17.1": - version "4.17.1" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.1.tgz#4cf7849ae3b47125a567dfee18bfca4254b88c5c" - integrity sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w== +"@types/express@*", "@types/express@4.17.2": + version "4.17.2" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c" + integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "*" @@ -1408,6 +1408,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== +"@types/node-fetch@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.4.tgz#5245b6d8841fc3a6208b82291119bc11c4e0ce44" + integrity sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@>=6": version "12.7.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44" @@ -1644,13 +1651,13 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -apollo-cache-control@^0.8.10: - version "0.8.10" - resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.10.tgz#c104481ffa77ba32127ad8351ba8948c6382ff8d" - integrity sha512-1/CAEidIoY1PWw2mhwZChchK4fvLdHfdoB7AEikWMYFSbjYY6ZiayT+Q3z48wsiS/LTNugF/YJDJHQi4+qjuug== +apollo-cache-control@^0.8.11: + version "0.8.11" + resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.11.tgz#726e4e3c5685bacbf26c8fbba1f41b4e6252c597" + integrity sha512-8yz4qbRBIFDWRHdT8uPh0HHh+VbQXxoFGJQRAG8hyMRvR+EuURXX1ltXYkn5J3YJ3MKEqgsvwGaq60dFZq63UQ== dependencies: apollo-server-env "^2.4.3" - graphql-extensions "^0.10.9" + graphql-extensions "^0.10.10" apollo-cache-inmemory@~1.6.5: version "1.6.5" @@ -1700,25 +1707,26 @@ apollo-engine-reporting-protobuf@^0.4.4: dependencies: "@apollo/protobufjs" "^1.0.3" -apollo-engine-reporting@^1.4.13: - version "1.4.13" - resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.13.tgz#522b3782014c444e9656b7bdc590bfc59da5e209" - integrity sha512-p5fVmQigNGXoxCHnu8Bb6itNfwtjGnoyLf9i4oP0vfdSnTjaNI8KM7zXv16t1YnE/wsqYuieCnleoPSBReTy9Q== +apollo-engine-reporting@^1.4.14: + version "1.4.14" + resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.14.tgz#71a6509ebe86385da43df500cd0940525a3e8674" + integrity sha512-cCG9qDOPwbh87ZjQGHgmnP3oPqhqjIZcNmm/lNtWkWXGTlxV/jmUEqpVi+wsDbE5gR7d1OFk6GqSy2ZQh+S+Bw== dependencies: apollo-engine-reporting-protobuf "^0.4.4" - apollo-graphql "^0.3.4" + apollo-graphql "^0.3.7" apollo-server-caching "^0.5.1" apollo-server-env "^2.4.3" apollo-server-errors "^2.3.4" apollo-server-types "^0.2.10" async-retry "^1.2.1" - graphql-extensions "^0.10.9" + graphql-extensions "^0.10.10" -apollo-env@0.5.1, apollo-env@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.5.1.tgz#b9b0195c16feadf0fe9fd5563edb0b9b7d9e97d3" - integrity sha512-fndST2xojgSdH02k5hxk1cbqA9Ti8RX4YzzBoAB4oIe1Puhq7+YlhXGXfXB5Y4XN0al8dLg+5nAkyjNAR2qZTw== +apollo-env@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.6.1.tgz#12cc869c4276a5f794edf5e5f243676038d4fb07" + integrity sha512-B9BgpQGR1ndeDtb4Gtor0J4CITQ+OPACZrVW6lgStnljKEe9ZB76DZ1dAd3OCeizAswW6Lo9uvfK8jhVS5nBhQ== dependencies: + "@types/node-fetch" "2.5.4" core-js "^3.0.1" node-fetch "^2.2.0" sha.js "^2.4.11" @@ -1731,12 +1739,12 @@ apollo-errors@^1.9.0: assert "^1.4.1" extendable-error "^0.1.5" -apollo-graphql@^0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.4.tgz#c1f68591a4775945441d049eff9323542ab0401f" - integrity sha512-w+Az1qxePH4oQ8jvbhQBl5iEVvqcqynmU++x/M7MM5xqN1C7m1kyIzpN17gybXlTJXY4Oxej2WNURC2/hwpfYw== +apollo-graphql@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.7.tgz#533232ed48b0b6dbcf5635f65e66cf8677a5b768" + integrity sha512-ghW16xx9tRcyL38Pw6G5OidMnYn+CNUGZWmvqQgEO2nRy4T0ONPZZBOvGrIMtJQ70oEykNMKGm0zm6PdHdxd8Q== dependencies: - apollo-env "^0.5.1" + apollo-env "^0.6.1" lodash.sortby "^4.7.0" apollo-link-context@~1.0.19: @@ -1782,26 +1790,26 @@ apollo-server-caching@^0.5.1: dependencies: lru-cache "^5.0.0" -apollo-server-core@^2.9.15: - version "2.9.15" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.15.tgz#fa18659d90430e2f9556191f9d873dbed6a58ca5" - integrity sha512-MgqtxZkUO2u7cSDQjp8feQlyHT/iZlKv3TV5kNy+xa9ewbfiR/qjziMsz46x+oVPBah+VH9WbGShSbVO0b2TJA== +apollo-server-core@^2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.16.tgz#b4c869a6babfa6906fbbf1e6facf3b7231dbf777" + integrity sha512-4ftdjSfs/3aEare9QNTVSF0yUvXETxiohuDLZ7gmMIQxNnZhUjVXiZL1rYKuIZ12uH7xLvh/DwkXRt6nLG/lZA== dependencies: - "@apollographql/apollo-tools" "^0.4.0" + "@apollographql/apollo-tools" "^0.4.3" "@apollographql/graphql-playground-html" "1.6.24" "@types/graphql-upload" "^8.0.0" "@types/ws" "^6.0.0" - apollo-cache-control "^0.8.10" + apollo-cache-control "^0.8.11" apollo-datasource "^0.6.4" - apollo-engine-reporting "^1.4.13" + apollo-engine-reporting "^1.4.14" apollo-server-caching "^0.5.1" apollo-server-env "^2.4.3" apollo-server-errors "^2.3.4" apollo-server-plugin-base "^0.6.10" apollo-server-types "^0.2.10" - apollo-tracing "^0.8.10" + apollo-tracing "^0.8.11" fast-json-stable-stringify "^2.0.0" - graphql-extensions "^0.10.9" + graphql-extensions "^0.10.10" graphql-tag "^2.9.2" graphql-tools "^4.0.0" graphql-upload "^8.0.2" @@ -1822,18 +1830,18 @@ apollo-server-errors@^2.3.4: resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz#b70ef01322f616cbcd876f3e0168a1a86b82db34" integrity sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA== -apollo-server-express@^2.9.14, apollo-server-express@^2.9.15: - version "2.9.15" - resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.15.tgz#cd0b8c7275be8a6e120809c6c36147a29f3b8129" - integrity sha512-RrPFAW6QqxAGAlubdvxjluGc7SOr70H69ElLxDgXy3HREXN25Y4XZoCE+L3PoURwFy2mNtITZeDO7JKW1cbHNg== +apollo-server-express@^2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.16.tgz#4c30b1769426c010b37943c0fb7766e5825973a0" + integrity sha512-ZDc7GP+piUm67alJ0DIE9f36tHcCiNm3PHMLIVJlVE/rcGwzRjV5rardRqeslljQiO2J+1IwXKwJ0/kRrZ4JvQ== dependencies: "@apollographql/graphql-playground-html" "1.6.24" "@types/accepts" "^1.3.5" "@types/body-parser" "1.17.1" "@types/cors" "^2.8.4" - "@types/express" "4.17.1" + "@types/express" "4.17.2" accepts "^1.3.5" - apollo-server-core "^2.9.15" + apollo-server-core "^2.9.16" apollo-server-types "^0.2.10" body-parser "^1.18.3" cors "^2.8.4" @@ -1851,12 +1859,12 @@ apollo-server-plugin-base@^0.6.10: dependencies: apollo-server-types "^0.2.10" -apollo-server-testing@~2.9.15: - version "2.9.15" - resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.15.tgz#d7dfbd2d07b91f05b57927e471edce34d98dc126" - integrity sha512-R+v+QrOVmeP95xomvbky4jV1MN6e5ihijZkpc/Ir0JhJthWcIZluaMEJqFWZr4K5k0+G66aF+I8O/bh2LM+4GQ== +apollo-server-testing@~2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.16.tgz#35e9b0b102a11bac8db2fce04281cb43e7993d45" + integrity sha512-CLfYZY2Htwzw6iPlFO32/SNXNstWQsvGd5/FQ8KEwRpNfYM4g0rAE98y/THEQTvTh0xPH+qWxA7CVQcc7/FMbQ== dependencies: - apollo-server-core "^2.9.15" + apollo-server-core "^2.9.16" apollo-server-types@^0.2.10: version "0.2.10" @@ -1867,24 +1875,24 @@ apollo-server-types@^0.2.10: apollo-server-caching "^0.5.1" apollo-server-env "^2.4.3" -apollo-server@~2.9.15: - version "2.9.15" - resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.15.tgz#a8d62437fcd26d10351b83050df9e8e9c8ad9a71" - integrity sha512-b9FTRNpuMY2tax+Ln7e9cQdTxP8BBPe6kldbeIc3Rcl6AyHzfzuv682mJ5J87+oQwypx6xavtupXh5KHuQlJRw== +apollo-server@~2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.16.tgz#c0054ed70ecb637cb3f585ff46fb4a060730076f" + integrity sha512-dqB1shkjl9ne7DfSHXDH5sT70llr9zswLL+/g/4zt4/H+k+2pkD1BShQkNIK7PBYcVa8KvRAHXiHTXZ36GCspA== dependencies: - apollo-server-core "^2.9.15" - apollo-server-express "^2.9.15" + apollo-server-core "^2.9.16" + apollo-server-express "^2.9.16" express "^4.0.0" graphql-subscriptions "^1.0.0" graphql-tools "^4.0.0" -apollo-tracing@^0.8.10: - version "0.8.10" - resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.10.tgz#e031a0e7fbd8662d4c2e3af03284a3eefeaf0a45" - integrity sha512-EjToHqHdDrjj0xBRnbie57lz3U81Onrs55YqHhvzaMv8gDAeKX3rnXiw5EfYZNxR7uutoyXH0iNKaYPyolWbZA== +apollo-tracing@^0.8.11: + version "0.8.11" + resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.11.tgz#55822aac7381da77c703b52d35c4dab9393ec33c" + integrity sha512-Z0wDZ5QOBmpGoajB74ZKGTM7GzG6rqZRzAph4kxud6axcyNqUDKiKZ3Eere+NSLwvvt8M3qnPW4UJSUy/wwOXg== dependencies: apollo-server-env "^2.4.3" - graphql-extensions "^0.10.9" + graphql-extensions "^0.10.10" apollo-utilities@1.3.3, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.3: version "1.3.3" @@ -4199,12 +4207,12 @@ graphql-custom-directives@~0.2.14: moment "^2.22.2" numeral "^2.0.6" -graphql-extensions@^0.10.9: - version "0.10.9" - resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.9.tgz#1b66baba8018c568f33a94e445134b614972a3db" - integrity sha512-NJdV8aGVGCrcm1Pbq8IbmV5FfLCAEPxgplh9EJU7qAP+Z4PenkuH6V6RAPRqIwwZ287m8/aDXKW+X0qFe8gyUQ== +graphql-extensions@^0.10.10: + version "0.10.10" + resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.10.tgz#6b89d6b171f02a83bd4252f1e71c8d69147e7e2d" + integrity sha512-pNb1DmUk6vsGtCjCRecpKoXadKNMyKxyLyE9IX65N9aKSmLL+AF7dJOOc4MWhdaAXlzxaDDhe54GpaOfoH7AOw== dependencies: - "@apollographql/apollo-tools" "^0.4.0" + "@apollographql/apollo-tools" "^0.4.3" apollo-server-env "^2.4.3" apollo-server-types "^0.2.10" @@ -5965,12 +5973,12 @@ metascraper-title@^5.9.5: "@metascraper/helpers" "^5.9.5" lodash "~4.17.15" -metascraper-url@^5.8.13: - version "5.8.13" - resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.8.13.tgz#ddeefbfee556a6eceb687ed9d4ff1f3ed9170bdf" - integrity sha512-jPezb6ICTU7LK6QuhIhXLmNkL7GfKHnNm8BDmOfI266tfLP7xiz0NZZHJA0+xsmRoP3iMCo3wLA1c7s3Kie75w== +metascraper-url@^5.9.5: + version "5.9.5" + resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.9.5.tgz#9e7a07bc393256872cfb56e4f54011852912baf1" + integrity sha512-A6gs/RkgWaD/xD2bUK1pcBqK8IdA5mYFxSo7clizOeoFkwr/O3oD8Unp465ILiPkXJddtw0BdDpVUwFGEziufg== dependencies: - "@metascraper/helpers" "^5.8.13" + "@metascraper/helpers" "^5.9.5" metascraper-video@^5.9.5: version "5.9.5" @@ -6243,10 +6251,10 @@ neo4j-driver@^4.0.1: text-encoding-utf-8 "^1.0.2" uri-js "^4.2.2" -neo4j-graphql-js@^2.11.4: - version "2.11.4" - resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.11.4.tgz#9d52db0d989df9329c0e8c6d99bfc499a41136ae" - integrity sha512-uxC2N3KoH4/X6JAyQybWe3w5c88YtkIuj1VRSWmVkk8MTaxsNDGyDi2jyppSqL3p7KzdLTv8+jmhKutmXoiufg== +neo4j-graphql-js@^2.11.5: + version "2.11.5" + resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.11.5.tgz#4e887d727ec05b2c57ab81fad373fa3fcb734e39" + integrity sha512-vex6PRqDT5wdxYgmw9p5oii9EUbflEkjzpjJ0tG1JfhWl5e7W/CLHfjT6wyl5wWRq8WYYvREAX3ADsdNapqUtw== dependencies: "@babel/runtime" "^7.5.5" "@babel/runtime-corejs2" "^7.5.5" diff --git a/cypress/integration/common/search.js b/cypress/integration/common/search.js index 35b2d1346..020607bf0 100644 --- a/cypress/integration/common/search.js +++ b/cypress/integration/common/search.js @@ -1,21 +1,24 @@ import { When, Then } from "cypress-cucumber-preprocessor/steps"; When("I search for {string}", value => { - cy.get("#nav-search") + cy.get(".searchable-input .ds-select-search") .focus() .type(value); }); -Then("I should have one post in the select dropdown", () => { - cy.get(".input .ds-select-dropdown").should($li => { +Then("I should have one item in the select dropdown", () => { + cy.get(".searchable-input .ds-select-dropdown").should($li => { expect($li).to.have.length(1); }); }); Then("the search has no results", () => { - cy.get(".input .ds-select-dropdown").should($li => { + cy.get(".searchable-input .ds-select-dropdown").should($li => { expect($li).to.have.length(1); }); cy.get(".ds-select-dropdown").should("contain", 'Nothing found'); + cy.get(".searchable-input .ds-select-search") + .focus() + .type("{esc}"); }); Then("I should see the following posts in the select dropdown:", table => { @@ -24,26 +27,33 @@ Then("I should see the following posts in the select dropdown:", table => { }); }); +Then("I should see the following users in the select dropdown:", table => { + cy.get(".ds-heading").should("contain", "Users"); + table.hashes().forEach(({ slug }) => { + cy.get(".ds-select-dropdown").should("contain", slug); + }); +}); + When("I type {string} and press Enter", value => { - cy.get("#nav-search") + cy.get(".searchable-input .ds-select-search") .focus() .type(value) .type("{enter}", { force: true }); }); When("I type {string} and press escape", value => { - cy.get("#nav-search") + cy.get(".searchable-input .ds-select-search") .focus() .type(value) .type("{esc}"); }); Then("the search field should clear", () => { - cy.get("#nav-search").should("have.text", ""); + cy.get(".searchable-input .ds-select-search").should("have.text", ""); }); -When("I select an entry", () => { - cy.get(".input .ds-select-dropdown ul li") +When("I select a post entry", () => { + cy.get(".searchable-input .search-post") .first() .trigger("click"); }); @@ -75,3 +85,13 @@ Then( ); } ); + +Then("I select a user entry", () => { + cy.get(".searchable-input .userinfo") + .first() + .trigger("click"); +}) + +Then("I should be on the user's profile", () => { + cy.location("pathname").should("eq", "/profile/user-for-search/search-for-me") +}) diff --git a/cypress/integration/search/Search.feature b/cypress/integration/search/Search.feature index c1afc5b97..e83f58477 100644 --- a/cypress/integration/search/Search.feature +++ b/cypress/integration/search/Search.feature @@ -9,18 +9,23 @@ Feature: Search | id | title | content | | p1 | 101 Essays that will change the way you think | 101 Essays, of course! | | p2 | No searched for content | will be found in this post, I guarantee | + And we have the following user accounts: + | slug | name | id | + | search-for-me | Search for me | user-for-search | + | not-to-be-found | Not to be found | just-an-id | + Given I am logged in Scenario: Search for specific words When I search for "Essays" - Then I should have one post in the select dropdown + Then I should have one item in the select dropdown Then I should see the following posts in the select dropdown: | title | | 101 Essays that will change the way you think | Scenario: Press enter starts search - When I type "Essa" and press Enter - Then I should have one post in the select dropdown + When I type "Es" and press Enter + Then I should have one item in the select dropdown Then I should see the following posts in the select dropdown: | title | | 101 Essays that will change the way you think | @@ -31,11 +36,20 @@ Feature: Search Scenario: Select entry goes to post When I search for "Essays" - And I select an entry + And I select a post entry Then I should be on the post's page Scenario: Select dropdown content When I search for "Essays" - Then I should have one post in the select dropdown + Then I should have one item in the select dropdown Then I should see posts with the searched-for term in the select dropdown And I should not see posts without the searched-for term in the select dropdown + + Scenario: Search for users + Given I search for "Search" + Then I should have one item in the select dropdown + And I should see the following users in the select dropdown: + | slug | + | search-for-me | + And I select a user entry + Then I should be on the user's profile \ No newline at end of file diff --git a/neo4j/db_setup.sh b/neo4j/db_setup.sh index ba90ee5f4..b7562d0c9 100755 --- a/neo4j/db_setup.sh +++ b/neo4j/db_setup.sh @@ -2,7 +2,6 @@ ENV_FILE=$(dirname "$0")/.env [[ -f "$ENV_FILE" ]] && source "$ENV_FILE" - if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables." echo "Setting up database constraints and indexes will probably fail because of authentication errors." @@ -21,7 +20,8 @@ CALL db.indexes(); ' | cypher-shell echo ' -CALL db.index.fulltext.createNodeIndex("full_text_search",["Post"],["title", "content"]); +CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"]); +CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"]); CREATE CONSTRAINT ON (p:Post) ASSERT p.id IS UNIQUE; CREATE CONSTRAINT ON (c:Comment) ASSERT c.id IS UNIQUE; CREATE CONSTRAINT ON (c:Category) ASSERT c.id IS UNIQUE; diff --git a/package.json b/package.json index e29c99586..d8b880cdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "human-connection", - "version": "0.2.0", + "version": "0.2.1", "description": "Fullstack and API tests with cypress and cucumber for Human Connection", "author": "Human Connection gGmbh", "license": "MIT", diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 29f7b2b13..c5025949b 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -1,4 +1,4 @@ -FROM node:13.6.0-alpine as base +FROM node:lts-alpine as base LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)" EXPOSE 3000 diff --git a/webapp/Dockerfile.maintenance b/webapp/Dockerfile.maintenance index ee0db908a..ca4ba37bc 100644 --- a/webapp/Dockerfile.maintenance +++ b/webapp/Dockerfile.maintenance @@ -1,4 +1,4 @@ -FROM node:13.6.0-alpine as build +FROM node:lts-alpine as build LABEL Description="Maintenance page of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)" EXPOSE 3000 diff --git a/webapp/components/DonationInfo/DonationInfo.spec.js b/webapp/components/DonationInfo/DonationInfo.spec.js index c308e2146..307d997c4 100644 --- a/webapp/components/DonationInfo/DonationInfo.spec.js +++ b/webapp/components/DonationInfo/DonationInfo.spec.js @@ -37,7 +37,7 @@ describe('DonationInfo.vue', () => { ).toBe('donations.donate-now') }) - it('creates a title from the current month and a translation string', () => { + it.skip('creates a title from the current month and a translation string', () => { mocks.$t = jest.fn(() => 'Spenden für') expect(Wrapper().vm.title).toBe('Spenden für Dezember') }) @@ -49,7 +49,7 @@ describe('DonationInfo.vue', () => { }) describe('given german locale', () => { - it('creates a label from the given amounts and a translation string', () => { + it.skip('creates a label from the given amounts and a translation string', () => { expect(mocks.$t).toBeCalledWith( 'donations.amount-of-total', expect.objectContaining({ @@ -65,7 +65,7 @@ describe('DonationInfo.vue', () => { mocks.$i18n.locale = () => 'en' }) - it('creates a label from the given amounts and a translation string', () => { + it.skip('creates a label from the given amounts and a translation string', () => { expect(mocks.$t).toBeCalledWith( 'donations.amount-of-total', expect.objectContaining({ diff --git a/webapp/components/LoginForm/LoginForm.spec.js b/webapp/components/LoginForm/LoginForm.spec.js index b60680f37..045378287 100644 --- a/webapp/components/LoginForm/LoginForm.spec.js +++ b/webapp/components/LoginForm/LoginForm.spec.js @@ -51,11 +51,10 @@ describe('LoginForm', () => { it('dispatches login with form data', () => { fillIn(Wrapper()) - expect(storeMocks.actions['auth/login']).toHaveBeenCalledWith( - expect.any(Object), - { email: 'email@example.org', password: '1234' }, - undefined, - ) + expect(storeMocks.actions['auth/login']).toHaveBeenCalledWith(expect.any(Object), { + email: 'email@example.org', + password: '1234', + }) }) }) }) diff --git a/webapp/components/SearchInput.spec.js b/webapp/components/SearchInput.spec.js deleted file mode 100644 index 8cc8b9459..000000000 --- a/webapp/components/SearchInput.spec.js +++ /dev/null @@ -1,140 +0,0 @@ -import { mount } from '@vue/test-utils' -import SearchInput from './SearchInput.vue' - -const localVue = global.localVue - -localVue.filter('truncate', () => 'truncated string') -localVue.filter('dateTime', () => Date.now) - -describe('SearchInput.vue', () => { - let mocks - let propsData - - beforeEach(() => { - propsData = {} - }) - - describe('mount', () => { - const Wrapper = () => { - mocks = { - $t: () => {}, - } - return mount(SearchInput, { mocks, localVue, propsData }) - } - - it('renders', () => { - expect(Wrapper().is('div')).toBe(true) - }) - - it('has id "nav-search"', () => { - expect(Wrapper().contains('#nav-search')).toBe(true) - }) - - it('defaults to an empty value', () => { - expect(Wrapper().vm.value).toBe('') - }) - - it('defaults to id "nav-search"', () => { - expect(Wrapper().vm.id).toBe('nav-search') - }) - - it('default to a 300 millisecond delay from the time the user stops typing to when the search starts', () => { - expect(Wrapper().vm.delay).toEqual(300) - }) - - it('defaults to an empty array as results', () => { - expect(Wrapper().vm.results).toEqual([]) - }) - - it('defaults to pending false, as in the search is not pending', () => { - expect(Wrapper().vm.pending).toBe(false) - }) - - it('accepts values as a string', () => { - propsData = { value: 'abc' } - const wrapper = Wrapper() - expect(wrapper.vm.value).toEqual('abc') - }) - - describe('testing custom functions', () => { - let select - let wrapper - - beforeEach(() => { - wrapper = Wrapper() - select = wrapper.find('.ds-select') - select.trigger('focus') - select.element.value = 'abcd' - }) - - it('opens the select dropdown when focused on', () => { - expect(wrapper.vm.isOpen).toBe(true) - }) - - it('opens the select dropdown and blurs after focused on', () => { - select.trigger('blur') - expect(wrapper.vm.isOpen).toBe(false) - }) - - it('is clearable', () => { - select.trigger('input') - select.trigger('keyup.esc') - expect(wrapper.emitted().clear.length).toBe(1) - }) - - it('changes the unprocessedSearchInput as the value changes', () => { - select.trigger('input') - expect(wrapper.vm.unprocessedSearchInput).toBe('abcd') - }) - - it('searches for the term when enter is pressed', async () => { - select.trigger('input') - select.trigger('keyup.enter') - await expect(wrapper.emitted().search[0]).toEqual(['abcd']) - }) - - it('calls onDelete when the delete key is pressed', () => { - const spy = jest.spyOn(wrapper.vm, 'onDelete') - select.trigger('input') - select.trigger('keyup.delete') - expect(spy).toHaveBeenCalledTimes(1) - }) - - it('calls query when a user starts a search by pressing enter', () => { - const spy = jest.spyOn(wrapper.vm, 'query') - select.trigger('input') - select.trigger('keyup.enter') - expect(spy).toHaveBeenCalledWith('abcd') - }) - - it('calls onSelect when a user selects an item in the search dropdown menu', async () => { - // searched for term in the browser, copied the results from Vuex in Vue dev tools - propsData = { - results: [ - { - __typename: 'Post', - author: { - __typename: 'User', - id: 'u5', - name: 'Trick', - slug: 'trick', - }, - commentsCount: 0, - createdAt: '2019-03-13T11:00:20.835Z', - id: 'p10', - label: 'Eos aut illo omnis quis eaque et iure aut.', - shoutedCount: 0, - slug: 'eos-aut-illo-omnis-quis-eaque-et-iure-aut', - value: 'Eos aut illo omnis quis eaque et iure aut.', - }, - ], - } - wrapper = Wrapper() - select.trigger('input') - const results = wrapper.find('.ds-select-option') - results.trigger('click') - await expect(wrapper.emitted().select[0]).toEqual(propsData.results) - }) - }) - }) -}) diff --git a/webapp/components/SearchInput.vue b/webapp/components/SearchInput.vue deleted file mode 100644 index b1b967355..000000000 --- a/webapp/components/SearchInput.vue +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - - - {{ option.label | truncate(70) }} - - - - - - - {{ option.commentsCount }} - - - - {{ option.shoutedCount }} - - - - - - - {{ option.author.name | truncate(32) }} - - {{ option.createdAt | dateTime('dd.MM.yyyy') }} - - - - - - - - - - - - - - - diff --git a/webapp/components/features/SearchField/SearchField.spec.js b/webapp/components/features/SearchField/SearchField.spec.js new file mode 100644 index 000000000..05daf7a9c --- /dev/null +++ b/webapp/components/features/SearchField/SearchField.spec.js @@ -0,0 +1,64 @@ +import { config, mount } from '@vue/test-utils' +import Vuex from 'vuex' +import SearchField from './SearchField.vue' +import SearchableInput from '~/components/generic/SearchableInput/SearchableInput' +import { searchResults } from '~/components/generic/SearchableInput/SearchableInput.story' +const localVue = global.localVue + +localVue.filter('truncate', () => 'truncated string') +localVue.filter('dateTime', () => Date.now) +config.stubs['nuxt-link'] = '' + +describe('SearchField.vue', () => { + let mocks, wrapper, getters + beforeEach(() => { + mocks = { + $apollo: { + query: jest.fn(), + }, + $t: jest.fn(string => string), + } + getters = { 'auth/isModerator': () => false } + wrapper = Wrapper() + }) + + const Wrapper = () => { + const store = new Vuex.Store({ + getters, + }) + return mount(SearchField, { mocks, localVue, store }) + } + + describe('mount', () => { + describe('Emitted events', () => { + let searchableInputComponent + beforeEach(() => { + searchableInputComponent = wrapper.find(SearchableInput) + }) + + describe('query event', () => { + it('calls an apollo query', () => { + searchableInputComponent.vm.$emit('query', 'abcd') + expect(mocks.$apollo.query).toHaveBeenCalledWith( + expect.objectContaining({ variables: { query: 'abcd' } }), + ) + }) + }) + + describe('clearSearch event', () => { + beforeEach(() => { + wrapper.setData({ searchResults, pending: true }) + searchableInputComponent.vm.$emit('clearSearch') + }) + + it('clears searchResults', () => { + expect(wrapper.vm.searchResults).toEqual([]) + }) + + it('set pending to false', () => { + expect(wrapper.vm.pending).toBe(false) + }) + }) + }) + }) +}) diff --git a/webapp/components/features/SearchField/SearchField.vue b/webapp/components/features/SearchField/SearchField.vue new file mode 100644 index 000000000..29ab8650d --- /dev/null +++ b/webapp/components/features/SearchField/SearchField.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/webapp/components/generic/SearchHeading/SearchHeading.spec.js b/webapp/components/generic/SearchHeading/SearchHeading.spec.js new file mode 100644 index 000000000..2ddd3e9ba --- /dev/null +++ b/webapp/components/generic/SearchHeading/SearchHeading.spec.js @@ -0,0 +1,27 @@ +import { mount } from '@vue/test-utils' +import SearchHeading from './SearchHeading.vue' + +const localVue = global.localVue + +describe('SearchHeading.vue', () => { + let mocks, wrapper, propsData + beforeEach(() => { + mocks = { + $t: jest.fn(string => string), + } + propsData = { + resourceType: 'Post', + } + wrapper = Wrapper() + }) + + const Wrapper = () => { + return mount(SearchHeading, { mocks, localVue, propsData }) + } + + describe('mount', () => { + it('renders heading', () => { + expect(wrapper.text()).toMatch('search.heading.Post') + }) + }) +}) diff --git a/webapp/components/generic/SearchHeading/SearchHeading.vue b/webapp/components/generic/SearchHeading/SearchHeading.vue new file mode 100644 index 000000000..fdd9357ab --- /dev/null +++ b/webapp/components/generic/SearchHeading/SearchHeading.vue @@ -0,0 +1,26 @@ + + + + {{ $t(`search.heading.${resourceType}`) }} + + + + + diff --git a/webapp/components/generic/SearchPost/SearchPost.spec.js b/webapp/components/generic/SearchPost/SearchPost.spec.js new file mode 100644 index 000000000..39cf5dcc0 --- /dev/null +++ b/webapp/components/generic/SearchPost/SearchPost.spec.js @@ -0,0 +1,64 @@ +import { shallowMount } from '@vue/test-utils' +import SearchPost from './SearchPost.vue' + +const localVue = global.localVue +localVue.filter('dateTime', d => d) + +describe('SearchPost.vue', () => { + let mocks, wrapper, propsData + beforeEach(() => { + mocks = { + $t: jest.fn(string => string), + } + propsData = { + option: { + title: 'Post Title', + commentsCount: 3, + shoutedCount: 6, + createdAt: '23.08.2019', + author: { + name: 'Post Author', + }, + }, + } + wrapper = Wrapper() + }) + + const Wrapper = () => { + return shallowMount(SearchPost, { mocks, localVue, propsData }) + } + + describe('shallowMount', () => { + it('renders post title', () => { + expect(wrapper.find('.search-option-label').text()).toMatch('Post Title') + }) + + it('renders post commentsCount', () => { + expect( + wrapper + .find('.search-post-meta') + .findAll('span') + .filter(item => item.text() === '3') + .exists(), + ).toBe(true) + }) + + it('renders post shoutedCount', () => { + expect( + wrapper + .find('.search-post-meta') + .findAll('span') + .filter(item => item.text() === '6') + .exists(), + ).toBe(true) + }) + + it('renders post author', () => { + expect(wrapper.find('.search-post-author').text()).toContain('Post Author') + }) + + it('renders post createdAt', () => { + expect(wrapper.find('.search-post-author').text()).toContain('23.08.2019') + }) + }) +}) diff --git a/webapp/components/generic/SearchPost/SearchPost.vue b/webapp/components/generic/SearchPost/SearchPost.vue new file mode 100644 index 000000000..3da6056ba --- /dev/null +++ b/webapp/components/generic/SearchPost/SearchPost.vue @@ -0,0 +1,71 @@ + + + + {{ option.title | truncate(70) }} + + + + + + + {{ option.commentsCount }} + + + + {{ option.shoutedCount }} + + + + + + + {{ option.author.name | truncate(32) }} - + {{ option.createdAt | dateTime('dd.MM.yyyy') }} + + + + + + + + diff --git a/webapp/components/generic/SearchableInput/SearchableInput.spec.js b/webapp/components/generic/SearchableInput/SearchableInput.spec.js new file mode 100644 index 000000000..db314630f --- /dev/null +++ b/webapp/components/generic/SearchableInput/SearchableInput.spec.js @@ -0,0 +1,115 @@ +import { config, mount } from '@vue/test-utils' +import Vuex from 'vuex' +import Vue from 'vue' +import SearchableInput from './SearchableInput' +import { searchResults } from '~/components/generic/SearchableInput/SearchableInput.story' + +const localVue = global.localVue + +localVue.filter('truncate', () => 'truncated string') +localVue.filter('dateTime', () => Date.now) +config.stubs['nuxt-link'] = '' + +describe('SearchableInput.vue', () => { + let mocks, propsData, getters, wrapper + + beforeEach(() => { + propsData = {} + mocks = { + $router: { + push: jest.fn(), + }, + $t: jest.fn(string => string), + } + getters = { 'auth/isModerator': () => false } + wrapper = Wrapper() + }) + const Wrapper = () => { + const store = new Vuex.Store({ + getters, + }) + return mount(SearchableInput, { mocks, localVue, propsData, store }) + } + + describe('mount', () => { + describe('testing custom functions', () => { + let select + + beforeEach(() => { + select = wrapper.find('.ds-select') + select.trigger('focus') + select.element.value = 'abcd' + }) + + it('opens the select dropdown when focused on', () => { + expect(wrapper.find('.is-open').exists()).toBe(true) + }) + + it('opens the select dropdown and blurs after focused on', () => { + select.trigger('blur') + expect(wrapper.find('.is-open').exists()).toBe(false) + }) + + it('is clearable', () => { + select.trigger('input') + select.trigger('keyup.esc') + expect(wrapper.find('.is-open').exists()).toBe(false) + }) + + it('changes the unprocessedSearchInput as the value changes', () => { + select.trigger('input') + expect(select.element.value).toBe('abcd') + }) + + it('searches for the term when enter is pressed', async () => { + select.element.value = 'ab' + select.trigger('input') + select.trigger('keyup.enter') + await expect(wrapper.emitted().query[0]).toEqual(['ab']) + }) + + it('calls onDelete when the delete key is pressed', () => { + const spy = jest.spyOn(wrapper.vm, 'onDelete') + select.trigger('input') + select.trigger('keyup.delete') + expect(spy).toHaveBeenCalledTimes(1) + }) + + describe('navigating to resource', () => { + beforeEach(() => { + propsData = { options: searchResults } + wrapper = Wrapper() + select = wrapper.find('.ds-select') + select.trigger('focus') + }) + + it('pushes to post page', async () => { + select.element.value = 'Post' + select.trigger('input') + const post = wrapper.find('.search-post') + post.trigger('click') + await Vue.nextTick().then(() => { + expect(mocks.$router.push).toHaveBeenCalledWith({ + name: 'post-id-slug', + params: { id: 'post-by-jenny', slug: 'user-post-by-jenny' }, + }) + }) + }) + + it("pushes to user's profile", async () => { + select.element.value = 'Bob' + select.trigger('input') + const users = wrapper.findAll('.userinfo') + const bob = users.filter(item => item.text() === '@bob-der-baumeister') + bob.trigger('click') + await Vue.nextTick().then(() => { + expect(mocks.$router.push).toHaveBeenCalledWith({ + name: 'profile-id-slug', + params: { id: 'u2', slug: 'bob-der-baumeister' }, + }) + }) + }) + }) + }) + }) +}) diff --git a/webapp/components/generic/SearchableInput/SearchableInput.story.js b/webapp/components/generic/SearchableInput/SearchableInput.story.js new file mode 100644 index 000000000..68feaadba --- /dev/null +++ b/webapp/components/generic/SearchableInput/SearchableInput.story.js @@ -0,0 +1,115 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import SearchableInput from './SearchableInput.vue' +import helpers from '~/storybook/helpers' + +helpers.init() + +export const searchResults = [ + { + id: 'post-by-jenny', + __typename: 'Post', + slug: 'user-post-by-jenny', + title: 'User Post by Jenny', + value: 'User Post by Jenny', + shoutedCount: 0, + commentCount: 4, + createdAt: '2019-11-13T03:03:16.155Z', + author: { + id: 'u3', + name: 'Jenny Rostock', + slug: 'jenny-rostock', + }, + }, + { + id: 'f48f00a0-c412-432f-8334-4276a4e15d1c', + __typename: 'Post', + slug: 'eum-quos-est-molestiae-enim-magni-consequuntur-sed-commodi-eos', + title: 'Eum quos est molestiae enim magni consequuntur sed commodi eos.', + value: 'Eum quos est molestiae enim magni consequuntur sed commodi eos.', + shoutedCount: 0, + commentCount: 0, + createdAt: '2019-11-13T03:00:45.478Z', + author: { + id: 'u6', + name: 'Louie', + slug: 'louie', + }, + }, + { + id: 'p7', + __typename: 'Post', + slug: 'this-is-post-7', + title: 'This is post #7', + value: 'This is post #7', + shoutedCount: 1, + commentCount: 1, + createdAt: '2019-11-13T03:00:23.098Z', + author: { + id: 'u6', + name: 'Louie', + slug: 'louie', + }, + }, + { + id: 'p12', + __typename: 'Post', + slug: 'this-is-post-12', + title: 'This is post #12', + value: 'This is post #12', + shoutedCount: 0, + commentCount: 12, + createdAt: '2019-11-13T03:00:23.098Z', + author: { + id: 'u6', + name: 'Louie', + slug: 'louie', + }, + }, + { + id: 'u1', + __typename: 'User', + avatar: + 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg', + name: 'Peter Lustig', + slug: 'peter-lustig', + }, + { + id: 'cdbca762-0632-4564-b646-415a0c42d8b8', + __typename: 'User', + avatar: + 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg', + name: 'Herbert Schultz', + slug: 'herbert-schultz', + }, + { + id: 'u2', + __typename: 'User', + avatar: + 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg', + name: 'Bob der Baumeister', + slug: 'bob-der-baumeister', + }, + { + id: '7b654f72-f4da-4315-8bed-39de0859754b', + __typename: 'User', + avatar: + 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg', + name: 'Tonya Mohr', + slug: 'tonya-mohr', + }, +] + +storiesOf('Search Field', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .add('test', () => ({ + components: { SearchableInput }, + store: helpers.store, + data: () => ({ + searchResults, + }), + template: ` + + `, + })) diff --git a/webapp/components/generic/SearchableInput/SearchableInput.vue b/webapp/components/generic/SearchableInput/SearchableInput.vue new file mode 100644 index 000000000..cc9269ecf --- /dev/null +++ b/webapp/components/generic/SearchableInput/SearchableInput.vue @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webapp/graphql/Search.js b/webapp/graphql/Search.js new file mode 100644 index 000000000..9b142b429 --- /dev/null +++ b/webapp/graphql/Search.js @@ -0,0 +1,24 @@ +import gql from 'graphql-tag' +import { userFragment, postFragment } from './Fragments' + +export const findResourcesQuery = gql` + ${userFragment} + ${postFragment} + + query($query: String!) { + findResources(query: $query, limit: 5) { + __typename + ... on Post { + ...post + commentsCount + shoutedCount + author { + ...user + } + } + ... on User { + ...user + } + } + } +` diff --git a/webapp/layouts/default.vue b/webapp/layouts/default.vue index 94a9c0912..6be75cd35 100644 --- a/webapp/layouts/default.vue +++ b/webapp/layouts/default.vue @@ -19,18 +19,10 @@ - - quickSearch({ value })" - @select="goToPost" - /> - +