Merge branch 'master' into migrate-styleguide-card

This commit is contained in:
Alina Beck 2020-02-19 10:25:37 +01:00
commit 6aecbd6c6d
158 changed files with 9500 additions and 7527 deletions

24
.github/ISSUE_TEMPLATE/devops_ticket.md vendored Normal file
View File

@ -0,0 +1,24 @@
---
name: 💥 DevOps ticket
about: Help us manage our deployed App.
labels: devops
title: 💥 [DevOps]
---
## :fire: DevOps ticket
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the problem is.-->
### Motive
<!-- Why does this task need to be done? What can we benefit from this? -->
### Related issues
<!-- Are there any related issues to link to? Please paste them below for reference. -->
### Implementation
<!-- Please, document any ideas of how the task can be performed. -->
### Validation
<!-- How can we make sure that this task was successful? -->
### Additional context
<!-- Add other context or background about the feature request here.-->

View File

@ -0,0 +1,20 @@
---
name: 🔧 Refactor ticket
about: Help us improve our code by refactoring it.
labels: refactor
title: 🔧 [Refactor]
---
## :zap: Refactor ticket
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the problem is.-->
### Motive
<!-- What is the purpose of this refactoring? If it's removing depcrecated code, please link to the deprecation notice. -->
### Related issues
<!-- Are there any related issues to link to? Please paste them below for reference. -->
### Implementation
<!-- Please, document any ideas of how the code should be refactored. -->
### Additional context
<!-- Add other context or background about the feature request here.-->

View File

@ -24,6 +24,9 @@ script:
- export CYPRESS_RETRIES=1 - export CYPRESS_RETRIES=1
- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) - export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)
- echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH" - echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH"
# Miscellaneous
- ./scripts/translations/sort.sh
- ./scripts/translations/missing-keys.sh
# Backend # Backend
- docker-compose exec backend yarn run lint - docker-compose exec backend yarn run lint
- docker-compose exec backend yarn run test --ci --verbose=false --coverage - docker-compose exec backend yarn run test --ci --verbose=false --coverage
@ -69,7 +72,3 @@ deploy:
script: bash scripts/deploy.sh script: bash scripts/deploy.sh
on: on:
branch: master branch: master
- provider: script
script: bash scripts/github_release.sh
on:
branch: master

11
.vscode/settings.json vendored
View File

@ -1,11 +0,0 @@
{
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "vue",
"autoFix": true
}
],
"editor.formatOnSave": false,
}

View File

@ -4,10 +4,67 @@ 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). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.3.1](https://github.com/Human-Connection/Human-Connection/compare/v0.3.0...v0.3.1)
> 10 February 2020
- fix: Display unblock feature only for blocking user [`#3034`](https://github.com/Human-Connection/Human-Connection/pull/3034)
- refactor(factories): Refactor test factories with rosie.js [`#2921`](https://github.com/Human-Connection/Human-Connection/pull/2921)
- build(deps-dev): bump @vue/cli-shared-utils from 4.1.2 to 4.2.2 in /webapp [`#3031`](https://github.com/Human-Connection/Human-Connection/pull/3031)
- build(deps): bump graphql-shield from 7.0.10 to 7.0.11 in /backend [`#3028`](https://github.com/Human-Connection/Human-Connection/pull/3028)
- build(deps-dev): bump codecov from 3.6.4 to 3.6.5 [`#3027`](https://github.com/Human-Connection/Human-Connection/pull/3027)
- chore: Add DevOps issue template [`#2999`](https://github.com/Human-Connection/Human-Connection/pull/2999)
- fix: Error pages can be translated [`#2826`](https://github.com/Human-Connection/Human-Connection/pull/2826)
- build(deps-dev): bump apollo-server-testing from 2.9.16 to 2.10.0 in /backend [`#3020`](https://github.com/Human-Connection/Human-Connection/pull/3020)
- build(deps): bump apollo-server from 2.9.16 to 2.10.0 in /backend [`#3019`](https://github.com/Human-Connection/Human-Connection/pull/3019)
- build(deps): bump graphql-tag from 2.10.2 to 2.10.3 in /backend [`#3011`](https://github.com/Human-Connection/Human-Connection/pull/3011)
- build(deps): bump graphql-shield from 7.0.9 to 7.0.10 in /backend [`#3010`](https://github.com/Human-Connection/Human-Connection/pull/3010)
- build(deps-dev): bump @storybook/addon-actions from 5.3.10 to 5.3.12 in /webapp [`#2998`](https://github.com/Human-Connection/Human-Connection/pull/2998)
- build(deps-dev): bump @storybook/addon-notes from 5.3.10 to 5.3.12 in /webapp [`#2997`](https://github.com/Human-Connection/Human-Connection/pull/2997)
- build(deps-dev): bump @storybook/addon-a11y from 5.3.10 to 5.3.12 in /webapp [`#2996`](https://github.com/Human-Connection/Human-Connection/pull/2996)
- build(deps): bump metascraper-author from 5.10.6 to 5.10.7 in /backend [`#2994`](https://github.com/Human-Connection/Human-Connection/pull/2994)
- build(deps): bump metascraper-title from 5.10.6 to 5.10.7 in /backend [`#2978`](https://github.com/Human-Connection/Human-Connection/pull/2978)
- build(deps-dev): bump @storybook/vue from 5.3.10 to 5.3.12 in /webapp [`#2995`](https://github.com/Human-Connection/Human-Connection/pull/2995)
- build(deps): bump metascraper-audio from 5.10.6 to 5.10.7 in /backend [`#2993`](https://github.com/Human-Connection/Human-Connection/pull/2993)
- build(deps): bump graphql-tag from 2.10.1 to 2.10.2 in /backend [`#2992`](https://github.com/Human-Connection/Human-Connection/pull/2992)
- build(deps): bump metascraper-url from 5.10.6 to 5.10.7 in /backend [`#2991`](https://github.com/Human-Connection/Human-Connection/pull/2991)
- build(deps): bump @sentry/node from 5.12.0 to 5.12.3 in /backend [`#2990`](https://github.com/Human-Connection/Human-Connection/pull/2990)
- build(deps-dev): bump @storybook/addon-notes from 5.3.9 to 5.3.10 in /webapp [`#2951`](https://github.com/Human-Connection/Human-Connection/pull/2951)
- build(deps): bump metascraper from 5.10.6 to 5.11.0 in /backend [`#2976`](https://github.com/Human-Connection/Human-Connection/pull/2976)
- build(deps): bump metascraper-logo from 5.10.6 to 5.10.7 in /backend [`#2975`](https://github.com/Human-Connection/Human-Connection/pull/2975)
- chore: Add issue template for Refactoring tickets [`#2983`](https://github.com/Human-Connection/Human-Connection/pull/2983)
- build(deps): bump @nuxtjs/sentry from 3.1.0 to 3.2.2 in /webapp [`#2974`](https://github.com/Human-Connection/Human-Connection/pull/2974)
- build(deps): bump metascraper-video from 5.10.6 to 5.10.7 in /backend [`#2952`](https://github.com/Human-Connection/Human-Connection/pull/2952)
- build(deps): bump metascraper-lang from 5.10.6 to 5.10.7 in /backend [`#2950`](https://github.com/Human-Connection/Human-Connection/pull/2950)
- build(deps): bump metascraper-description from 5.10.6 to 5.11.0 in /backend [`#2948`](https://github.com/Human-Connection/Human-Connection/pull/2948)
- build(deps): bump @sentry/node from 5.11.2 to 5.12.0 in /backend [`#2977`](https://github.com/Human-Connection/Human-Connection/pull/2977)
- build(deps): bump @nuxtjs/pwa from 3.0.0-beta.19 to 3.0.0-beta.20 in /webapp [`#2959`](https://github.com/Human-Connection/Human-Connection/pull/2959)
- build(deps-dev): bump @storybook/addon-a11y from 5.3.9 to 5.3.10 in /webapp [`#2956`](https://github.com/Human-Connection/Human-Connection/pull/2956)
- build(deps-dev): bump eslint-plugin-import from 2.20.0 to 2.20.1 in /webapp [`#2949`](https://github.com/Human-Connection/Human-Connection/pull/2949)
- build(deps): bump metascraper-soundcloud from 5.10.6 to 5.10.7 in /backend [`#2945`](https://github.com/Human-Connection/Human-Connection/pull/2945)
- build(deps): bump metascraper-date from 5.10.6 to 5.10.7 in /backend [`#2944`](https://github.com/Human-Connection/Human-Connection/pull/2944)
- build(deps-dev): bump codecov from 3.6.2 to 3.6.4 [`#2943`](https://github.com/Human-Connection/Human-Connection/pull/2943)
- build(deps-dev): bump @storybook/addon-actions in /webapp [`#2953`](https://github.com/Human-Connection/Human-Connection/pull/2953)
- build(deps): bump metascraper-publisher in /backend [`#2954`](https://github.com/Human-Connection/Human-Connection/pull/2954)
- build(deps-dev): bump eslint-plugin-import in /backend [`#2955`](https://github.com/Human-Connection/Human-Connection/pull/2955)
- build(deps): bump metascraper-youtube from 5.10.6 to 5.10.7 in /backend [`#2957`](https://github.com/Human-Connection/Human-Connection/pull/2957)
- build(deps): bump metascraper-image from 5.10.6 to 5.10.7 in /backend [`#2960`](https://github.com/Human-Connection/Human-Connection/pull/2960)
- build(deps-dev): bump @storybook/vue from 5.3.9 to 5.3.10 in /webapp [`#2961`](https://github.com/Human-Connection/Human-Connection/pull/2961)
- build(deps): bump @nuxtjs/axios from 5.9.4 to 5.9.5 in /webapp [`#2962`](https://github.com/Human-Connection/Human-Connection/pull/2962)
- fix: Update mute/unmute icon to unused icon [`#2973`](https://github.com/Human-Connection/Human-Connection/pull/2973)
- fix: Remove github release script breaking build [`#2971`](https://github.com/Human-Connection/Human-Connection/pull/2971)
- Use original createdAt for merged users/emails [`#2969`](https://github.com/Human-Connection/Human-Connection/pull/2969)
- Fix typo [`#2966`](https://github.com/Human-Connection/Human-Connection/pull/2966)
- chore: Update to v0.3.0 [`#2941`](https://github.com/Human-Connection/Human-Connection/pull/2941)
- Replace buildList with array of Promises [`46edc3f`](https://github.com/Human-Connection/Human-Connection/commit/46edc3fdd5b83c2f00506f595b1254d7597767e0)
- build(deps-dev): bump @storybook/addon-notes in /webapp [`75137ce`](https://github.com/Human-Connection/Human-Connection/commit/75137ce716dadcc6f0ceeed6a2b0fe5c50fa7b8f)
- Update to v0.3.0 [`dbe2c4c`](https://github.com/Human-Connection/Human-Connection/commit/dbe2c4cdd5bab2195c6369b84989507b9f7da768)
#### [v0.3.0](https://github.com/Human-Connection/Human-Connection/compare/v0.2.1...v0.3.0) #### [v0.3.0](https://github.com/Human-Connection/Human-Connection/compare/v0.2.1...v0.3.0)
> 31 January 2020 > 31 January 2020
- build(deps-dev): bump @babel/core from 7.8.3 to 7.8.4 in /webapp [`#2939`](https://github.com/Human-Connection/Human-Connection/pull/2939)
- feat: 🍰 Direct Reply On Comment [`#2608`](https://github.com/Human-Connection/Human-Connection/pull/2608) - feat: 🍰 Direct Reply On Comment [`#2608`](https://github.com/Human-Connection/Human-Connection/pull/2608)
- build(deps-dev): bump @babel/core from 7.8.3 to 7.8.4 in /backend [`#2938`](https://github.com/Human-Connection/Human-Connection/pull/2938) - build(deps-dev): bump @babel/core from 7.8.3 to 7.8.4 in /backend [`#2938`](https://github.com/Human-Connection/Human-Connection/pull/2938)
- fix: deploy script with new naming convention [`#2930`](https://github.com/Human-Connection/Human-Connection/pull/2930) - fix: deploy script with new naming convention [`#2930`](https://github.com/Human-Connection/Human-Connection/pull/2930)
@ -181,9 +238,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- refactor(modules): Various import fixes [`#2773`](https://github.com/Human-Connection/Human-Connection/issues/2773) [`#2774`](https://github.com/Human-Connection/Human-Connection/issues/2774) - refactor(modules): Various import fixes [`#2773`](https://github.com/Human-Connection/Human-Connection/issues/2773) [`#2774`](https://github.com/Human-Connection/Human-Connection/issues/2774)
- feat(webapp): Display deployed version in footer [`#1831`](https://github.com/Human-Connection/Human-Connection/issues/1831) - feat(webapp): Display deployed version in footer [`#1831`](https://github.com/Human-Connection/Human-Connection/issues/1831)
- fix #2229 [`#2229`](https://github.com/Human-Connection/Human-Connection/issues/2229) - fix #2229 [`#2229`](https://github.com/Human-Connection/Human-Connection/issues/2229)
- refactor: Make `db:setup` init stage of `migrate` [`b063847`](https://github.com/Human-Connection/Human-Connection/commit/b063847849a84db885337dc8e84e75ddaf87011f) - Get rid of different factory files [`fc36729`](https://github.com/Human-Connection/Human-Connection/commit/fc367297e3e054f09b7f8f31788ab68d87f6babf)
- Improve styling per @alina-beck review [`bcc1ab1`](https://github.com/Human-Connection/Human-Connection/commit/bcc1ab167e8b1dfdac1ec0a05a0c14e8234bcabc) - Refactor factory for comments [`2fc71d7`](https://github.com/Human-Connection/Human-Connection/commit/2fc71d75a5d5eab9c3467e94e00257ef6dd7d8a0)
- test(cypress): Cover "Pinned post" feature [`d49afc2`](https://github.com/Human-Connection/Human-Connection/commit/d49afc25cfa1c1f98ed04f78dd3ff826cd85ae25) - Refactor user factory [`2a79c53`](https://github.com/Human-Connection/Human-Connection/commit/2a79c53765b73f9b91691eb75f55cf8c9e48306e)
#### [v0.2.1](https://github.com/Human-Connection/Human-Connection/compare/v0.2.0...v0.2.1) #### [v0.2.1](https://github.com/Human-Connection/Human-Connection/compare/v0.2.0...v0.2.1)

View File

@ -1,6 +1,6 @@
{ {
"name": "human-connection-backend", "name": "human-connection-backend",
"version": "0.2.2", "version": "0.3.1",
"description": "GraphQL Backend for Human Connection", "description": "GraphQL Backend for Human Connection",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
@ -38,12 +38,12 @@
}, },
"dependencies": { "dependencies": {
"@hapi/joi": "^17.1.0", "@hapi/joi": "^17.1.0",
"@sentry/node": "^5.11.2", "@sentry/node": "^5.12.3",
"apollo-cache-inmemory": "~1.6.5", "apollo-cache-inmemory": "~1.6.5",
"apollo-client": "~2.6.8", "apollo-client": "~2.6.8",
"apollo-link-context": "~1.0.19", "apollo-link-context": "~1.0.19",
"apollo-link-http": "~1.5.16", "apollo-link-http": "~1.5.16",
"apollo-server": "~2.9.16", "apollo-server": "~2.10.0",
"apollo-server-express": "^2.9.16", "apollo-server-express": "^2.9.16",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
"bcryptjs": "~2.4.3", "bcryptjs": "~2.4.3",
@ -60,29 +60,31 @@
"graphql-iso-date": "~3.6.1", "graphql-iso-date": "~3.6.1",
"graphql-middleware": "~4.0.2", "graphql-middleware": "~4.0.2",
"graphql-middleware-sentry": "^3.2.1", "graphql-middleware-sentry": "^3.2.1",
"graphql-shield": "~7.0.9", "graphql-redis-subscriptions": "^2.1.2",
"graphql-tag": "~2.10.1", "graphql-shield": "~7.0.11",
"graphql-tag": "~2.10.3",
"helmet": "~3.21.2", "helmet": "~3.21.2",
"ioredis": "^4.14.1",
"jsonwebtoken": "~8.5.1", "jsonwebtoken": "~8.5.1",
"linkifyjs": "~2.1.8", "linkifyjs": "~2.1.8",
"lodash": "~4.17.14", "lodash": "~4.17.14",
"merge-graphql-schemas": "^1.7.6", "merge-graphql-schemas": "^1.7.6",
"metascraper": "^5.10.6", "metascraper": "^5.11.0",
"metascraper-audio": "^5.10.6", "metascraper-audio": "^5.10.7",
"metascraper-author": "^5.10.6", "metascraper-author": "^5.10.7",
"metascraper-clearbit-logo": "^5.3.0", "metascraper-clearbit-logo": "^5.3.0",
"metascraper-date": "^5.10.6", "metascraper-date": "^5.10.7",
"metascraper-description": "^5.10.6", "metascraper-description": "^5.11.0",
"metascraper-image": "^5.10.6", "metascraper-image": "^5.11.1",
"metascraper-lang": "^5.10.6", "metascraper-lang": "^5.10.7",
"metascraper-lang-detector": "^4.10.2", "metascraper-lang-detector": "^4.10.2",
"metascraper-logo": "^5.10.6", "metascraper-logo": "^5.10.7",
"metascraper-publisher": "^5.10.6", "metascraper-publisher": "^5.10.7",
"metascraper-soundcloud": "^5.10.6", "metascraper-soundcloud": "^5.10.7",
"metascraper-title": "^5.10.6", "metascraper-title": "^5.10.7",
"metascraper-url": "^5.10.6", "metascraper-url": "^5.10.7",
"metascraper-video": "^5.10.6", "metascraper-video": "^5.10.7",
"metascraper-youtube": "^5.10.6", "metascraper-youtube": "^5.10.7",
"migrate": "^1.6.2", "migrate": "^1.6.2",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
"mustache": "^4.0.0", "mustache": "^4.0.0",
@ -93,9 +95,10 @@
"nodemailer": "^6.4.2", "nodemailer": "^6.4.2",
"nodemailer-html-to-text": "^3.1.0", "nodemailer-html-to-text": "^3.1.0",
"npm-run-all": "~4.1.5", "npm-run-all": "~4.1.5",
"request": "~2.88.0", "request": "~2.88.2",
"sanitize-html": "~1.21.1", "sanitize-html": "~1.21.1",
"slug": "~2.1.1", "slug": "~2.1.1",
"subscriptions-transport-ws": "^0.9.16",
"trunc-html": "~1.1.2", "trunc-html": "~1.1.2",
"uuid": "~3.4.0", "uuid": "~3.4.0",
"validator": "^12.2.0", "validator": "^12.2.0",
@ -109,7 +112,7 @@
"@babel/plugin-proposal-throw-expressions": "^7.8.3", "@babel/plugin-proposal-throw-expressions": "^7.8.3",
"@babel/preset-env": "~7.8.4", "@babel/preset-env": "~7.8.4",
"@babel/register": "^7.8.3", "@babel/register": "^7.8.3",
"apollo-server-testing": "~2.9.16", "apollo-server-testing": "~2.10.0",
"babel-core": "~7.0.0-0", "babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.3", "babel-eslint": "~10.0.3",
"babel-jest": "~25.1.0", "babel-jest": "~25.1.0",
@ -118,8 +121,8 @@
"eslint": "~6.8.0", "eslint": "~6.8.0",
"eslint-config-prettier": "~6.10.0", "eslint-config-prettier": "~6.10.0",
"eslint-config-standard": "~14.1.0", "eslint-config-standard": "~14.1.0",
"eslint-plugin-import": "~2.20.0", "eslint-plugin-import": "~2.20.1",
"eslint-plugin-jest": "~23.6.0", "eslint-plugin-jest": "~23.7.0",
"eslint-plugin-node": "~11.0.0", "eslint-plugin-node": "~11.0.0",
"eslint-plugin-prettier": "~3.1.2", "eslint-plugin-prettier": "~3.1.2",
"eslint-plugin-promise": "~4.2.1", "eslint-plugin-promise": "~4.2.1",
@ -127,6 +130,10 @@
"jest": "~25.1.0", "jest": "~25.1.0",
"nodemon": "~2.0.2", "nodemon": "~2.0.2",
"prettier": "~1.19.1", "prettier": "~1.19.1",
"rosie": "^2.0.1",
"supertest": "~4.0.2" "supertest": "~4.0.2"
},
"resolutions": {
"fs-capacitor": "6.0.0"
} }
} }

View File

@ -1,10 +1,9 @@
import { handler } from './webfinger' import { handler } from './webfinger'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { getDriver } from '../../db/neo4j' import { getDriver } from '../../db/neo4j'
let resource, res, json, status, contentType let resource, res, json, status, contentType
const factory = Factory()
const driver = getDriver() const driver = getDriver()
const request = () => { const request = () => {
@ -28,7 +27,7 @@ const request = () => {
} }
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('webfinger', () => { describe('webfinger', () => {
@ -90,7 +89,7 @@ describe('webfinger', () => {
describe('given a user for acct', () => { describe('given a user for acct', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { slug: 'some-user' }) await Factory.build('user', { slug: 'some-user' })
}) })
it('returns user object', async () => { it('returns user object', async () => {

View File

@ -4,6 +4,9 @@ if (require.resolve) {
dotenv.config({ path: require.resolve('../../.env') }) dotenv.config({ path: require.resolve('../../.env') })
} }
// eslint-disable-next-line no-undef
const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env
const { const {
MAPBOX_TOKEN, MAPBOX_TOKEN,
JWT_SECRET, JWT_SECRET,
@ -20,7 +23,10 @@ const {
NEO4J_PASSWORD = 'neo4j', NEO4J_PASSWORD = 'neo4j',
CLIENT_URI = 'http://localhost:3000', CLIENT_URI = 'http://localhost:3000',
GRAPHQL_URI = 'http://localhost:4000', GRAPHQL_URI = 'http://localhost:4000',
} = process.env REDIS_DOMAIN,
REDIS_PORT,
REDIS_PASSWORD,
} = env
export const requiredConfigs = { export const requiredConfigs = {
MAPBOX_TOKEN, MAPBOX_TOKEN,
@ -58,7 +64,7 @@ export const developmentConfigs = {
} }
export const sentryConfigs = { SENTRY_DSN_BACKEND, COMMIT } export const sentryConfigs = { SENTRY_DSN_BACKEND, COMMIT }
export const redisConfiig = { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD }
export default { export default {
...requiredConfigs, ...requiredConfigs,
...smtpConfigs, ...smtpConfigs,
@ -66,4 +72,5 @@ export default {
...serverConfigs, ...serverConfigs,
...developmentConfigs, ...developmentConfigs,
...sentryConfigs, ...sentryConfigs,
...redisConfiig,
} }

View File

@ -1,4 +1,4 @@
import { cleanDatabase } from '../factories' import { cleanDatabase } from '../db/factories'
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
throw new Error(`You cannot clean the database in production environment!`) throw new Error(`You cannot clean the database in production environment!`)

229
backend/src/db/factories.js Normal file
View File

@ -0,0 +1,229 @@
import uuid from 'uuid/v4'
import faker from 'faker'
import slugify from 'slug'
import { hashSync } from 'bcryptjs'
import { Factory } from 'rosie'
import { getDriver, getNeode } from './neo4j'
const neode = getNeode()
export const cleanDatabase = async (options = {}) => {
const { driver = getDriver() } = options
const session = driver.session()
try {
await session.writeTransaction(transaction => {
return transaction.run(
`
MATCH (everything)
DETACH DELETE everything
`,
)
})
} finally {
session.close()
}
}
Factory.define('category')
.attr('id', uuid)
.attr('icon', 'globe')
.attr('name', 'Global Peace & Nonviolence')
.after((buildObject, options) => {
return neode.create('Category', buildObject)
})
Factory.define('badge')
.attr('type', 'crowdfunding')
.attr('status', 'permanent')
.after((buildObject, options) => {
return neode.create('Badge', buildObject)
})
Factory.define('userWithoutEmailAddress')
.option('password', '1234')
.attrs({
id: uuid,
name: faker.name.findName,
password: '1234',
role: 'user',
avatar: faker.internet.avatar,
about: faker.lorem.paragraph,
termsAndConditionsAgreedVersion: '0.0.1',
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
allowEmbedIframes: false,
showShoutsPublicly: false,
locale: 'en',
})
.attr('slug', ['slug', 'name'], (slug, name) => {
return slug || slugify(name, { lower: true })
})
.attr('encryptedPassword', ['password'], password => {
return hashSync(password, 10)
})
.after(async (buildObject, options) => {
return neode.create('User', buildObject)
})
Factory.define('user')
.extend('userWithoutEmailAddress')
.option('email', faker.internet.exampleEmail)
.after(async (buildObject, options) => {
const [user, email] = await Promise.all([
buildObject,
neode.create('EmailAddress', { email: options.email }),
])
await Promise.all([user.relateTo(email, 'primaryEmail'), email.relateTo(user, 'belongsTo')])
return user
})
Factory.define('post')
.option('categoryIds', [])
.option('categories', ['categoryIds'], categoryIds => {
if (categoryIds.length) return Promise.all(categoryIds.map(id => neode.find('Category', id)))
// there must be at least one category
return Promise.all([Factory.build('category')])
})
.option('tagIds', [])
.option('tags', ['tagIds'], tagIds => {
return Promise.all(tagIds.map(id => neode.find('Tag', id)))
})
.option('authorId', null)
.option('author', ['authorId'], authorId => {
if (authorId) return neode.find('User', authorId)
return Factory.build('user')
})
.option('pinnedBy', null)
.attrs({
id: uuid,
title: faker.lorem.sentence,
content: faker.lorem.paragraphs,
image: faker.image.unsplash.imageUrl,
visibility: 'public',
deleted: false,
imageBlurred: false,
imageAspectRatio: 1.333,
})
.attr('pinned', ['pinned'], pinned => {
// Convert false to null
return pinned || null
})
.attr('contentExcerpt', ['contentExcerpt', 'content'], (contentExcerpt, content) => {
return contentExcerpt || content
})
.attr('slug', ['slug', 'title'], (slug, title) => {
return slug || slugify(title, { lower: true })
})
.attr('language', ['language'], language => {
return language || 'en'
})
.after(async (buildObject, options) => {
const [post, author, categories, tags] = await Promise.all([
neode.create('Post', buildObject),
options.author,
options.categories,
options.tags,
])
await Promise.all([
post.relateTo(author, 'author'),
Promise.all(categories.map(c => c.relateTo(post, 'post'))),
Promise.all(tags.map(t => t.relateTo(post, 'post'))),
])
if (buildObject.pinned) {
const pinnedBy = await (options.pinnedBy || Factory.build('user', { role: 'admin' }))
await pinnedBy.relateTo(post, 'pinned')
}
return post
})
Factory.define('comment')
.option('postId', null)
.option('post', ['postId'], postId => {
if (postId) return neode.find('Post', postId)
return Factory.build('post')
})
.option('authorId', null)
.option('author', ['authorId'], authorId => {
if (authorId) return neode.find('User', authorId)
return Factory.build('user')
})
.attrs({
id: uuid,
content: faker.lorem.sentence,
})
.attr('contentExcerpt', ['contentExcerpt', 'content'], (contentExcerpt, content) => {
return contentExcerpt || content
})
.after(async (buildObject, options) => {
const [comment, author, post] = await Promise.all([
neode.create('Comment', buildObject),
options.author,
options.post,
])
await Promise.all([comment.relateTo(author, 'author'), comment.relateTo(post, 'post')])
return comment
})
Factory.define('donations')
.attr('id', uuid)
.attr('goal', 15000)
.attr('progress', 0)
.after((buildObject, options) => {
return neode.create('Donations', buildObject)
})
const emailDefaults = {
email: faker.internet.email,
verifiedAt: () => new Date().toISOString(),
}
Factory.define('emailAddress')
.attr(emailDefaults)
.after((buildObject, options) => {
return neode.create('EmailAddress', buildObject)
})
Factory.define('unverifiedEmailAddress')
.attr(emailDefaults)
.after((buildObject, options) => {
return neode.create('UnverifiedEmailAddress', buildObject)
})
Factory.define('location')
.attrs({
name: 'Germany',
namePT: 'Alemanha',
nameDE: 'Deutschland',
nameES: 'Alemania',
nameNL: 'Duitsland',
namePL: 'Niemcy',
nameFR: 'Allemagne',
nameIT: 'Germania',
nameEN: 'Germany',
id: 'country.10743216036480410',
type: 'country',
})
.after((buildObject, options) => {
return neode.create('Location', buildObject)
})
Factory.define('report').after((buildObject, options) => {
return neode.create('Report', buildObject)
})
Factory.define('tag')
.attrs({
name: '#human-connection',
})
.after((buildObject, options) => {
return neode.create('Tag', buildObject)
})
Factory.define('socialMedia')
.attrs({
url: 'https://mastodon.social/@Gargron',
})
.after((buildObject, options) => {
return neode.create('SocialMedia', buildObject)
})
export default Factory

View File

@ -18,6 +18,7 @@ export async function up(next) {
await transaction.rollback() await transaction.rollback()
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('rolled back') console.log('rolled back')
throw new Error(error)
} finally { } finally {
session.close() session.close()
} }

View File

@ -34,12 +34,11 @@ export function up(next) {
return txc return txc
.run( .run(
` `
MATCH (oldUser:User)-[:PRIMARY_EMAIL]->(oldEmail:EmailAddress {email: $email}), (oldUser)-[previousRelationship]-(oldEmail) MATCH (oldUser:User)-[:PRIMARY_EMAIL]->(oldEmail:EmailAddress {email: $email})
MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email: $normalizedEmail}) MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email: $normalizedEmail})
DELETE previousRelationship
WITH oldUser, oldEmail, user, email WITH oldUser, oldEmail, user, email
CALL apoc.refactor.mergeNodes([user, oldUser], { properties: 'discard', mergeRels: true }) YIELD node as mergedUser CALL apoc.refactor.mergeNodes([user, oldUser], { properties: { createdAt: 'overwrite', \`.*\`: 'discard' }, mergeRels: true }) YIELD node as mergedUser
CALL apoc.refactor.mergeNodes([email, oldEmail], { properties: 'discard', mergeRels: true }) YIELD node as mergedEmail CALL apoc.refactor.mergeNodes([email, oldEmail], { properties: { createdAt: 'overwrite', verifiedAt: 'overwrite', \`.*\`: 'discard' }, mergeRels: true }) YIELD node as mergedEmail
RETURN user {.*}, email {.*} RETURN user {.*}, email {.*}
`, `,
{ email, normalizedEmail }, { email, normalizedEmail },

View File

@ -0,0 +1,42 @@
import { getDriver } from '../../db/neo4j'
export const description = `
This migration swaps the value stored in Location.lat with the value
of Location.lng. This is necessary as the values of lat and lng were
stored incorrectly. For example Hamburg, Germany, was stored with the
values lat=10.0 and lng=53.55, which is close to the horn of Africa,
but it is lat=53.55 and lng=10.0
`
const swap = async function(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// Implement your migration here.
await transaction.run(`
MATCH (l:Location) WHERE NOT(l.lat IS NULL)
WITH l.lng AS longitude, l.lat AS latitude, l AS location
SET location.lat = longitude, location.lng = latitude
`)
await transaction.commit()
next()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
}
}
export async function up(next) {
swap(next)
}
export async function down(next) {
swap(next)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +0,0 @@
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
type: 'crowdfunding',
status: 'permanent',
}
args = {
...defaults,
...args,
}
return neodeInstance.create('Badge', args)
},
}
}

View File

@ -1,18 +0,0 @@
import uuid from 'uuid/v4'
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
id: uuid(),
icon: 'img/badges/fundraisingbox_de_airship.svg',
name: 'Some category name',
}
args = {
...defaults,
...args,
}
return neodeInstance.create('Category', args)
},
}
}

View File

@ -1,38 +0,0 @@
import faker from 'faker'
import uuid from 'uuid/v4'
export default function create() {
return {
factory: async ({ args, neodeInstance, factoryInstance }) => {
const defaults = {
id: uuid(),
content: [faker.lorem.sentence(), faker.lorem.sentence()].join('. '),
}
args = {
...defaults,
...args,
}
args.contentExcerpt = args.contentExcerpt || args.content
let { post, postId } = args
delete args.post
delete args.postId
if (post && postId) throw new Error('You provided both post and postId')
if (postId) post = await neodeInstance.find('Post', postId)
post = post || (await factoryInstance.create('Post'))
let { author, authorId } = args
delete args.author
delete args.authorId
if (author && authorId) throw new Error('You provided both author and authorId')
if (authorId) author = await neodeInstance.find('User', authorId)
author = author || (await factoryInstance.create('User'))
delete args.author
const comment = await neodeInstance.create('Comment', args)
await comment.relateTo(post, 'post')
await comment.relateTo(author, 'author')
return comment
},
}
}

View File

@ -1,18 +0,0 @@
import uuid from 'uuid/v4'
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
id: uuid(),
goal: 15000,
progress: 0,
}
args = {
...defaults,
...args,
}
return neodeInstance.create('Donations', args)
},
}
}

View File

@ -1,22 +0,0 @@
import faker from 'faker'
export function defaults({ args }) {
const defaults = {
email: faker.internet.email(),
verifiedAt: new Date().toISOString(),
}
args = {
...defaults,
...args,
}
return args
}
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
args = defaults({ args })
return neodeInstance.create('EmailAddress', args)
},
}
}

View File

@ -1,63 +0,0 @@
import { getDriver, getNeode } from '../db/neo4j'
const factories = {
Badge: require('./badges.js').default,
User: require('./users.js').default,
Post: require('./posts.js').default,
Comment: require('./comments.js').default,
Category: require('./categories.js').default,
Tag: require('./tags.js').default,
SocialMedia: require('./socialMedia.js').default,
Location: require('./locations.js').default,
EmailAddress: require('./emailAddresses.js').default,
UnverifiedEmailAddress: require('./unverifiedEmailAddresses.js').default,
Donations: require('./donations.js').default,
Report: require('./reports.js').default,
}
export const cleanDatabase = async (options = {}) => {
const { driver = getDriver() } = options
const session = driver.session()
try {
await session.writeTransaction(transaction => {
return transaction.run(
`
MATCH (everything)
DETACH DELETE everything
`,
)
})
} finally {
session.close()
}
}
export default function Factory(options = {}) {
const { neo4jDriver = getDriver(), neodeInstance = getNeode() } = options
const result = {
neo4jDriver,
factories,
lastResponse: null,
neodeInstance,
async create(node, args = {}) {
const { factory } = this.factories[node](args)
this.lastResponse = await factory({
args,
neodeInstance,
factoryInstance: this,
})
return this.lastResponse
},
async cleanDatabase() {
this.lastResponse = await cleanDatabase({
driver: this.neo4jDriver,
})
return this
},
}
result.create.bind(result)
result.cleanDatabase.bind(result)
return result
}

View File

@ -1,24 +0,0 @@
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
name: 'Germany',
namePT: 'Alemanha',
nameDE: 'Deutschland',
nameES: 'Alemania',
nameNL: 'Duitsland',
namePL: 'Niemcy',
nameFR: 'Allemagne',
nameIT: 'Germania',
nameEN: 'Germany',
id: 'country.10743216036480410',
type: 'country',
}
args = {
...defaults,
...args,
}
return neodeInstance.create('Location', args)
},
}
}

View File

@ -1,89 +0,0 @@
import faker from 'faker'
import slugify from 'slug'
import uuid from 'uuid/v4'
export default function create() {
return {
factory: async ({ args, neodeInstance, factoryInstance }) => {
const defaults = {
id: uuid(),
title: faker.lorem.sentence(),
content: [
faker.lorem.sentence(),
faker.lorem.sentence(),
faker.lorem.sentence(),
faker.lorem.sentence(),
faker.lorem.sentence(),
].join('. '),
image: faker.image.unsplash.imageUrl(),
visibility: 'public',
deleted: false,
categoryIds: [],
imageBlurred: false,
imageAspectRatio: 1.333,
pinned: null,
}
args = {
...defaults,
...args,
}
// Convert false to null
args.pinned = args.pinned || null
args.slug = args.slug || slugify(args.title, { lower: true })
args.contentExcerpt = args.contentExcerpt || args.content
let { categories, categoryIds } = args
delete args.categories
delete args.categoryIds
if (categories && categoryIds) throw new Error('You provided both categories and categoryIds')
if (categoryIds)
categories = await Promise.all(categoryIds.map(id => neodeInstance.find('Category', id)))
categories = categories || (await Promise.all([factoryInstance.create('Category')]))
const { tagIds = [] } = args
delete args.tags
const tags = await Promise.all(
tagIds.map(t => {
return neodeInstance.find('Tag', t)
}),
)
let { author, authorId } = args
delete args.author
delete args.authorId
if (author && authorId) throw new Error('You provided both author and authorId')
if (authorId) author = await neodeInstance.find('User', authorId)
author = author || (await factoryInstance.create('User'))
const post = await neodeInstance.create('Post', args)
const { commentContent } = args
let comment
delete args.commentContent
if (commentContent)
comment = await factoryInstance.create('Comment', {
contentExcerpt: commentContent,
post,
author,
})
await post.relateTo(author, 'author')
if (comment) await post.relateTo(comment, 'comments')
if (args.pinned) {
args.pinnedAt = args.pinnedAt || new Date().toISOString()
if (!args.pinnedBy) {
const admin = await factoryInstance.create('User', {
role: 'admin',
updatedAt: new Date().toISOString(),
})
await admin.relateTo(post, 'pinned')
args.pinnedBy = admin
}
}
await Promise.all(categories.map(c => c.relateTo(post, 'post')))
await Promise.all(tags.map(t => t.relateTo(post, 'post')))
return post
},
}
}

View File

@ -1,7 +0,0 @@
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
return neodeInstance.create('Report', args)
},
}
}

View File

@ -1,14 +0,0 @@
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
url: 'https://mastodon.social/@Gargron',
}
args = {
...defaults,
...args,
}
return neodeInstance.create('SocialMedia', args)
},
}
}

View File

@ -1,12 +0,0 @@
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = { name: '#human-connection' }
args = {
...defaults,
...args,
}
return neodeInstance.create('Tag', args)
},
}
}

View File

@ -1,10 +0,0 @@
import { defaults } from './emailAddresses.js'
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
args = defaults({ args })
return neodeInstance.create('UnverifiedEmailAddress', args)
},
}
}

View File

@ -1,44 +0,0 @@
import faker from 'faker'
import uuid from 'uuid/v4'
import encryptPassword from '../helpers/encryptPassword'
import slugify from 'slug'
export default function create() {
return {
factory: async ({ args, neodeInstance, factoryInstance }) => {
const defaults = {
id: uuid(),
name: faker.name.findName(),
email: faker.internet.email(),
password: '1234',
role: 'user',
avatar: faker.internet.avatar(),
about: faker.lorem.paragraph(),
termsAndConditionsAgreedVersion: '0.0.1',
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
allowEmbedIframes: false,
showShoutsPublicly: false,
locale: 'en',
}
defaults.slug = slugify(defaults.name, { lower: true })
args = {
...defaults,
...args,
}
args = await encryptPassword(args)
const user = await neodeInstance.create('User', args)
let email
if (typeof args.email === 'object') {
// probably a neode node
email = args.email
} else {
email = await factoryInstance.create('EmailAddress', { email: args.email })
}
await user.relateTo(email, 'primaryEmail')
await email.relateTo(user, 'belongsTo')
return user
},
}
}

View File

@ -1,9 +1,11 @@
import createServer from './server' import createServer from './server'
import CONFIG from './config' import CONFIG from './config'
const { app } = createServer() const { server, httpServer } = createServer()
const url = new URL(CONFIG.GRAPHQL_URI) const url = new URL(CONFIG.GRAPHQL_URI)
app.listen({ port: url.port }, () => { httpServer.listen({ port: url.port }, () => {
/* eslint-disable-next-line no-console */ /* eslint-disable-next-line no-console */
console.log(`GraphQLServer ready at ${CONFIG.GRAPHQL_URI} 🚀`) console.log(`🚀 Server ready at http://localhost:${url.port}${server.graphqlPath}`)
/* eslint-disable-next-line no-console */
console.log(`🚀 Subscriptions ready at ws://localhost:${url.port}${server.subscriptionsPath}`)
}) })

View File

@ -1,8 +1,7 @@
import Factory from '../factories/index' import Factory, { cleanDatabase } from '../db/factories'
import { getDriver, getNeode } from '../db/neo4j' import { getDriver, getNeode } from '../db/neo4j'
import decode from './decode' import decode from './decode'
const factory = Factory()
const driver = getDriver() const driver = getDriver()
const neode = getNeode() const neode = getNeode()
@ -26,7 +25,7 @@ export const validAuthorizationHeader =
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc' 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc'
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('decode', () => { describe('decode', () => {
@ -65,14 +64,19 @@ describe('decode', () => {
describe('and corresponding user in the database', () => { describe('and corresponding user in the database', () => {
let user let user
beforeEach(async () => { beforeEach(async () => {
user = await factory.create('User', { user = await Factory.build(
role: 'user', 'user',
name: 'Jenny Rostock', {
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sasha_shestakov/128.jpg', role: 'user',
id: 'u3', name: 'Jenny Rostock',
email: 'user@example.org', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sasha_shestakov/128.jpg',
slug: 'jenny-rostock', id: 'u3',
}) slug: 'jenny-rostock',
},
{
email: 'user@example.org',
},
)
}) })
it('returns user object except email', async () => { it('returns user object except email', async () => {

View File

@ -3,14 +3,12 @@ import CONFIG from './../config'
// Generate an Access Token for the given User ID // Generate an Access Token for the given User ID
export default function encode(user) { export default function encode(user) {
const token = jwt.sign(user, CONFIG.JWT_SECRET, { const { id, name, slug } = user
const token = jwt.sign({ id, name, slug }, CONFIG.JWT_SECRET, {
expiresIn: '1d', expiresIn: '1d',
issuer: CONFIG.GRAPHQL_URI, issuer: CONFIG.GRAPHQL_URI,
audience: CONFIG.CLIENT_URI, audience: CONFIG.CLIENT_URI,
subject: user.id.toString(), subject: user.id.toString(),
}) })
// jwt.verifySignature(token, CONFIG.JWT_SECRET, (err, data) => {
// console.log('token verification:', err, data)
// })
return token return token
} }

View File

@ -0,0 +1,62 @@
import encode from './encode'
import jwt from 'jsonwebtoken'
import CONFIG from './../config'
describe('encode', () => {
let payload
beforeEach(() => {
payload = {
name: 'Some body',
slug: 'some-body',
id: 'some-id',
}
})
it('encodes a valided JWT bearer token', () => {
const token = encode(payload)
expect(token.split('.')).toHaveLength(3)
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
expect(decoded).toEqual({
name: 'Some body',
slug: 'some-body',
id: 'some-id',
sub: 'some-id',
aud: expect.any(String),
iss: expect.any(String),
iat: expect.any(Number),
exp: expect.any(Number),
})
})
describe('given sensitive data', () => {
beforeEach(() => {
payload = {
...payload,
email: 'none-of-your-business@example.org',
password: 'topsecret',
}
})
it('does not encode sensitive data', () => {
const token = encode(payload)
expect(payload).toEqual({
email: 'none-of-your-business@example.org',
password: 'topsecret',
name: 'Some body',
slug: 'some-body',
id: 'some-id',
})
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
expect(decoded).toEqual({
name: 'Some body',
slug: 'some-body',
id: 'some-id',
sub: 'some-id',
aud: expect.any(String),
iss: expect.any(String),
iat: expect.any(Number),
exp: expect.any(Number),
})
})
})
})

View File

@ -1,5 +1,5 @@
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import Factory from '../../factories' import { cleanDatabase } from '../../db/factories'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
@ -9,7 +9,6 @@ let query
let mutate let mutate
let hashtagingUser let hashtagingUser
let authenticatedUser let authenticatedUser
const factory = Factory()
const driver = getDriver() const driver = getDriver()
const neode = getNeode() const neode = getNeode()
const categoryIds = ['cat9'] const categoryIds = ['cat9']
@ -48,13 +47,18 @@ beforeAll(() => {
}) })
beforeEach(async () => { beforeEach(async () => {
hashtagingUser = await neode.create('User', { hashtagingUser = await neode.create(
id: 'you', 'User',
name: 'Al Capone', {
slug: 'al-capone', id: 'you',
email: 'test@example.org', name: 'Al Capone',
password: '1234', slug: 'al-capone',
}) },
{
password: '1234',
email: 'test@example.org',
},
)
await neode.create('Category', { await neode.create('Category', {
id: 'cat9', id: 'cat9',
name: 'Democracy & Politics', name: 'Democracy & Politics',
@ -63,7 +67,7 @@ beforeEach(async () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('hashtags', () => { describe('hashtags', () => {

View File

@ -1,5 +1,6 @@
import extractMentionedUsers from './mentions/extractMentionedUsers' import extractMentionedUsers from './mentions/extractMentionedUsers'
import { validateNotifyUsers } from '../validation/validationMiddleware' import { validateNotifyUsers } from '../validation/validationMiddleware'
import { pubsub, NOTIFICATION_ADDED } from '../../server'
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
const idsOfUsers = extractMentionedUsers(args.content) const idsOfUsers = extractMentionedUsers(args.content)
@ -52,34 +53,48 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
WHERE user.id in $idsOfUsers WHERE user.id in $idsOfUsers
AND NOT (user)-[:BLOCKED]-(author) AND NOT (user)-[:BLOCKED]-(author)
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user) MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
WITH post AS resource, notification, user
` `
break break
} }
case 'mentioned_in_comment': { case 'mentioned_in_comment': {
mentionedCypher = ` mentionedCypher = `
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(commenter: User)
MATCH (user: User) MATCH (user: User)
WHERE user.id in $idsOfUsers WHERE user.id in $idsOfUsers
AND NOT (user)-[:BLOCKED]-(author) AND NOT (user)-[:BLOCKED]-(commenter)
AND NOT (user)-[:BLOCKED]-(postAuthor) AND NOT (user)-[:BLOCKED]-(postAuthor)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user) MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
WITH comment AS resource, notification, user
` `
break break
} }
} }
mentionedCypher += ` mentionedCypher += `
WITH notification, user, resource,
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
SET notification.read = FALSE SET notification.read = FALSE
SET ( SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
CASE
WHEN notification.createdAt IS NULL
THEN notification END ).createdAt = toString(datetime())
SET notification.updatedAt = toString(datetime()) SET notification.updatedAt = toString(datetime())
RETURN notification {.*, from: finalResource, to: properties(user)}
` `
const session = context.driver.session() const session = context.driver.session()
try { const writeTxResultPromise = session.writeTransaction(async transaction => {
await session.writeTransaction(transaction => { const notificationTransactionResponse = await transaction.run(mentionedCypher, {
return transaction.run(mentionedCypher, { id, idsOfUsers, reason }) id,
idsOfUsers,
reason,
}) })
return notificationTransactionResponse.records.map(record => record.get('notification'))
})
try {
const [notification] = await writeTxResultPromise
return pubsub.publish(NOTIFICATION_ADDED, { notificationAdded: notification })
} catch (error) {
throw new Error(error)
} finally { } finally {
session.close() session.close()
} }
@ -88,24 +103,26 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => { const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
await validateNotifyUsers(label, reason) await validateNotifyUsers(label, reason)
const session = context.driver.session() const session = context.driver.session()
const writeTxResultPromise = await session.writeTransaction(async transaction => {
const notificationTransactionResponse = await transaction.run(
`
MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
WHERE NOT (postAuthor)-[:BLOCKED]-(commenter)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor)
SET notification.read = FALSE
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
SET notification.updatedAt = toString(datetime())
WITH notification, postAuthor, post,
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
RETURN notification {.*, from: finalResource, to: properties(postAuthor)}
`,
{ commentId, postAuthorId, reason },
)
return notificationTransactionResponse.records.map(record => record.get('notification'))
})
try { try {
await session.writeTransaction(async transaction => { const [notification] = await writeTxResultPromise
await transaction.run( return pubsub.publish(NOTIFICATION_ADDED, { notificationAdded: notification })
`
MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
WHERE NOT (postAuthor)-[:BLOCKED]-(commenter)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor)
SET notification.read = FALSE
SET (
CASE
WHEN notification.createdAt IS NULL
THEN notification END ).createdAt = toString(datetime())
SET notification.updatedAt = toString(datetime())
`,
{ commentId, postAuthorId, reason },
)
})
} finally { } finally {
session.close() session.close()
} }

View File

@ -1,11 +1,10 @@
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import Factory from '../../factories' import { cleanDatabase } from '../../db/factories'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
let server, query, mutate, notifiedUser, authenticatedUser let server, query, mutate, notifiedUser, authenticatedUser
const factory = Factory()
const driver = getDriver() const driver = getDriver()
const neode = getNeode() const neode = getNeode()
const categoryIds = ['cat9'] const categoryIds = ['cat9']
@ -36,7 +35,7 @@ const createCommentMutation = gql`
` `
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
const createServerResult = createServer({ const createServerResult = createServer({
context: () => { context: () => {
return { return {
@ -53,13 +52,18 @@ beforeAll(async () => {
}) })
beforeEach(async () => { beforeEach(async () => {
notifiedUser = await neode.create('User', { notifiedUser = await neode.create(
id: 'you', 'User',
name: 'Al Capone', {
slug: 'al-capone', id: 'you',
email: 'test@example.org', name: 'Al Capone',
password: '1234', slug: 'al-capone',
}) },
{
email: 'test@example.org',
password: '1234',
},
)
await neode.create('Category', { await neode.create('Category', {
id: 'cat9', id: 'cat9',
name: 'Democracy & Politics', name: 'Democracy & Politics',
@ -68,7 +72,7 @@ beforeEach(async () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('notifications', () => { describe('notifications', () => {
@ -143,13 +147,18 @@ describe('notifications', () => {
describe('commenter is not me', () => { describe('commenter is not me', () => {
beforeEach(async () => { beforeEach(async () => {
commentContent = 'Commenters comment.' commentContent = 'Commenters comment.'
commentAuthor = await neode.create('User', { commentAuthor = await neode.create(
id: 'commentAuthor', 'User',
name: 'Mrs Comment', {
slug: 'mrs-comment', id: 'commentAuthor',
email: 'commentauthor@example.org', name: 'Mrs Comment',
password: '1234', slug: 'mrs-comment',
}) },
{
email: 'commentauthor@example.org',
password: '1234',
},
)
}) })
it('sends me a notification', async () => { it('sends me a notification', async () => {
@ -224,13 +233,18 @@ describe('notifications', () => {
}) })
beforeEach(async () => { beforeEach(async () => {
postAuthor = await neode.create('User', { postAuthor = await neode.create(
id: 'postAuthor', 'User',
name: 'Mrs Post', {
slug: 'mrs-post', id: 'postAuthor',
email: 'post-author@example.org', name: 'Mrs Post',
password: '1234', slug: 'mrs-post',
}) },
{
email: 'post-author@example.org',
password: '1234',
},
)
}) })
describe('mentions me in a post', () => { describe('mentions me in a post', () => {
@ -428,23 +442,33 @@ describe('notifications', () => {
beforeEach(async () => { beforeEach(async () => {
commentContent = commentContent =
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.' 'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
commentAuthor = await neode.create('User', { commentAuthor = await neode.create(
id: 'commentAuthor', 'User',
name: 'Mrs Comment', {
slug: 'mrs-comment', id: 'commentAuthor',
email: 'comment-author@example.org', name: 'Mrs Comment',
password: '1234', slug: 'mrs-comment',
}) },
{
email: 'comment-author@example.org',
password: '1234',
},
)
}) })
it('sends only one notification with reason mentioned_in_comment', async () => { it('sends only one notification with reason mentioned_in_comment', async () => {
postAuthor = await neode.create('User', { postAuthor = await neode.create(
id: 'MrPostAuthor', 'User',
name: 'Mr Author', {
slug: 'mr-author', id: 'MrPostAuthor',
email: 'post-author@example.org', name: 'Mr Author',
password: '1234', slug: 'mr-author',
}) },
{
email: 'post-author@example.org',
password: '1234',
},
)
await createCommentOnPostAction() await createCommentOnPostAction()
const expected = expect.objectContaining({ const expected = expect.objectContaining({
@ -514,13 +538,18 @@ describe('notifications', () => {
await postAuthor.relateTo(notifiedUser, 'blocked') await postAuthor.relateTo(notifiedUser, 'blocked')
commentContent = commentContent =
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.' 'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
commentAuthor = await neode.create('User', { commentAuthor = await neode.create(
id: 'commentAuthor', 'User',
name: 'Mrs Comment', {
slug: 'mrs-comment', id: 'commentAuthor',
email: 'comment-author@example.org', name: 'Mrs Comment',
password: '1234', slug: 'mrs-comment',
}) },
{
email: 'comment-author@example.org',
password: '1234',
},
)
}) })
it('sends no notification', async () => { it('sends no notification', async () => {

View File

@ -1,10 +1,9 @@
import { gql } from '../helpers/jest' import { gql } from '../helpers/jest'
import Factory from '../factories' import { cleanDatabase } from '../db/factories'
import { getNeode, getDriver } from '../db/neo4j' import { getNeode, getDriver } from '../db/neo4j'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../server' import createServer from '../server'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
const driver = getDriver() const driver = getDriver()
@ -27,7 +26,7 @@ beforeEach(async () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('Query', () => { describe('Query', () => {

View File

@ -1,10 +1,9 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../server' import createServer from '../server'
import Factory from '../factories' import Factory, { cleanDatabase } from '../db/factories'
import { gql } from '../helpers/jest' import { gql } from '../helpers/jest'
import { getDriver, getNeode } from '../db/neo4j' import { getDriver, getNeode } from '../db/neo4j'
const factory = Factory()
const instance = getNeode() const instance = getNeode()
const driver = getDriver() const driver = getDriver()
@ -20,7 +19,7 @@ const userQuery = gql`
describe('authorization', () => { describe('authorization', () => {
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
const { server } = createServer({ const { server } = createServer({
context: () => ({ context: () => ({
driver, driver,
@ -34,34 +33,54 @@ describe('authorization', () => {
describe('given two existing users', () => { describe('given two existing users', () => {
beforeEach(async () => { beforeEach(async () => {
;[owner, anotherRegularUser, administrator, moderator] = await Promise.all([ ;[owner, anotherRegularUser, administrator, moderator] = await Promise.all([
factory.create('User', { Factory.build(
email: 'owner@example.org', 'user',
name: 'Owner', {
password: 'iamtheowner', name: 'Owner',
}), },
factory.create('User', { {
email: 'another.regular.user@example.org', email: 'owner@example.org',
name: 'Another Regular User', password: 'iamtheowner',
password: 'else', },
}), ),
factory.create('User', { Factory.build(
email: 'admin@example.org', 'user',
name: 'Admin', {
password: 'admin', name: 'Another Regular User',
role: 'admin', },
}), {
factory.create('User', { email: 'another.regular.user@example.org',
email: 'moderator@example.org', password: 'else',
name: 'Moderator', },
password: 'moderator', ),
role: 'moderator', Factory.build(
}), 'user',
{
name: 'Admin',
role: 'admin',
},
{
email: 'admin@example.org',
password: 'admin',
},
),
Factory.build(
'user',
{
name: 'Moderator',
role: 'moderator',
},
{
email: 'moderator@example.org',
password: 'moderator',
},
),
]) ])
variables = {} variables = {}
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('access email address', () => { describe('access email address', () => {

View File

@ -1,11 +1,9 @@
import Factory from '../factories' import Factory, { cleanDatabase } from '../db/factories'
import { gql } from '../helpers/jest' import { gql } from '../helpers/jest'
import { getNeode, getDriver } from '../db/neo4j' import { getNeode, getDriver } from '../db/neo4j'
import createServer from '../server' import createServer from '../server'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
let mutate let mutate
let authenticatedUser let authenticatedUser
let variables let variables
@ -28,14 +26,18 @@ beforeAll(() => {
beforeEach(async () => { beforeEach(async () => {
variables = {} variables = {}
const admin = await factory.create('User', { const admin = await Factory.build('user', {
role: 'admin', role: 'admin',
}) })
await factory.create('User', { await Factory.build(
email: 'someone@example.org', 'user',
password: '1234', {},
}) {
await factory.create('Category', { email: 'someone@example.org',
password: '1234',
},
)
await Factory.build('category', {
id: 'cat9', id: 'cat9',
name: 'Democracy & Politics', name: 'Democracy & Politics',
icon: 'university', icon: 'university',
@ -44,7 +46,7 @@ beforeEach(async () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('slugifyMiddleware', () => { describe('slugifyMiddleware', () => {
@ -84,12 +86,17 @@ describe('slugifyMiddleware', () => {
describe('if slug exists', () => { describe('if slug exists', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Post', { await Factory.build(
title: 'Pre-existing post', 'post',
slug: 'pre-existing-post', {
content: 'as Someone else content', title: 'Pre-existing post',
categoryIds, slug: 'pre-existing-post',
}) content: 'as Someone else content',
},
{
categoryIds,
},
)
}) })
it('chooses another slug', async () => { it('chooses another slug', async () => {
@ -190,7 +197,7 @@ describe('slugifyMiddleware', () => {
describe('given a user has signed up with their email address', () => { describe('given a user has signed up with their email address', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('EmailAddress', { await Factory.build('emailAddress', {
email: '123@example.org', email: '123@example.org',
nonce: '123456', nonce: '123456',
verifiedAt: null, verifiedAt: null,
@ -214,7 +221,7 @@ describe('slugifyMiddleware', () => {
describe('if slug exists', () => { describe('if slug exists', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { await Factory.build('user', {
name: 'I am a user', name: 'I am a user',
slug: 'i-am-a-user', slug: 'i-am-a-user',
}) })

View File

@ -1,10 +1,9 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
const driver = getDriver() const driver = getDriver()
@ -18,13 +17,18 @@ const action = () => {
beforeAll(async () => { beforeAll(async () => {
// For performance reasons we do this only once // For performance reasons we do this only once
const users = await Promise.all([ const users = await Promise.all([
factory.create('User', { id: 'u1', role: 'user' }), Factory.build('user', { id: 'u1', role: 'user' }),
factory.create('User', { Factory.build(
id: 'm1', 'user',
role: 'moderator', {
password: '1234', id: 'm1',
}), role: 'moderator',
factory.create('User', { },
{
password: '1234',
},
),
Factory.build('user', {
id: 'u2', id: 'u2',
role: 'user', role: 'user',
name: 'Offensive Name', name: 'Offensive Name',
@ -45,48 +49,73 @@ beforeAll(async () => {
await Promise.all([ await Promise.all([
user.relateTo(troll, 'following'), user.relateTo(troll, 'following'),
factory.create('Post', { Factory.build(
author: user, 'post',
id: 'p1', {
title: 'Deleted post', id: 'p1',
slug: 'deleted-post', title: 'Deleted post',
deleted: true, slug: 'deleted-post',
categoryIds, deleted: true,
}), },
factory.create('Post', { {
author: user, author: user,
id: 'p3', categoryIds,
title: 'Publicly visible post', },
slug: 'publicly-visible-post', ),
deleted: false, Factory.build(
categoryIds, 'post',
}), {
id: 'p3',
title: 'Publicly visible post',
slug: 'publicly-visible-post',
deleted: false,
},
{
author: user,
categoryIds,
},
),
]) ])
const resources = await Promise.all([ const resources = await Promise.all([
factory.create('Comment', { Factory.build(
author: user, 'comment',
id: 'c2', {
postId: 'p3', id: 'c2',
content: 'Enabled comment on public post', content: 'Enabled comment on public post',
}), },
factory.create('Post', { {
id: 'p2', author: user,
author: troll, postId: 'p3',
title: 'Disabled post', },
content: 'This is an offensive post content', ),
contentExcerpt: 'This is an offensive post content', Factory.build(
image: '/some/offensive/image.jpg', 'post',
deleted: false, {
categoryIds, id: 'p2',
}), title: 'Disabled post',
factory.create('Comment', { content: 'This is an offensive post content',
id: 'c1', contentExcerpt: 'This is an offensive post content',
author: troll, image: '/some/offensive/image.jpg',
postId: 'p3', deleted: false,
content: 'Disabled comment', },
contentExcerpt: 'Disabled comment', {
}), author: troll,
categoryIds,
},
),
Factory.build(
'comment',
{
id: 'c1',
content: 'Disabled comment',
contentExcerpt: 'Disabled comment',
},
{
author: troll,
postId: 'p3',
},
),
]) ])
const { server } = createServer({ const { server } = createServer({
@ -105,9 +134,9 @@ beforeAll(async () => {
const trollingComment = resources[2] const trollingComment = resources[2]
const reports = await Promise.all([ const reports = await Promise.all([
factory.create('Report'), Factory.build('report'),
factory.create('Report'), Factory.build('report'),
factory.create('Report'), Factory.build('report'),
]) ])
const reportAgainstTroll = reports[0] const reportAgainstTroll = reports[0]
const reportAgainstTrollingPost = reports[1] const reportAgainstTrollingPost = reports[1]
@ -154,7 +183,7 @@ beforeAll(async () => {
}) })
afterAll(async () => { afterAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('softDeleteMiddleware', () => { describe('softDeleteMiddleware', () => {

View File

@ -1,10 +1,9 @@
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../../server' import createServer from '../../server'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
const driver = getDriver() const driver = getDriver()
let authenticatedUser, let authenticatedUser,
@ -94,14 +93,14 @@ beforeAll(() => {
beforeEach(async () => { beforeEach(async () => {
users = await Promise.all([ users = await Promise.all([
factory.create('User', { Factory.build('user', {
id: 'reporting-user', id: 'reporting-user',
}), }),
factory.create('User', { Factory.build('user', {
id: 'moderating-user', id: 'moderating-user',
role: 'moderator', role: 'moderator',
}), }),
factory.create('User', { Factory.build('user', {
id: 'commenting-user', id: 'commenting-user',
}), }),
]) ])
@ -119,20 +118,30 @@ beforeEach(async () => {
moderatingUser = users[1] moderatingUser = users[1]
commentingUser = users[2] commentingUser = users[2]
const posts = await Promise.all([ const posts = await Promise.all([
factory.create('Post', { Factory.build(
id: 'offensive-post', 'post',
authorId: 'moderating-user', {
}), id: 'offensive-post',
factory.create('Post', { },
id: 'post-4-commenting', {
authorId: 'commenting-user', authorId: 'moderating-user',
}), },
),
Factory.build(
'post',
{
id: 'post-4-commenting',
},
{
authorId: 'commenting-user',
},
),
]) ])
offensivePost = posts[0] offensivePost = posts[0]
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('validateCreateComment', () => { describe('validateCreateComment', () => {
@ -182,10 +191,15 @@ describe('validateCreateComment', () => {
describe('validateUpdateComment', () => { describe('validateUpdateComment', () => {
let updateCommentVariables let updateCommentVariables
beforeEach(async () => { beforeEach(async () => {
await factory.create('Comment', { await Factory.build(
id: 'comment-id', 'comment',
authorId: 'commenting-user', {
}) id: 'comment-id',
},
{
authorId: 'commenting-user',
},
)
updateCommentVariables = { updateCommentVariables = {
id: 'whatever', id: 'whatever',
content: '', content: '',
@ -328,7 +342,7 @@ describe('validateReport', () => {
describe('validateReview', () => { describe('validateReview', () => {
beforeEach(async () => { beforeEach(async () => {
const reportAgainstModerator = await factory.create('Report') const reportAgainstModerator = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstModerator.relateTo(reportingUser, 'filed', { reportAgainstModerator.relateTo(reportingUser, 'filed', {
...reportVariables, ...reportVariables,
@ -370,7 +384,7 @@ describe('validateReview', () => {
}) })
it('throws an error if a moderator tries to review their own resource(Post|Comment)', async () => { it('throws an error if a moderator tries to review their own resource(Post|Comment)', async () => {
const reportAgainstOffensivePost = await factory.create('Report') const reportAgainstOffensivePost = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstOffensivePost.relateTo(reportingUser, 'filed', { reportAgainstOffensivePost.relateTo(reportingUser, 'filed', {
...reportVariables, ...reportVariables,
@ -389,7 +403,7 @@ describe('validateReview', () => {
describe('moderate a resource that is not a (Comment|Post|User) ', () => { describe('moderate a resource that is not a (Comment|Post|User) ', () => {
beforeEach(async () => { beforeEach(async () => {
await Promise.all([factory.create('Tag', { id: 'tag-id' })]) await Promise.all([Factory.build('tag', { id: 'tag-id' })])
}) })
it('returns null', async () => { it('returns null', async () => {
@ -419,7 +433,7 @@ describe('validateReview', () => {
id: 'updating-user', id: 'updating-user',
name: 'John Doughnut', name: 'John Doughnut',
} }
updatingUser = await factory.create('User', userParams) updatingUser = await Factory.build('user', userParams)
authenticatedUser = await updatingUser.toJson() authenticatedUser = await updatingUser.toJson()
}) })

View File

@ -52,5 +52,4 @@ export default {
}, },
}, },
pinned: { type: 'boolean', default: null, valid: [null, true] }, pinned: { type: 'boolean', default: null, valid: [null, true] },
pinnedAt: { type: 'string', isoDate: true },
} }

View File

@ -1,11 +1,10 @@
import Factory from '../factories' import { cleanDatabase } from '../db/factories'
import { getNeode } from '../db/neo4j' import { getNeode } from '../db/neo4j'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('role', () => { describe('role', () => {

View File

@ -1,4 +1,4 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../../server' import createServer from '../../server'
@ -6,12 +6,11 @@ import { getNeode, getDriver } from '../../db/neo4j'
const driver = getDriver() const driver = getDriver()
const neode = getNeode() const neode = getNeode()
const factory = Factory()
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
const { server } = createServer({ const { server } = createServer({
context: () => { context: () => {
return { return {
@ -33,7 +32,7 @@ beforeEach(async () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
const createCommentMutation = gql` const createCommentMutation = gql`
@ -48,18 +47,28 @@ const createCommentMutation = gql`
} }
` `
const setupPostAndComment = async () => { const setupPostAndComment = async () => {
commentAuthor = await factory.create('User') commentAuthor = await Factory.build('user')
await factory.create('Post', { await Factory.build(
id: 'p1', 'post',
content: 'Post to be commented', {
categoryIds: ['cat9'], id: 'p1',
}) content: 'Post to be commented',
newlyCreatedComment = await factory.create('Comment', { },
id: 'c456', {
postId: 'p1', categoryIds: ['cat9'],
author: commentAuthor, },
content: 'Comment to be deleted', )
}) newlyCreatedComment = await Factory.build(
'comment',
{
id: 'c456',
content: 'Comment to be deleted',
},
{
postId: 'p1',
author: commentAuthor,
},
)
variables = { variables = {
...variables, ...variables,
id: 'c456', id: 'c456',
@ -88,7 +97,7 @@ describe('CreateComment', () => {
describe('given a post', () => { describe('given a post', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Post', { categoryIds: ['cat9'], id: 'p1' }) await Factory.build('post', { id: 'p1' }, { categoryIds: ['cat9'] })
variables = { variables = {
...variables, ...variables,
postId: 'p1', postId: 'p1',
@ -141,7 +150,7 @@ describe('UpdateComment', () => {
describe('authenticated but not the author', () => { describe('authenticated but not the author', () => {
beforeEach(async () => { beforeEach(async () => {
const randomGuy = await factory.create('User') const randomGuy = await Factory.build('user')
authenticatedUser = await randomGuy.toJson() authenticatedUser = await randomGuy.toJson()
}) })
@ -233,7 +242,7 @@ describe('DeleteComment', () => {
describe('authenticated but not the author', () => { describe('authenticated but not the author', () => {
beforeEach(async () => { beforeEach(async () => {
const randomGuy = await factory.create('User') const randomGuy = await Factory.build('user')
authenticatedUser = await randomGuy.toJson() authenticatedUser = await randomGuy.toJson()
}) })

View File

@ -1,11 +1,10 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
let mutate, query, authenticatedUser, variables let mutate, query, authenticatedUser, variables
const factory = Factory()
const instance = getNeode() const instance = getNeode()
const driver = getDriver() const driver = getDriver()
@ -33,7 +32,7 @@ const donationsQuery = gql`
describe('donations', () => { describe('donations', () => {
let currentUser, newlyCreatedDonations let currentUser, newlyCreatedDonations
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
authenticatedUser = undefined authenticatedUser = undefined
const { server } = createServer({ const { server } = createServer({
context: () => { context: () => {
@ -50,11 +49,11 @@ describe('donations', () => {
beforeEach(async () => { beforeEach(async () => {
variables = {} variables = {}
newlyCreatedDonations = await factory.create('Donations') newlyCreatedDonations = await Factory.build('donations')
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('query for donations', () => { describe('query for donations', () => {
@ -68,7 +67,7 @@ describe('donations', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await factory.create('User', { currentUser = await Factory.build('user', {
id: 'normal-user', id: 'normal-user',
role: 'user', role: 'user',
}) })
@ -102,7 +101,7 @@ describe('donations', () => {
describe('authenticated', () => { describe('authenticated', () => {
describe('as a normal user', () => { describe('as a normal user', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await factory.create('User', { currentUser = await Factory.build('user', {
id: 'normal-user', id: 'normal-user',
role: 'user', role: 'user',
}) })
@ -121,7 +120,7 @@ describe('donations', () => {
describe('as a moderator', () => { describe('as a moderator', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await factory.create('User', { currentUser = await Factory.build('user', {
id: 'moderator', id: 'moderator',
role: 'moderator', role: 'moderator',
}) })
@ -140,7 +139,7 @@ describe('donations', () => {
describe('as an admin', () => { describe('as an admin', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await factory.create('User', { currentUser = await Factory.build('user', {
id: 'admin', id: 'admin',
role: 'admin', role: 'admin',
}) })

View File

@ -1,10 +1,9 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getDriver, getNeode } from '../../db/neo4j' import { getDriver, getNeode } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
let mutate let mutate
@ -31,7 +30,7 @@ beforeAll(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('AddEmailAddress', () => { describe('AddEmailAddress', () => {
@ -63,7 +62,7 @@ describe('AddEmailAddress', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
user = await factory.create('User', { id: '567', email: 'user@example.org' }) user = await Factory.build('user', { id: '567' }, { email: 'user@example.org' })
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
}) })
@ -110,7 +109,7 @@ describe('AddEmailAddress', () => {
describe('if another `UnverifiedEmailAddress` node already exists with that email', () => { describe('if another `UnverifiedEmailAddress` node already exists with that email', () => {
it('throws no unique constraint violation error', async () => { it('throws no unique constraint violation error', async () => {
await factory.create('UnverifiedEmailAddress', { await Factory.build('unverifiedEmailAddress', {
createdAt: '2019-09-24T14:00:01.565Z', createdAt: '2019-09-24T14:00:01.565Z',
email: 'new-email@example.org', email: 'new-email@example.org',
}) })
@ -128,7 +127,7 @@ describe('AddEmailAddress', () => {
describe('but if another user owns an `EmailAddress` already with that email', () => { describe('but if another user owns an `EmailAddress` already with that email', () => {
it('throws UserInputError because of unique constraints', async () => { it('throws UserInputError because of unique constraints', async () => {
await factory.create('User', { email: 'new-email@example.org' }) await Factory.build('user', {}, { email: 'new-email@example.org' })
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { AddEmailAddress: null }, data: { AddEmailAddress: null },
errors: [{ message: 'A user account with this email already exists.' }], errors: [{ message: 'A user account with this email already exists.' }],
@ -169,7 +168,7 @@ describe('VerifyEmailAddress', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
user = await factory.create('User', { id: '567', email: 'user@example.org' }) user = await Factory.build('user', { id: '567' }, { email: 'user@example.org' })
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
}) })
@ -185,7 +184,7 @@ describe('VerifyEmailAddress', () => {
describe('given a `UnverifiedEmailAddress`', () => { describe('given a `UnverifiedEmailAddress`', () => {
let emailAddress let emailAddress
beforeEach(async () => { beforeEach(async () => {
emailAddress = await factory.create('UnverifiedEmailAddress', { emailAddress = await Factory.build('unverifiedEmailAddress', {
nonce: 'abcdef', nonce: 'abcdef',
verifiedAt: null, verifiedAt: null,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
@ -281,7 +280,7 @@ describe('VerifyEmailAddress', () => {
describe('Edge case: In the meantime someone created an `EmailAddress` node with the given email', () => { describe('Edge case: In the meantime someone created an `EmailAddress` node with the given email', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('EmailAddress', { email: 'to-be-verified@example.org' }) await Factory.build('emailAddress', { email: 'to-be-verified@example.org' })
}) })
it('throws UserInputError because of unique constraints', async () => { it('throws UserInputError because of unique constraints', async () => {

View File

@ -1,24 +1,27 @@
import { createWriteStream } from 'fs' import { createWriteStream } from 'fs'
import path from 'path' import path from 'path'
import slug from 'slug' import slug from 'slug'
import uuid from 'uuid/v4'
const storeUpload = ({ createReadStream, fileLocation }) => const localFileUpload = async ({ createReadStream, uniqueFilename }) => {
new Promise((resolve, reject) => await new Promise((resolve, reject) =>
createReadStream() createReadStream()
.pipe(createWriteStream(`public${fileLocation}`)) .pipe(createWriteStream(`public${uniqueFilename}`))
.on('finish', resolve) .on('finish', resolve)
.on('error', reject), .on('error', reject),
) )
return uniqueFilename
}
export default async function fileUpload(params, { file, url }, uploadCallback = storeUpload) { export default async function fileUpload(params, { file, url }, uploadCallback = localFileUpload) {
const upload = params[file] const upload = params[file]
if (upload) { if (upload) {
const { createReadStream, filename } = await upload const { createReadStream, filename } = await upload
const { name } = path.parse(filename) const { name, ext } = path.parse(filename)
const fileLocation = `/uploads/${Date.now()}-${slug(name)}` const uniqueFilename = `/uploads/${uuid()}-${slug(name)}${ext}`
await uploadCallback({ createReadStream, fileLocation }) const location = await uploadCallback({ createReadStream, uniqueFilename })
delete params[file] delete params[file]
params[url] = fileLocation params[url] = location
} }
return params return params

View File

@ -1,5 +1,7 @@
import fileUpload from '.' import fileUpload from '.'
const uuid = '[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}'
describe('fileUpload', () => { describe('fileUpload', () => {
let params let params
let uploadCallback let uploadCallback
@ -13,7 +15,7 @@ describe('fileUpload', () => {
createReadStream: jest.fn(), createReadStream: jest.fn(),
}, },
} }
uploadCallback = jest.fn() uploadCallback = jest.fn(({ uniqueFilename }) => uniqueFilename)
}) })
it('calls uploadCallback', async () => { it('calls uploadCallback', async () => {
@ -24,20 +26,13 @@ describe('fileUpload', () => {
describe('file name', () => { describe('file name', () => {
it('saves the upload url in params[url]', async () => { it('saves the upload url in params[url]', async () => {
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback) await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(params.attribute).toMatch(/^\/uploads\/\d+-avatar$/) expect(params.attribute).toMatch(new RegExp(`^/uploads/${uuid}-avatar.jpg`))
})
it('uses the name without file ending', async () => {
params.uploadAttribute.filename = 'somePng.png'
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(params.attribute).toMatch(/^\/uploads\/\d+-somePng/)
}) })
it('creates a url safe name', async () => { it('creates a url safe name', async () => {
params.uploadAttribute.filename = params.uploadAttribute.filename = '/path/to/awkward?/ file-location/?foo- bar-avatar.jpg'
'/path/to/awkward?/ file-location/?foo- bar-avatar.jpg?foo- bar'
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback) await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(params.attribute).toMatch(/^\/uploads\/\d+-foo-bar-avatar$/) expect(params.attribute).toMatch(new RegExp(`/uploads/${uuid}-foo-bar-avatar.jpg$`))
}) })
describe('in case of duplicates', () => { describe('in case of duplicates', () => {
@ -50,7 +45,6 @@ describe('fileUpload', () => {
uploadCallback, uploadCallback,
) )
await new Promise(resolve => setTimeout(resolve, 1000))
const { attribute: second } = await fileUpload( const { attribute: second } = await fileUpload(
{ {
...params, ...params,

View File

@ -1,10 +1,9 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { getDriver, getNeode } from '../../db/neo4j' import { getDriver, getNeode } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
const factory = Factory()
const driver = getDriver() const driver = getDriver()
const neode = getNeode() const neode = getNeode()
@ -54,7 +53,7 @@ const userQuery = gql`
` `
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
const { server } = createServer({ const { server } = createServer({
context: () => ({ context: () => ({
driver, driver,
@ -72,29 +71,35 @@ beforeAll(async () => {
}) })
beforeEach(async () => { beforeEach(async () => {
user1 = await factory user1 = await Factory.build(
.create('User', { 'user',
{
id: 'u1', id: 'u1',
name: 'user1', name: 'user1',
},
{
email: 'test@example.org', email: 'test@example.org',
password: '1234', password: '1234',
}) },
.then(user => user.toJson()) ).then(user => user.toJson())
user2 = await factory user2 = await Factory.build(
.create('User', { 'user',
{
id: 'u2', id: 'u2',
name: 'user2', name: 'user2',
},
{
email: 'test2@example.org', email: 'test2@example.org',
password: '1234', password: '1234',
}) },
.then(user => user.toJson()) ).then(user => user.toJson())
authenticatedUser = user1 authenticatedUser = user1
variables = { id: user2.id } variables = { id: user2.id }
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('follow', () => { describe('follow', () => {

View File

@ -1,11 +1,9 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
let mutate, authenticatedUser let mutate, authenticatedUser
const driver = getDriver() const driver = getDriver()
@ -25,7 +23,7 @@ beforeAll(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('resolvers', () => { describe('resolvers', () => {
@ -49,16 +47,16 @@ describe('resolvers', () => {
id: 'u47', id: 'u47',
name: 'John Doughnut', name: 'John Doughnut',
} }
const Paris = await factory.create('Location', { const Paris = await Factory.build('location', {
id: 'region.9397217726497330', id: 'region.9397217726497330',
name: 'Paris', name: 'Paris',
type: 'region', type: 'region',
lat: 2.35183, lng: 2.35183,
lng: 48.85658, lat: 48.85658,
nameEN: 'Paris', nameEN: 'Paris',
}) })
const user = await factory.create('User', { const user = await Factory.build('user', {
id: 'u47', id: 'u47',
name: 'John Doe', name: 'John Doe',
}) })

View File

@ -1,10 +1,9 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
const driver = getDriver() const driver = getDriver()
@ -54,7 +53,7 @@ const reviewMutation = gql`
describe('moderate resources', () => { describe('moderate resources', () => {
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
authenticatedUser = undefined authenticatedUser = undefined
const { server } = createServer({ const { server } = createServer({
context: () => { context: () => {
@ -80,23 +79,33 @@ describe('moderate resources', () => {
closed: false, closed: false,
} }
authenticatedUser = null authenticatedUser = null
moderator = await factory.create('User', { moderator = await Factory.build(
id: 'moderator-id', 'user',
name: 'Moderator', {
email: 'moderator@example.org', id: 'moderator-id',
password: '1234', name: 'Moderator',
role: 'moderator', role: 'moderator',
}) },
nonModerator = await factory.create('User', { {
id: 'non-moderator', email: 'moderator@example.org',
name: 'Non Moderator', password: '1234',
email: 'non.moderator@example.org', },
password: '1234', )
}) nonModerator = await Factory.build(
'user',
{
id: 'non-moderator',
name: 'Non Moderator',
},
{
email: 'non.moderator@example.org',
password: '1234',
},
)
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('review to close report, leaving resource enabled', () => { describe('review to close report, leaving resource enabled', () => {
@ -127,10 +136,10 @@ describe('moderate resources', () => {
describe('moderator', () => { describe('moderator', () => {
beforeEach(async () => { beforeEach(async () => {
authenticatedUser = await moderator.toJson() authenticatedUser = await moderator.toJson()
const questionablePost = await factory.create('Post', { const questionablePost = await Factory.build('post', {
id: 'should-i-be-disabled', id: 'should-i-be-disabled',
}) })
const reportAgainstQuestionablePost = await factory.create('Report') const reportAgainstQuestionablePost = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstQuestionablePost.relateTo(nonModerator, 'filed', { reportAgainstQuestionablePost.relateTo(nonModerator, 'filed', {
resourceId: 'should-i-be-disabled', resourceId: 'should-i-be-disabled',
@ -229,10 +238,10 @@ describe('moderate resources', () => {
describe('moderate a comment', () => { describe('moderate a comment', () => {
beforeEach(async () => { beforeEach(async () => {
const trollingComment = await factory.create('Comment', { const trollingComment = await Factory.build('comment', {
id: 'comment-id', id: 'comment-id',
}) })
const reportAgainstTrollingComment = await factory.create('Report') const reportAgainstTrollingComment = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstTrollingComment.relateTo(nonModerator, 'filed', { reportAgainstTrollingComment.relateTo(nonModerator, 'filed', {
resourceId: 'comment-id', resourceId: 'comment-id',
@ -307,10 +316,10 @@ describe('moderate resources', () => {
describe('moderate a post', () => { describe('moderate a post', () => {
beforeEach(async () => { beforeEach(async () => {
const trollingPost = await factory.create('Post', { const trollingPost = await Factory.build('post', {
id: 'post-id', id: 'post-id',
}) })
const reportAgainstTrollingPost = await factory.create('Report') const reportAgainstTrollingPost = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstTrollingPost.relateTo(nonModerator, 'filed', { reportAgainstTrollingPost.relateTo(nonModerator, 'filed', {
resourceId: 'post-id', resourceId: 'post-id',
@ -387,10 +396,10 @@ describe('moderate resources', () => {
describe('moderate a user', () => { describe('moderate a user', () => {
beforeEach(async () => { beforeEach(async () => {
const troll = await factory.create('User', { const troll = await Factory.build('user', {
id: 'user-id', id: 'user-id',
}) })
const reportAgainstTroll = await factory.create('Report') const reportAgainstTroll = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstTroll.relateTo(nonModerator, 'filed', { reportAgainstTroll.relateTo(nonModerator, 'filed', {
resourceId: 'user-id', resourceId: 'user-id',
@ -504,10 +513,10 @@ describe('moderate resources', () => {
describe('moderate a comment', () => { describe('moderate a comment', () => {
beforeEach(async () => { beforeEach(async () => {
const trollingComment = await factory.create('Comment', { const trollingComment = await Factory.build('comment', {
id: 'comment-id', id: 'comment-id',
}) })
const reportAgainstTrollingComment = await factory.create('Report') const reportAgainstTrollingComment = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstTrollingComment.relateTo(nonModerator, 'filed', { reportAgainstTrollingComment.relateTo(nonModerator, 'filed', {
resourceId: 'comment-id', resourceId: 'comment-id',
@ -568,10 +577,10 @@ describe('moderate resources', () => {
describe('moderate a post', () => { describe('moderate a post', () => {
beforeEach(async () => { beforeEach(async () => {
const trollingPost = await factory.create('Post', { const trollingPost = await Factory.build('post', {
id: 'post-id', id: 'post-id',
}) })
const reportAgainstTrollingPost = await factory.create('Report') const reportAgainstTrollingPost = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstTrollingPost.relateTo(nonModerator, 'filed', { reportAgainstTrollingPost.relateTo(nonModerator, 'filed', {
resourceId: 'post-id', resourceId: 'post-id',
@ -633,10 +642,10 @@ describe('moderate resources', () => {
describe('moderate a user', () => { describe('moderate a user', () => {
beforeEach(async () => { beforeEach(async () => {
const troll = await factory.create('User', { const troll = await Factory.build('user', {
id: 'user-id', id: 'user-id',
}) })
const reportAgainstTroll = await factory.create('Report') const reportAgainstTroll = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstTroll.relateTo(nonModerator, 'filed', { reportAgainstTroll.relateTo(nonModerator, 'filed', {
resourceId: 'user-id', resourceId: 'user-id',

View File

@ -1,21 +1,18 @@
import log from './helpers/databaseLogger' import log from './helpers/databaseLogger'
import { withFilter } from 'graphql-subscriptions'
const resourceTypes = ['Post', 'Comment'] import { pubsub, NOTIFICATION_ADDED } from '../../server'
const transformReturnType = record => {
return {
...record.get('notification').properties,
from: {
__typename: record.get('resource').labels.find(l => resourceTypes.includes(l)),
...record.get('resource').properties,
},
to: {
...record.get('user').properties,
},
}
}
export default { export default {
Subscription: {
notificationAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator(NOTIFICATION_ADDED),
(payload, variables) => {
return payload.notificationAdded.to.id === variables.userId
},
),
},
},
Query: { Query: {
notifications: async (_parent, args, context, _resolveInfo) => { notifications: async (_parent, args, context, _resolveInfo) => {
const { user: currentUser } = context const { user: currentUser } = context
@ -51,10 +48,10 @@ export default {
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
${whereClause} ${whereClause}
WITH user, notification, resource, WITH user, notification, resource,
[(resource)<-[:WROTE]-(author:User) | author {.*}] as authors, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts, WITH resource, user, notification, authors, posts,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} as finalResource resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
RETURN notification {.*, from: finalResource, to: properties(user)} RETURN notification {.*, from: finalResource, to: properties(user)}
${orderByClause} ${orderByClause}
${offset} ${limit} ${offset} ${limit}
@ -81,12 +78,19 @@ export default {
` `
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id}) MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
SET notification.read = TRUE SET notification.read = TRUE
RETURN resource, notification, user WITH user, notification, resource,
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
RETURN notification {.*, from: finalResource, to: properties(user)}
`, `,
{ resourceId: args.id, id: currentUser.id }, { resourceId: args.id, id: currentUser.id },
) )
log(markNotificationAsReadTransactionResponse) log(markNotificationAsReadTransactionResponse)
return markNotificationAsReadTransactionResponse.records.map(transformReturnType) return markNotificationAsReadTransactionResponse.records.map(record =>
record.get('notification'),
)
}) })
try { try {
const [notifications] = await writeTxResultPromise const [notifications] = await writeTxResultPromise

View File

@ -1,10 +1,9 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getDriver } from '../../db/neo4j' import { getDriver } from '../../db/neo4j'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../.././server' import createServer from '../.././server'
const factory = Factory()
const driver = getDriver() const driver = getDriver()
let authenticatedUser let authenticatedUser
let user let user
@ -32,52 +31,77 @@ beforeEach(async () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('given some notifications', () => { describe('given some notifications', () => {
beforeEach(async () => { beforeEach(async () => {
const categoryIds = ['cat1'] const categoryIds = ['cat1']
author = await factory.create('User', { id: 'author' }) author = await Factory.build('user', { id: 'author' })
user = await factory.create('User', { id: 'you' }) user = await Factory.build('user', { id: 'you' })
const [neighbor] = await Promise.all([ const [neighbor] = await Promise.all([
factory.create('User', { id: 'neighbor' }), Factory.build('user', { id: 'neighbor' }),
factory.create('Category', { id: 'cat1' }), Factory.build('category', { id: 'cat1' }),
]) ])
const [post1, post2, post3] = await Promise.all([ const [post1, post2, post3] = await Promise.all([
factory.create('Post', { author, id: 'p1', categoryIds, content: 'Not for you' }), Factory.build('post', { id: 'p1', content: 'Not for you' }, { author, categoryIds }),
factory.create('Post', { Factory.build(
author, 'post',
id: 'p2', {
categoryIds, id: 'p2',
content: 'Already seen post mention', content: 'Already seen post mention',
}), },
factory.create('Post', { {
author, author,
id: 'p3', categoryIds,
categoryIds, },
content: 'You have been mentioned in a post', ),
}), Factory.build(
'post',
{
id: 'p3',
content: 'You have been mentioned in a post',
},
{
author,
categoryIds,
},
),
]) ])
const [comment1, comment2, comment3] = await Promise.all([ const [comment1, comment2, comment3] = await Promise.all([
factory.create('Comment', { Factory.build(
author, 'comment',
postId: 'p3', {
id: 'c1', id: 'c1',
content: 'You have seen this comment mentioning already', content: 'You have seen this comment mentioning already',
}), },
factory.create('Comment', { {
author, author,
postId: 'p3', postId: 'p3',
id: 'c2', },
content: 'You have been mentioned in a comment', ),
}), Factory.build(
factory.create('Comment', { 'comment',
author, {
postId: 'p3', id: 'c2',
id: 'c3', content: 'You have been mentioned in a comment',
content: 'Somebody else was mentioned in a comment', },
}), {
author,
postId: 'p3',
},
),
Factory.build(
'comment',
{
id: 'c3',
content: 'Somebody else was mentioned in a comment',
},
{
author,
postId: 'p3',
},
),
]) ])
await Promise.all([ await Promise.all([
post1.relateTo(neighbor, 'notified', { post1.relateTo(neighbor, 'notified', {

View File

@ -1,4 +1,4 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createPasswordReset from './helpers/createPasswordReset' import createPasswordReset from './helpers/createPasswordReset'
@ -7,7 +7,6 @@ import { createTestClient } from 'apollo-server-testing'
const neode = getNeode() const neode = getNeode()
const driver = getDriver() const driver = getDriver()
const factory = Factory()
let mutate let mutate
let authenticatedUser let authenticatedUser
@ -39,15 +38,19 @@ beforeAll(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('passwordReset', () => { describe('passwordReset', () => {
describe('given a user', () => { describe('given a user', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { await Factory.build(
email: 'user@example.org', 'user',
}) {},
{
email: 'user@example.org',
},
)
}) })
describe('requestPasswordReset', () => { describe('requestPasswordReset', () => {
@ -123,11 +126,16 @@ describe('resetPassword', () => {
describe('given a user', () => { describe('given a user', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { await Factory.build(
email: 'user@example.org', 'user',
role: 'user', {
password: '1234', role: 'user',
}) },
{
email: 'user@example.org',
password: '1234',
},
)
}) })
describe('invalid email', () => { describe('invalid email', () => {

View File

@ -1,11 +1,10 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
const driver = getDriver() const driver = getDriver()
const factory = Factory()
const neode = getNeode() const neode = getNeode()
let query let query
@ -40,7 +39,7 @@ const createPostMutation = gql`
` `
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
const { server } = createServer({ const { server } = createServer({
context: () => { context: () => {
return { return {
@ -56,12 +55,17 @@ beforeAll(async () => {
beforeEach(async () => { beforeEach(async () => {
variables = {} variables = {}
user = await factory.create('User', { user = await Factory.build(
id: 'current-user', 'user',
name: 'TestUser', {
email: 'test@example.org', id: 'current-user',
password: '1234', name: 'TestUser',
}) },
{
email: 'test@example.org',
password: '1234',
},
)
await Promise.all([ await Promise.all([
neode.create('Category', { neode.create('Category', {
id: 'cat9', id: 'cat9',
@ -88,7 +92,7 @@ beforeEach(async () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('Post', () => { describe('Post', () => {
@ -96,21 +100,31 @@ describe('Post', () => {
let followedUser, happyPost, cryPost let followedUser, happyPost, cryPost
beforeEach(async () => { beforeEach(async () => {
;[followedUser] = await Promise.all([ ;[followedUser] = await Promise.all([
factory.create('User', { Factory.build(
id: 'followed-by-me', 'user',
email: 'followed@example.org', {
name: 'Followed User', id: 'followed-by-me',
password: '1234', name: 'Followed User',
}), },
{
email: 'followed@example.org',
password: '1234',
},
),
]) ])
;[happyPost, cryPost] = await Promise.all([ ;[happyPost, cryPost] = await Promise.all([
factory.create('Post', { id: 'happy-post', categoryIds: ['cat4'] }), Factory.build('post', { id: 'happy-post' }, { categoryIds: ['cat4'] }),
factory.create('Post', { id: 'cry-post', categoryIds: ['cat15'] }), Factory.build('post', { id: 'cry-post' }, { categoryIds: ['cat15'] }),
factory.create('Post', { Factory.build(
id: 'post-by-followed-user', 'post',
categoryIds: ['cat9'], {
author: followedUser, id: 'post-by-followed-user',
}), },
{
categoryIds: ['cat9'],
author: followedUser,
},
),
]) ])
}) })
@ -340,14 +354,19 @@ describe('UpdatePost', () => {
} }
` `
beforeEach(async () => { beforeEach(async () => {
author = await factory.create('User', { slug: 'the-author' }) author = await Factory.build('user', { slug: 'the-author' })
newlyCreatedPost = await factory.create('Post', { newlyCreatedPost = await Factory.build(
author, 'post',
id: 'p9876', {
title: 'Old title', id: 'p9876',
content: 'Old content', title: 'Old title',
categoryIds, content: 'Old content',
}) },
{
author,
categoryIds,
},
)
variables = { variables = {
id: 'p9876', id: 'p9876',
@ -529,10 +548,15 @@ describe('UpdatePost', () => {
describe('are allowed to pin posts', () => { describe('are allowed to pin posts', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Post', { await Factory.build(
id: 'created-and-pinned-by-same-admin', 'post',
author: admin, {
}) id: 'created-and-pinned-by-same-admin',
},
{
author: admin,
},
)
variables = { ...variables, id: 'created-and-pinned-by-same-admin' } variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
}) })
@ -589,15 +613,20 @@ describe('UpdatePost', () => {
describe('post created by another admin', () => { describe('post created by another admin', () => {
let otherAdmin let otherAdmin
beforeEach(async () => { beforeEach(async () => {
otherAdmin = await factory.create('User', { otherAdmin = await Factory.build('user', {
role: 'admin', role: 'admin',
name: 'otherAdmin', name: 'otherAdmin',
}) })
authenticatedUser = await otherAdmin.toJson() authenticatedUser = await otherAdmin.toJson()
await factory.create('Post', { await Factory.build(
id: 'created-by-one-admin-pinned-by-different-one', 'post',
author: otherAdmin, {
}) id: 'created-by-one-admin-pinned-by-different-one',
},
{
author: otherAdmin,
},
)
}) })
it('responds with the updated Post', async () => { it('responds with the updated Post', async () => {
@ -654,10 +683,15 @@ describe('UpdatePost', () => {
describe('pinned post already exists', () => { describe('pinned post already exists', () => {
let pinnedPost let pinnedPost
beforeEach(async () => { beforeEach(async () => {
await factory.create('Post', { await Factory.build(
id: 'only-pinned-post', 'post',
author: admin, {
}) id: 'only-pinned-post',
},
{
author: admin,
},
)
await mutate({ mutation: pinPostMutation, variables }) await mutate({ mutation: pinPostMutation, variables })
}) })
@ -683,12 +717,12 @@ describe('UpdatePost', () => {
describe('PostOrdering', () => { describe('PostOrdering', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Post', { await Factory.build('post', {
id: 'im-a-pinned-post', id: 'im-a-pinned-post',
createdAt: '2019-11-22T17:26:29.070Z', createdAt: '2019-11-22T17:26:29.070Z',
pinned: true, pinned: true,
}) })
await factory.create('Post', { await Factory.build('post', {
id: 'i-was-created-before-pinned-post', id: 'i-was-created-before-pinned-post',
// fairly old, so this should be 3rd // fairly old, so this should be 3rd
createdAt: '2019-10-22T17:26:29.070Z', createdAt: '2019-10-22T17:26:29.070Z',
@ -807,7 +841,7 @@ describe('UpdatePost', () => {
describe('admin can unpin posts', () => { describe('admin can unpin posts', () => {
let admin, pinnedPost let admin, pinnedPost
beforeEach(async () => { beforeEach(async () => {
pinnedPost = await factory.create('Post', { id: 'post-to-be-unpinned' }) pinnedPost = await Factory.build('post', { id: 'post-to-be-unpinned' })
admin = await user.update({ admin = await user.update({
role: 'admin', role: 'admin',
name: 'Admin', name: 'Admin',
@ -874,15 +908,20 @@ describe('DeletePost', () => {
` `
beforeEach(async () => { beforeEach(async () => {
author = await factory.create('User') author = await Factory.build('user')
await factory.create('Post', { await Factory.build(
id: 'p4711', 'post',
author, {
title: 'I will be deleted', id: 'p4711',
content: 'To be deleted', title: 'I will be deleted',
image: 'path/to/some/image', content: 'To be deleted',
categoryIds, image: 'path/to/some/image',
}) },
{
author,
categoryIds,
},
)
variables = { ...variables, id: 'p4711' } variables = { ...variables, id: 'p4711' }
}) })
@ -929,11 +968,16 @@ describe('DeletePost', () => {
describe('if there are comments on the post', () => { describe('if there are comments on the post', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Comment', { await Factory.build(
postId: 'p4711', 'comment',
content: 'to be deleted comment content', {
contentExcerpt: 'to be deleted comment content', content: 'to be deleted comment content',
}) contentExcerpt: 'to be deleted comment content',
},
{
postId: 'p4711',
},
)
}) })
it('marks the comments as deleted', async () => { it('marks the comments as deleted', async () => {
@ -988,11 +1032,16 @@ describe('emotions', () => {
beforeEach(async () => { beforeEach(async () => {
author = await neode.create('User', { id: 'u257' }) author = await neode.create('User', { id: 'u257' })
postToEmote = await factory.create('Post', { postToEmote = await Factory.build(
author, 'post',
id: 'p1376', {
categoryIds, id: 'p1376',
}) },
{
author,
categoryIds,
},
)
variables = { variables = {
...variables, ...variables,

View File

@ -1,10 +1,9 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getDriver, getNeode } from '../../db/neo4j' import { getDriver, getNeode } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
let mutate let mutate
@ -30,7 +29,7 @@ beforeAll(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('Signup', () => { describe('Signup', () => {
@ -58,11 +57,16 @@ describe('Signup', () => {
describe('as admin', () => { describe('as admin', () => {
beforeEach(async () => { beforeEach(async () => {
const admin = await factory.create('User', { const admin = await Factory.build(
role: 'admin', 'user',
email: 'admin@example.org', {
password: '1234', role: 'admin',
}) },
{
email: 'admin@example.org',
password: '1234',
},
)
authenticatedUser = await admin.toJson() authenticatedUser = await admin.toJson()
}) })
@ -90,9 +94,9 @@ describe('Signup', () => {
}) })
describe('if the email already exists', () => { describe('if the email already exists', () => {
let email let emailAddress
beforeEach(async () => { beforeEach(async () => {
email = await factory.create('EmailAddress', { emailAddress = await Factory.build('emailAddress', {
email: 'someuser@example.org', email: 'someuser@example.org',
verifiedAt: null, verifiedAt: null,
}) })
@ -100,7 +104,8 @@ describe('Signup', () => {
describe('and the user has registered already', () => { describe('and the user has registered already', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { email }) const user = await Factory.build('userWithoutEmailAddress')
await emailAddress.relateTo(user, 'belongsTo')
}) })
it('throws UserInputError error because of unique constraint violation', async () => { it('throws UserInputError error because of unique constraint violation', async () => {

View File

@ -1,10 +1,9 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../.././server' import createServer from '../.././server'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getDriver, getNeode } from '../../db/neo4j' import { getDriver, getNeode } from '../../db/neo4j'
const factory = Factory()
const instance = getNeode() const instance = getNeode()
const driver = getDriver() const driver = getDriver()
@ -53,7 +52,7 @@ describe('file a report on a resource', () => {
} }
beforeAll(async () => { beforeAll(async () => {
await factory.cleanDatabase() await cleanDatabase()
const { server } = createServer({ const { server } = createServer({
context: () => { context: () => {
return { return {
@ -68,7 +67,7 @@ describe('file a report on a resource', () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('report a resource', () => { describe('report a resource', () => {
@ -84,24 +83,39 @@ describe('file a report on a resource', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await factory.create('User', { currentUser = await Factory.build(
id: 'current-user-id', 'user',
role: 'user', {
email: 'test@example.org', id: 'current-user-id',
password: '1234', role: 'user',
}) },
otherReportingUser = await factory.create('User', { {
id: 'other-reporting-user-id', email: 'test@example.org',
role: 'user', password: '1234',
email: 'reporting@example.org', },
password: '1234', )
}) otherReportingUser = await Factory.build(
await factory.create('User', { 'user',
id: 'abusive-user-id', {
role: 'user', id: 'other-reporting-user-id',
name: 'abusive-user', role: 'user',
email: 'abusive-user@example.org', },
}) {
email: 'reporting@example.org',
password: '1234',
},
)
await Factory.build(
'user',
{
id: 'abusive-user-id',
role: 'user',
name: 'abusive-user',
},
{
email: 'abusive-user@example.org',
},
)
await instance.create('Category', { await instance.create('Category', {
id: 'cat9', id: 'cat9',
name: 'Democracy & Politics', name: 'Democracy & Politics',
@ -341,12 +355,17 @@ describe('file a report on a resource', () => {
describe('reported resource is a post', () => { describe('reported resource is a post', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Post', { await Factory.build(
author: currentUser, 'post',
id: 'post-to-report-id', {
title: 'This is a post that is going to be reported', id: 'post-to-report-id',
categoryIds, title: 'This is a post that is going to be reported',
}) },
{
author: currentUser,
categoryIds,
},
)
}) })
it('returns type "Post"', async () => { it('returns type "Post"', async () => {
@ -394,21 +413,30 @@ describe('file a report on a resource', () => {
}) })
describe('reported resource is a comment', () => { describe('reported resource is a comment', () => {
let createPostVariables
beforeEach(async () => { beforeEach(async () => {
createPostVariables = { await Factory.build(
id: 'p1', 'post',
title: 'post to comment on', {
content: 'please comment on me', id: 'p1',
categoryIds, title: 'post to comment on',
} content: 'please comment on me',
await factory.create('Post', { ...createPostVariables, author: currentUser }) },
await factory.create('Comment', { {
author: currentUser, categoryIds,
postId: 'p1', author: currentUser,
id: 'comment-to-report-id', },
content: 'Post comment to be reported.', )
}) await Factory.build(
'comment',
{
id: 'comment-to-report-id',
content: 'Post comment to be reported.',
},
{
author: currentUser,
postId: 'p1',
},
)
}) })
it('returns type "Comment"', async () => { it('returns type "Comment"', async () => {
@ -457,7 +485,7 @@ describe('file a report on a resource', () => {
describe('reported resource is a tag', () => { describe('reported resource is a tag', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Tag', { await Factory.build('tag', {
id: 'tag-to-report-id', id: 'tag-to-report-id',
}) })
}) })
@ -515,24 +543,39 @@ describe('file a report on a resource', () => {
beforeEach(async () => { beforeEach(async () => {
authenticatedUser = null authenticatedUser = null
moderator = await factory.create('User', { moderator = await Factory.build(
id: 'moderator-1', 'user',
role: 'moderator', {
email: 'moderator@example.org', id: 'moderator-1',
password: '1234', role: 'moderator',
}) },
currentUser = await factory.create('User', { {
id: 'current-user-id', email: 'moderator@example.org',
role: 'user', password: '1234',
email: 'current.user@example.org', },
password: '1234', )
}) currentUser = await Factory.build(
abusiveUser = await factory.create('User', { 'user',
id: 'abusive-user-1', {
role: 'user', id: 'current-user-id',
name: 'abusive-user', role: 'user',
email: 'abusive-user@example.org', },
}) {
email: 'current.user@example.org',
password: '1234',
},
)
abusiveUser = await Factory.build(
'user',
{
id: 'abusive-user-1',
role: 'user',
name: 'abusive-user',
},
{
email: 'abusive-user@example.org',
},
)
await instance.create('Category', { await instance.create('Category', {
id: 'cat9', id: 'cat9',
name: 'Democracy & Politics', name: 'Democracy & Politics',
@ -540,31 +583,51 @@ describe('file a report on a resource', () => {
}) })
await Promise.all([ await Promise.all([
factory.create('Post', { Factory.build(
author: abusiveUser, 'post',
id: 'abusive-post-1', {
categoryIds, id: 'abusive-post-1',
content: 'Interesting Knowledge', content: 'Interesting Knowledge',
}), },
factory.create('Post', { {
author: moderator, categoryIds,
id: 'post-2', author: abusiveUser,
categoryIds, },
content: 'More things to do …', ),
}), Factory.build(
factory.create('Post', { 'post',
author: currentUser, {
id: 'post-3', id: 'post-2',
categoryIds, content: 'More things to do …',
content: 'I am at school …', },
}), {
author: moderator,
categoryIds,
},
),
Factory.build(
'post',
{
id: 'post-3',
content: 'I am at school …',
},
{
categoryIds,
author: currentUser,
},
),
]) ])
await Promise.all([ await Promise.all([
factory.create('Comment', { Factory.build(
author: currentUser, 'comment',
id: 'abusive-comment-1', {
postId: 'post-1', id: 'abusive-comment-1',
}), },
{
postId: 'post-2',
author: currentUser,
},
),
]) ])
authenticatedUser = await currentUser.toJson() authenticatedUser = await currentUser.toJson()
await Promise.all([ await Promise.all([

View File

@ -1,10 +1,9 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
const factory = Factory()
const driver = getDriver() const driver = getDriver()
const instance = getNeode() const instance = getNeode()
@ -31,23 +30,38 @@ describe('rewards', () => {
}) })
beforeEach(async () => { beforeEach(async () => {
regularUser = await factory.create('User', { regularUser = await Factory.build(
id: 'regular-user-id', 'user',
role: 'user', {
email: 'user@example.org', id: 'regular-user-id',
password: '1234', role: 'user',
}) },
moderator = await factory.create('User', { {
id: 'moderator-id', email: 'user@example.org',
role: 'moderator', password: '1234',
email: 'moderator@example.org', },
}) )
administrator = await factory.create('User', { moderator = await Factory.build(
id: 'admin-id', 'user',
role: 'admin', {
email: 'admin@example.org', id: 'moderator-id',
}) role: 'moderator',
badge = await factory.create('Badge', { },
{
email: 'moderator@example.org',
},
)
administrator = await Factory.build(
'user',
{
id: 'admin-id',
role: 'admin',
},
{
email: 'admin@example.org',
},
)
badge = await Factory.build('badge', {
id: 'indiegogo_en_rhino', id: 'indiegogo_en_rhino',
type: 'crowdfunding', type: 'crowdfunding',
status: 'permanent', status: 'permanent',
@ -56,7 +70,7 @@ describe('rewards', () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('reward', () => { describe('reward', () => {
@ -130,7 +144,7 @@ describe('rewards', () => {
}) })
it('rewards a second different badge to same user', async () => { it('rewards a second different badge to same user', async () => {
await factory.create('Badge', { await Factory.build('badge', {
id: 'indiegogo_en_racoon', id: 'indiegogo_en_racoon',
icon: '/img/badges/indiegogo_en_racoon.svg', icon: '/img/badges/indiegogo_en_racoon.svg',
}) })
@ -172,10 +186,15 @@ describe('rewards', () => {
}, },
errors: undefined, errors: undefined,
} }
await factory.create('User', { await Factory.build(
id: 'regular-user-2-id', 'user',
email: 'regular2@email.com', {
}) id: 'regular-user-2-id',
},
{
email: 'regular2@email.com',
},
)
await mutate({ await mutate({
mutation: rewardMutation, mutation: rewardMutation,
variables, variables,

View File

@ -1,11 +1,10 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
let mutate, query, authenticatedUser, variables let mutate, query, authenticatedUser, variables
const factory = Factory()
const instance = getNeode() const instance = getNeode()
const driver = getDriver() const driver = getDriver()
@ -47,22 +46,32 @@ describe('shout and unshout posts', () => {
query = createTestClient(server).query query = createTestClient(server).query
}) })
beforeEach(async () => { beforeEach(async () => {
currentUser = await factory.create('User', { currentUser = await Factory.build(
id: 'current-user-id', 'user',
name: 'Current User', {
email: 'current.user@example.org', id: 'current-user-id',
password: '1234', name: 'Current User',
}) },
{
email: 'current.user@example.org',
password: '1234',
},
)
postAuthor = await factory.create('User', { postAuthor = await Factory.build(
id: 'id-of-another-user', 'user',
name: 'Another User', {
email: 'another.user@example.org', id: 'id-of-another-user',
password: '1234', name: 'Another User',
}) },
{
email: 'another.user@example.org',
password: '1234',
},
)
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('shout', () => { describe('shout', () => {
@ -78,16 +87,26 @@ describe('shout and unshout posts', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
authenticatedUser = await currentUser.toJson() authenticatedUser = await currentUser.toJson()
await factory.create('Post', { await Factory.build(
name: 'Other user post', 'post',
id: 'another-user-post-id', {
author: postAuthor, name: 'Other user post',
}) id: 'another-user-post-id',
await factory.create('Post', { },
name: 'current user post', {
id: 'current-user-post-id', author: postAuthor,
author: currentUser, },
}) )
await Factory.build(
'post',
{
name: 'current user post',
id: 'current-user-post-id',
},
{
author: currentUser,
},
)
variables = {} variables = {}
}) })
@ -144,11 +163,16 @@ describe('shout and unshout posts', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
authenticatedUser = await currentUser.toJson() authenticatedUser = await currentUser.toJson()
await factory.create('Post', { await Factory.build(
name: 'Posted By Another User', 'post',
id: 'posted-by-another-user', {
author: postAuthor, name: 'Posted By Another User',
}) id: 'posted-by-another-user',
},
{
author: postAuthor,
},
)
await mutate({ await mutate({
mutation: mutationShoutPost, mutation: mutationShoutPost,
variables: { id: 'posted-by-another-user' }, variables: { id: 'posted-by-another-user' },

View File

@ -1,41 +1,46 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../../server' import createServer from '../../server'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getDriver } from '../../db/neo4j'
const driver = getDriver() const driver = getDriver()
const factory = Factory()
const neode = getNeode()
describe('SocialMedia', () => { describe('SocialMedia', () => {
let socialMediaAction, someUser, ownerNode, owner let socialMediaAction, someUser, ownerNode, owner
const ownerParams = {
email: 'pippi@example.com',
password: '1234',
name: 'Pippi Langstrumpf',
}
const userParams = {
email: 'kalle@example.com',
password: 'abcd',
name: 'Kalle Blomqvist',
}
const url = 'https://twitter.com/pippi-langstrumpf' const url = 'https://twitter.com/pippi-langstrumpf'
const newUrl = 'https://twitter.com/bullerby' const newUrl = 'https://twitter.com/bullerby'
const setUpSocialMedia = async () => { const setUpSocialMedia = async () => {
const socialMediaNode = await neode.create('SocialMedia', { url }) const socialMediaNode = await Factory.build('socialMedia', { url })
await socialMediaNode.relateTo(ownerNode, 'ownedBy') await socialMediaNode.relateTo(ownerNode, 'ownedBy')
return socialMediaNode.toJson() return socialMediaNode.toJson()
} }
beforeEach(async () => { beforeEach(async () => {
const someUserNode = await neode.create('User', userParams) const someUserNode = await Factory.build(
'user',
{
name: 'Kalle Blomqvist',
},
{
email: 'kalle@example.com',
password: 'abcd',
},
)
someUser = await someUserNode.toJson() someUser = await someUserNode.toJson()
ownerNode = await neode.create('User', ownerParams) ownerNode = await Factory.build(
'user',
{
name: 'Pippi Langstrumpf',
},
{
email: 'pippi@example.com',
password: '1234',
},
)
owner = await ownerNode.toJson() owner = await ownerNode.toJson()
socialMediaAction = async (user, mutation, variables) => { socialMediaAction = async (user, mutation, variables) => {
@ -57,7 +62,7 @@ describe('SocialMedia', () => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('create social media', () => { describe('create social media', () => {

View File

@ -1,11 +1,10 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
let query, authenticatedUser let query, authenticatedUser
const factory = Factory()
const instance = getNeode() const instance = getNeode()
const driver = getDriver() const driver = getDriver()
@ -37,7 +36,7 @@ beforeAll(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('statistics', () => { describe('statistics', () => {
@ -45,7 +44,7 @@ describe('statistics', () => {
beforeEach(async () => { beforeEach(async () => {
await Promise.all( await Promise.all(
[...Array(6).keys()].map(() => { [...Array(6).keys()].map(() => {
return factory.create('User') return Factory.build('user')
}), }),
) )
}) })
@ -62,7 +61,7 @@ describe('statistics', () => {
beforeEach(async () => { beforeEach(async () => {
await Promise.all( await Promise.all(
[...Array(3).keys()].map(() => { [...Array(3).keys()].map(() => {
return factory.create('Post') return Factory.build('post')
}), }),
) )
}) })
@ -79,7 +78,7 @@ describe('statistics', () => {
beforeEach(async () => { beforeEach(async () => {
await Promise.all( await Promise.all(
[...Array(2).keys()].map(() => { [...Array(2).keys()].map(() => {
return factory.create('Comment') return Factory.build('comment')
}), }),
) )
}) })
@ -97,7 +96,7 @@ describe('statistics', () => {
beforeEach(async () => { beforeEach(async () => {
users = await Promise.all( users = await Promise.all(
[...Array(2).keys()].map(() => { [...Array(2).keys()].map(() => {
return factory.create('User') return Factory.build('user')
}), }),
) )
await users[0].relateTo(users[1], 'following') await users[0].relateTo(users[1], 'following')
@ -116,12 +115,12 @@ describe('statistics', () => {
beforeEach(async () => { beforeEach(async () => {
users = await Promise.all( users = await Promise.all(
[...Array(2).keys()].map(() => { [...Array(2).keys()].map(() => {
return factory.create('User') return Factory.build('user')
}), }),
) )
posts = await Promise.all( posts = await Promise.all(
[...Array(3).keys()].map(() => { [...Array(3).keys()].map(() => {
return factory.create('Post') return Factory.build('post')
}), }),
) )
await Promise.all([ await Promise.all([

View File

@ -1,20 +1,19 @@
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import CONFIG from './../../config' import CONFIG from './../../config'
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer, { context } from '../../server' import createServer, { context } from '../../server'
import encode from '../../jwt/encode' import encode from '../../jwt/encode'
import { getNeode } from '../../db/neo4j' import { getNeode } from '../../db/neo4j'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
let query, mutate, variables, req, user let query, mutate, variables, req, user
const disable = async id => { const disable = async id => {
const moderator = await factory.create('User', { id: 'u2', role: 'moderator' }) const moderator = await Factory.build('user', { id: 'u2', role: 'moderator' })
const user = await neode.find('User', id) const user = await neode.find('User', id)
const reportAgainstUser = await factory.create('Report') const reportAgainstUser = await Factory.build('report')
await Promise.all([ await Promise.all([
reportAgainstUser.relateTo(moderator, 'filed', { reportAgainstUser.relateTo(moderator, 'filed', {
resourceId: id, resourceId: id,
@ -48,7 +47,7 @@ beforeAll(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('isLoggedIn', () => { describe('isLoggedIn', () => {
@ -69,7 +68,7 @@ describe('isLoggedIn', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
user = await factory.create('User', { id: 'u3' }) user = await Factory.build('user', { id: 'u3' })
const userBearerToken = encode({ id: 'u3' }) const userBearerToken = encode({ id: 'u3' })
req = { headers: { authorization: `Bearer ${userBearerToken}` } } req = { headers: { authorization: `Bearer ${userBearerToken}` } }
}) })
@ -127,15 +126,20 @@ describe('currentUser', () => {
describe('authenticated', () => { describe('authenticated', () => {
describe('and corresponding user in the database', () => { describe('and corresponding user in the database', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { await Factory.build(
id: 'u3', 'user',
// the `id` is the only thing that has to match the decoded JWT bearer token {
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg', id: 'u3',
email: 'test@example.org', // the `id` is the only thing that has to match the decoded JWT bearer token
name: 'Matilde Hermiston', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg',
slug: 'matilde-hermiston', name: 'Matilde Hermiston',
role: 'user', slug: 'matilde-hermiston',
}) role: 'user',
},
{
email: 'test@example.org',
},
)
const userBearerToken = encode({ id: 'u3' }) const userBearerToken = encode({ id: 'u3' })
req = { headers: { authorization: `Bearer ${userBearerToken}` } } req = { headers: { authorization: `Bearer ${userBearerToken}` } }
}) })
@ -172,10 +176,13 @@ describe('login', () => {
beforeEach(async () => { beforeEach(async () => {
variables = { email: 'test@example.org', password: '1234' } variables = { email: 'test@example.org', password: '1234' }
user = await factory.create('User', { user = await Factory.build(
...variables, 'user',
id: 'acb2d923-f3af-479e-9f00-61b12e864666', {
}) id: 'acb2d923-f3af-479e-9f00-61b12e864666',
},
variables,
)
}) })
describe('ask for a `token`', () => { describe('ask for a `token`', () => {
@ -185,7 +192,9 @@ describe('login', () => {
data: { login: token }, data: { login: token },
} = await mutate({ mutation: loginMutation, variables }) } = await mutate({ mutation: loginMutation, variables })
jwt.verify(token, CONFIG.JWT_SECRET, (err, data) => { jwt.verify(token, CONFIG.JWT_SECRET, (err, data) => {
expect(data.email).toEqual('test@example.org') expect(data).toMatchObject({
id: 'acb2d923-f3af-479e-9f00-61b12e864666',
})
expect(err).toBeNull() expect(err).toBeNull()
done() done()
}) })
@ -295,7 +304,7 @@ describe('change password', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { id: 'u3' }) await Factory.build('user', { id: 'u3' })
const userBearerToken = encode({ id: 'u3' }) const userBearerToken = encode({ id: 'u3' })
req = { headers: { authorization: `Bearer ${userBearerToken}` } } req = { headers: { authorization: `Bearer ${userBearerToken}` } }
}) })

View File

@ -252,9 +252,11 @@ export default {
followedByCurrentUser: followedByCurrentUser:
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', 'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
blocked: blocked:
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', 'MATCH (this)-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
isMuted: isMuted:
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', 'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
isBlocked:
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
}, },
count: { count: {
contributionsCount: contributionsCount:

View File

@ -1,10 +1,9 @@
import Factory from '../../factories' import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest' import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
const categoryIds = ['cat9'] const categoryIds = ['cat9']
let user let user
@ -31,13 +30,13 @@ beforeAll(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('User', () => { describe('User', () => {
describe('query by email address', () => { describe('query by email address', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { name: 'Johnny', email: 'any-email-address@example.org' }) await Factory.build('user', { name: 'Johnny' }, { email: 'any-email-address@example.org' })
}) })
const userQuery = gql` const userQuery = gql`
@ -57,11 +56,16 @@ describe('User', () => {
describe('as admin', () => { describe('as admin', () => {
beforeEach(async () => { beforeEach(async () => {
const admin = await factory.create('User', { const admin = await Factory.build(
role: 'admin', 'user',
email: 'admin@example.org', {
password: '1234', role: 'admin',
}) },
{
email: 'admin@example.org',
password: '1234',
},
)
authenticatedUser = await admin.toJson() authenticatedUser = await admin.toJson()
}) })
@ -91,19 +95,9 @@ describe('User', () => {
}) })
describe('UpdateUser', () => { describe('UpdateUser', () => {
let userParams, variables let variables
beforeEach(async () => { beforeEach(async () => {
userParams = {
email: 'user@example.org',
password: '1234',
id: 'u47',
name: 'John Doe',
termsAndConditionsAgreedVersion: null,
termsAndConditionsAgreedAt: null,
allowEmbedIframes: false,
}
variables = { variables = {
id: 'u47', id: 'u47',
name: 'John Doughnut', name: 'John Doughnut',
@ -133,18 +127,33 @@ describe('UpdateUser', () => {
` `
beforeEach(async () => { beforeEach(async () => {
user = await factory.create('User', userParams) user = await Factory.build(
'user',
{
id: 'u47',
name: 'John Doe',
termsAndConditionsAgreedVersion: null,
termsAndConditionsAgreedAt: null,
allowEmbedIframes: false,
},
{
email: 'user@example.org',
},
)
}) })
describe('as another user', () => { describe('as another user', () => {
beforeEach(async () => { beforeEach(async () => {
const someoneElseParams = { const someoneElse = await Factory.build(
email: 'someone-else@example.org', 'user',
password: '1234', {
name: 'James Doe', name: 'James Doe',
} },
{
email: 'someone-else@example.org',
},
)
const someoneElse = await factory.create('User', someoneElseParams)
authenticatedUser = await someoneElse.toJson() authenticatedUser = await someoneElse.toJson()
}) })
@ -267,16 +276,20 @@ describe('DeleteUser', () => {
beforeEach(async () => { beforeEach(async () => {
variables = { id: ' u343', resource: [] } variables = { id: ' u343', resource: [] }
user = await factory.create('User', { user = await Factory.build('user', {
name: 'My name should be deleted', name: 'My name should be deleted',
about: 'along with my about', about: 'along with my about',
id: 'u343', id: 'u343',
}) })
await factory.create('User', { await Factory.build(
email: 'friends-account@example.org', 'user',
password: '1234', {
id: 'not-my-account', id: 'not-my-account',
}) },
{
email: 'friends-account@example.org',
},
)
}) })
describe('unauthenticated', () => { describe('unauthenticated', () => {
@ -309,27 +322,42 @@ describe('DeleteUser', () => {
describe('given posts and comments', () => { describe('given posts and comments', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('Category', { await Factory.build('category', {
id: 'cat9', id: 'cat9',
name: 'Democracy & Politics', name: 'Democracy & Politics',
icon: 'university', icon: 'university',
}) })
await factory.create('Post', { await Factory.build(
author: user, 'post',
id: 'p139', {
content: 'Post by user u343', id: 'p139',
categoryIds, content: 'Post by user u343',
}) },
await factory.create('Comment', { {
author: user, author: user,
id: 'c155', categoryIds,
content: 'Comment by user u343', },
}) )
await factory.create('Comment', { await Factory.build(
postId: 'p139', 'comment',
id: 'c156', {
content: "A comment by someone else on user u343's post", id: 'c155',
}) content: 'Comment by user u343',
},
{
author: user,
},
)
await Factory.build(
'comment',
{
id: 'c156',
content: "A comment by someone else on user u343's post",
},
{
postId: 'p139',
},
)
}) })
it("deletes my account, but doesn't delete posts or comments by default", async () => { it("deletes my account, but doesn't delete posts or comments by default", async () => {
@ -527,7 +555,7 @@ describe('DeleteUser', () => {
describe('connected `SocialMedia` nodes', () => { describe('connected `SocialMedia` nodes', () => {
beforeEach(async () => { beforeEach(async () => {
const socialMedia = await factory.create('SocialMedia') const socialMedia = await Factory.build('socialMedia')
await socialMedia.relateTo(user, 'ownedBy') await socialMedia.relateTo(user, 'ownedBy')
}) })

View File

@ -34,8 +34,8 @@ const createLocation = async (session, mapboxData) => {
namePL: mapboxData.text_pl, namePL: mapboxData.text_pl,
nameRU: mapboxData.text_ru, nameRU: mapboxData.text_ru,
type: mapboxData.id.split('.')[0].toLowerCase(), type: mapboxData.id.split('.')[0].toLowerCase(),
lat: mapboxData.center && mapboxData.center.length ? mapboxData.center[0] : null, lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[0] : null,
lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null, lat: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null,
} }
let mutation = let mutation =

View File

@ -1,10 +1,9 @@
import { gql } from '../../../helpers/jest' import { gql } from '../../../helpers/jest'
import Factory from '../../../factories' import Factory, { cleanDatabase } from '../../../db/factories'
import { getNeode, getDriver } from '../../../db/neo4j' import { getNeode, getDriver } from '../../../db/neo4j'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../../../server' import createServer from '../../../server'
const factory = Factory()
const neode = getNeode() const neode = getNeode()
const driver = getDriver() const driver = getDriver()
let authenticatedUser, mutate, variables let authenticatedUser, mutate, variables
@ -42,7 +41,7 @@ const updateUserMutation = gql`
let newlyCreatedNodesWithLocales = [ let newlyCreatedNodesWithLocales = [
{ {
city: { city: {
lng: 41.1534, lat: 41.1534,
nameES: 'Hamburg', nameES: 'Hamburg',
nameFR: 'Hamburg', nameFR: 'Hamburg',
nameIT: 'Hamburg', nameIT: 'Hamburg',
@ -55,7 +54,7 @@ let newlyCreatedNodesWithLocales = [
name: 'Hamburg', name: 'Hamburg',
namePL: 'Hamburg', namePL: 'Hamburg',
id: 'place.5977106083398860', id: 'place.5977106083398860',
lat: -74.5763, lng: -74.5763,
}, },
state: { state: {
namePT: 'Nova Jérsia', namePT: 'Nova Jérsia',
@ -107,7 +106,7 @@ beforeEach(() => {
}) })
afterEach(() => { afterEach(() => {
factory.cleanDatabase() cleanDatabase()
}) })
describe('userMiddleware', () => { describe('userMiddleware', () => {
@ -146,12 +145,12 @@ describe('userMiddleware', () => {
}) })
describe('UpdateUser', () => { describe('UpdateUser', () => {
let user, userParams let user
beforeEach(async () => { beforeEach(async () => {
newlyCreatedNodesWithLocales = [ newlyCreatedNodesWithLocales = [
{ {
city: { city: {
lng: 53.55, lat: 53.55,
nameES: 'Hamburgo', nameES: 'Hamburgo',
nameFR: 'Hambourg', nameFR: 'Hambourg',
nameIT: 'Amburgo', nameIT: 'Amburgo',
@ -164,7 +163,7 @@ describe('userMiddleware', () => {
namePL: 'Hamburg', namePL: 'Hamburg',
name: 'Hamburg', name: 'Hamburg',
id: 'region.10793468240398860', id: 'region.10793468240398860',
lat: 10, lng: 10,
}, },
country: { country: {
namePT: 'Alemanha', namePT: 'Alemanha',
@ -182,10 +181,9 @@ describe('userMiddleware', () => {
}, },
}, },
] ]
userParams = { user = await Factory.build('user', {
id: 'updating-user', id: 'updating-user',
} })
user = await factory.create('User', userParams)
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
}) })

View File

@ -1,11 +1,10 @@
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import createServer from '../../../server' import createServer from '../../../server'
import Factory from '../../../factories' import { cleanDatabase } from '../../../db/factories'
import { gql } from '../../../helpers/jest' import { gql } from '../../../helpers/jest'
import { getNeode, getDriver } from '../../../db/neo4j' import { getNeode, getDriver } from '../../../db/neo4j'
const driver = getDriver() const driver = getDriver()
const factory = Factory()
const neode = getNeode() const neode = getNeode()
let currentUser let currentUser
@ -30,7 +29,7 @@ beforeEach(() => {
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await cleanDatabase()
}) })
describe('mutedUsers', () => { describe('mutedUsers', () => {

View File

@ -30,3 +30,7 @@ type Query {
type Mutation { type Mutation {
markAsRead(id: ID!): NOTIFIED markAsRead(id: ID!): NOTIFIED
} }
type Subscription {
notificationAdded(userId: ID!): NOTIFIED
}

View File

@ -68,7 +68,12 @@ type User {
RETURN COUNT(u) >= 1 RETURN COUNT(u) >= 1
""" """
) )
isBlocked: Boolean! @cypher(
statement: """
MATCH (this)<-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})
RETURN COUNT(user) >= 1
"""
)
blocked: Boolean! @cypher( blocked: Boolean! @cypher(
statement: """ statement: """
MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId}) MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})

View File

@ -1,4 +1,5 @@
import express from 'express' import express from 'express'
import http from 'http'
import helmet from 'helmet' import helmet from 'helmet'
import { ApolloServer } from 'apollo-server-express' import { ApolloServer } from 'apollo-server-express'
import CONFIG from './config' import CONFIG from './config'
@ -7,11 +8,35 @@ import { getNeode, getDriver } from './db/neo4j'
import decode from './jwt/decode' import decode from './jwt/decode'
import schema from './schema' import schema from './schema'
import webfinger from './activitypub/routes/webfinger' import webfinger from './activitypub/routes/webfinger'
import { RedisPubSub } from 'graphql-redis-subscriptions'
import { PubSub } from 'graphql-subscriptions'
import Redis from 'ioredis'
import bodyParser from 'body-parser'
export const NOTIFICATION_ADDED = 'NOTIFICATION_ADDED'
const { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD } = CONFIG
let prodPubsub, devPubsub
const options = {
host: REDIS_DOMAIN,
port: REDIS_PORT,
password: REDIS_PASSWORD,
retryStrategy: times => {
return Math.min(times * 50, 2000)
},
}
if (options.host && options.port && options.password) {
prodPubsub = new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
})
} else {
devPubsub = new PubSub()
}
export const pubsub = prodPubsub || devPubsub
const driver = getDriver() const driver = getDriver()
const neode = getNeode() const neode = getNeode()
export const context = async ({ req }) => { const getContext = async req => {
const user = await decode(driver, req.headers.authorization) const user = await decode(driver, req.headers.authorization)
return { return {
driver, driver,
@ -23,11 +48,24 @@ export const context = async ({ req }) => {
}, },
} }
} }
export const context = async options => {
const { connection, req } = options
if (connection) {
return connection.context
} else {
return getContext(req)
}
}
const createServer = options => { const createServer = options => {
const defaults = { const defaults = {
context, context,
schema: middleware(schema), schema: middleware(schema),
subscriptions: {
onConnect: (connectionParams, webSocket) => {
return getContext(connectionParams)
},
},
debug: !!CONFIG.DEBUG, debug: !!CONFIG.DEBUG,
tracing: !!CONFIG.DEBUG, tracing: !!CONFIG.DEBUG,
formatError: error => { formatError: error => {
@ -45,9 +83,13 @@ const createServer = options => {
app.use(helmet()) app.use(helmet())
app.use('/.well-known/', webfinger()) app.use('/.well-known/', webfinger())
app.use(express.static('public')) app.use(express.static('public'))
app.use(bodyParser.json({ limit: '10mb' }))
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }))
server.applyMiddleware({ app, path: '/' }) server.applyMiddleware({ app, path: '/' })
const httpServer = http.createServer(app)
server.installSubscriptionHandlers(httpServer)
return { server, app } return { server, httpServer, app }
} }
export default createServer export default createServer

View File

@ -3,18 +3,18 @@ import { Given, When, Then, AfterAll } from 'cucumber'
import { expect } from 'chai' import { expect } from 'chai'
// import { client } from '../../../src/activitypub/apollo-client' // import { client } from '../../../src/activitypub/apollo-client'
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../../../src/factories' import Factory from '../../../src/db/factories'
const debug = require('debug')('ea:test:steps') const debug = require('debug')('ea:test:steps')
const factory = Factory()
const client = new GraphQLClient(host) const client = new GraphQLClient(host)
function createUser (slug) { function createUser (slug) {
debug(`creating user ${slug}`) debug(`creating user ${slug}`)
return factory.create('User', { return Factory.build('user', {
name: slug, name: slug,
}, {
password: '1234',
email: 'example@test.org', email: 'example@test.org',
password: '1234'
}) })
// await login({ email: 'example@test.org', password: '1234' }) // await login({ email: 'example@test.org', password: '1234' })
} }

View File

@ -1175,13 +1175,13 @@
url-regex "~4.1.1" url-regex "~4.1.1"
video-extensions "~1.1.0" video-extensions "~1.1.0"
"@metascraper/helpers@^5.10.6": "@metascraper/helpers@^5.10.7", "@metascraper/helpers@^5.11.1":
version "5.10.6" version "5.11.1"
resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.10.6.tgz#0b786607212925a577926fd0cd0313a49de3499c" resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.11.1.tgz#227fdd0caf1d33f4b24a85298a355ce7ebb451df"
integrity sha512-/jvhlM3RKGYMoUK8D8S1r3tN03/EYizCqWF7zDx0aBMC8Ihp33DRGs9oNdsgkgwzVF7O/YpDm55l9K+qVJlsyQ== integrity sha512-oES/e6bwKBlT7WGa2ni3xbJMDx2rbFxSzbUhRX8D+Kylb8H2ThP07c7f+VXMPXWx5CPrNMai/Oyp5IczCf3v8g==
dependencies: dependencies:
audio-extensions "0.0.0" audio-extensions "0.0.0"
chrono-node "~1.4.2" chrono-node "~1.4.3"
condense-whitespace "~2.0.0" condense-whitespace "~2.0.0"
entities "~2.0.0" entities "~2.0.0"
file-extension "~4.0.5" file-extension "~4.0.5"
@ -1194,7 +1194,7 @@
lodash "~4.17.15" lodash "~4.17.15"
memoize-one "~5.1.1" memoize-one "~5.1.1"
mime-types "~2.1.26" mime-types "~2.1.26"
normalize-url "~4.5.0" normalize-url "~5.0.0"
smartquotes "~2.3.1" smartquotes "~2.3.1"
title "~3.4.1" title "~3.4.1"
truncate "~2.1.0" truncate "~2.1.0"
@ -1275,83 +1275,83 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@sentry/apm@5.11.2": "@sentry/apm@5.12.3":
version "5.11.2" version "5.12.3"
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.2.tgz#35961b9d2319ad21ae91f1b697998a8c523f1919" resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.12.3.tgz#23a5e9c771a8748f59426a1d0f8b1fbb9b72a717"
integrity sha512-qn4HiSZ+6b1Gg+DlXdHVpiPPEbRu4IicGSbI8HTJLzrlsjoaBQPPkDwtuQUBVq21tU3RYXnTwrl9m45KuX6alA== integrity sha512-OSGEeo4b1Gsu/TUcWMx9vmgSnQvR+zM+1Iwq5xFQAK2ET3Y4gBFqZ1iRt2hxlzr8KCQmQTQc1mao1X0tmidFQg==
dependencies: dependencies:
"@sentry/browser" "5.11.2" "@sentry/browser" "5.12.1"
"@sentry/hub" "5.11.2" "@sentry/hub" "5.12.0"
"@sentry/minimal" "5.11.2" "@sentry/minimal" "5.12.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.12.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.12.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/browser@5.11.2": "@sentry/browser@5.12.1":
version "5.11.2" version "5.12.1"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.11.2.tgz#f0b19bd97e9f09a20e9f93a9835339ed9ab1f5a4" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.12.1.tgz#dc1f268595269fb7277f55eb625c7e92d76dc01b"
integrity sha512-ls6ARX5m+23ld8OsuoPnR+kehjR5ketYWRcDYlmJDX2VOq5K4EzprujAo8waDB0o5a92yLXQ0ZSoK/zzAV2VoA== integrity sha512-Zl7VdppUxctyaoqMSEhnDJp2rrupx8n8N2n3PSooH74yhB2Z91nt84mouczprBsw3JU1iggGyUw9seRFzDI1hw==
dependencies: dependencies:
"@sentry/core" "5.11.2" "@sentry/core" "5.12.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.12.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.12.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/core@5.11.2": "@sentry/core@5.12.0":
version "5.11.2" version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.11.2.tgz#f2d9d37940d291dbcb9a9e4a012f76919474bdf6" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.12.0.tgz#d6380c4ef7beee5f418ac1d0e5be86a2de2af449"
integrity sha512-IFCXGy7ebqIq/Kb8RVryCo/SjwhPcrfBmOjkicr4+DxN1UybLre2N3p9bejQMPIteOfDVHlySLYeipjTf+mxZw== integrity sha512-wY4rsoX71QsGpcs9tF+OxKgDPKzIFMRvFiSRcJoPMfhFsTilQ/CBMn/c3bDtWQd9Bnr/ReQIL6NbnIjUsPHA4Q==
dependencies: dependencies:
"@sentry/hub" "5.11.2" "@sentry/hub" "5.12.0"
"@sentry/minimal" "5.11.2" "@sentry/minimal" "5.12.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.12.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.12.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/hub@5.11.2": "@sentry/hub@5.12.0":
version "5.11.2" version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.11.2.tgz#a3b7ec27cd4cea2cddd75c372fbf1b4bc04c6aae" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.12.0.tgz#5e8c8f249f5bdbeb8cc4ec02c2ccc53a67f2cc02"
integrity sha512-5BiDin6ZPsaiTm29rCC41MAjP1vOaKniqfjtXHVPm7FeOBA2bpHm95ncjLkshKGJTPfPZHXTpX/1IZsHrfGVEA== integrity sha512-3k7yE8BEVJsKx8mR4LcI4IN0O8pngmq44OcJ/fRUUBAPqsT38jsJdP2CaWhdlM1jiNUzUDB1ktBv6/lY+VgcoQ==
dependencies: dependencies:
"@sentry/types" "5.11.0" "@sentry/types" "5.12.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.12.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/minimal@5.11.2": "@sentry/minimal@5.12.0":
version "5.11.2" version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.11.2.tgz#ae417699342266ecd109a97e53cd9519c0893b21" resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.12.0.tgz#2611e2aa520c1edb7999e6de51bd65ec66341757"
integrity sha512-oNuJuz3EZhVtamzABmPdr6lcYo06XHLWb2LvgnoNaYcMD1ExUSvhepOSyZ2h5STCMbmVgGVfXBNPV9RUTp8GZg== integrity sha512-fk73meyz4k4jCg9yzbma+WkggsfEIQWI2e2TWfYsRGcrV3RnlSrXyM4D91/A8Bjx10SNezHPUFHjasjlHXOkyA==
dependencies: dependencies:
"@sentry/hub" "5.11.2" "@sentry/hub" "5.12.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.12.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/node@^5.11.2": "@sentry/node@^5.12.3":
version "5.11.2" version "5.12.3"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.11.2.tgz#575c320b624c218d2155183f6bbe82b732bfb1f2" resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.12.3.tgz#4a4934b04c5163fc340312eaf0d152990aa7140f"
integrity sha512-jYq9u76BdAbOKPuYg39Xh/+797MevzjMkCIC9cw/bQxAm6nHc3FXeKqd79O33jO4Jag0JL+Bz/0JidgrKgKgXg== integrity sha512-QwqN+i6IC3/YrNq7kqxH7YiXtZYY8tBuJqFi84WbiMHF7MAqxMSPNQJGfX2GJuMHKHwn6IZdgSE8+FkfN9zGLQ==
dependencies: dependencies:
"@sentry/apm" "5.11.2" "@sentry/apm" "5.12.3"
"@sentry/core" "5.11.2" "@sentry/core" "5.12.0"
"@sentry/hub" "5.11.2" "@sentry/hub" "5.12.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.12.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.12.0"
cookie "^0.3.1" cookie "^0.3.1"
https-proxy-agent "^4.0.0" https-proxy-agent "^4.0.0"
lru_map "^0.3.3" lru_map "^0.3.3"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/types@5.11.0": "@sentry/types@5.12.0":
version "5.11.0" version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.11.0.tgz#40f0f3174362928e033ddd9725d55e7c5cb7c5b6" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.12.0.tgz#5367e53c74261beea01502e3f7b6f3d822682a31"
integrity sha512-1Uhycpmeo1ZK2GLvrtwZhTwIodJHcyIS6bn+t4IMkN9MFoo6ktbAfhvexBDW/IDtdLlCGJbfm8nIZerxy0QUpg== integrity sha512-aZbBouBLrKB8wXlztriIagZNmsB+wegk1Jkl6eprqRW/w24Sl/47tiwH8c5S4jYTxdAiJk+SAR10AAuYmIN3zg==
"@sentry/utils@5.11.1": "@sentry/utils@5.12.0":
version "5.11.1" version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.11.1.tgz#aa19fcc234cf632257b2281261651d2fac967607" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.12.0.tgz#62967f934a3ee6d21472eac0219084e37225933e"
integrity sha512-O0Zl4R2JJh8cTkQ8ZL2cDqGCmQdpA5VeXpuBbEl1v78LQPkBDISi35wH4mKmLwMsLBtTVpx2UeUHBj0KO5aLlA== integrity sha512-fYUadGLbfTCbs4OG5hKCOtv2jrNE4/8LHNABy9DwNJ/t5DVtGqWAZBnxsC+FG6a3nVqCpxjFI9AHlYsJ2wsf7Q==
dependencies: dependencies:
"@sentry/types" "5.11.0" "@sentry/types" "5.12.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sindresorhus/is@^0.14.0": "@sindresorhus/is@^0.14.0":
@ -1607,10 +1607,10 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@types/yup@0.26.29": "@types/yup@0.26.30":
version "0.26.29" version "0.26.30"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.29.tgz#5a533ad6f74e442436698e20b1441c68a7a1c931" resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.30.tgz#0d6066505bb67e7b9b161b2082c4cdfcdafd6a6b"
integrity sha512-M81oZOgLap0b0I/BySnpLwHjOj1BFxUKV1ytG2Kqj3jmkh8F3H11PEnk658UniftpjTXdueloOL+KZYn+SMQ9w== integrity sha512-b/uklO68T/eShWnxjlgwOJlEFOQ11ib3i1wQQiLmHqFJTxDMvz+tb4XzThGQISzOcelMnoSdb1Po4s69YkEQeg==
"@types/zen-observable@^0.8.0": "@types/zen-observable@^0.8.0":
version "0.8.0" version "0.8.0"
@ -1845,10 +1845,10 @@ apollo-client@~2.6.8:
tslib "^1.10.0" tslib "^1.10.0"
zen-observable "^0.8.0" zen-observable "^0.8.0"
apollo-datasource@^0.6.4: apollo-datasource@^0.7.0:
version "0.6.4" version "0.7.0"
resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.6.4.tgz#c0d1604b1a97e004844d4b61bd819a9a6b0a409f" resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.7.0.tgz#2a6d82edb2eba21b4ddf21877009ba39ff821945"
integrity sha512-u4eu6Q94q6KuZacZfdo4vCevA81F4QWeTYEXUvoksQMJpiacPHHe0DJrofKVKvxngUp5kCi1RnPXSc6kBY+/oA== integrity sha512-Yja12BgNQhzuFGG/5Nw2MQe0hkuQy2+9er09HxeEyAf2rUDIPnhPrn1MDoZTB8MU7UGfjwITC+1ofzKkkrZobA==
dependencies: dependencies:
apollo-server-caching "^0.5.1" apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3" apollo-server-env "^2.4.3"
@ -1860,13 +1860,13 @@ apollo-engine-reporting-protobuf@^0.4.4:
dependencies: dependencies:
"@apollo/protobufjs" "^1.0.3" "@apollo/protobufjs" "^1.0.3"
apollo-engine-reporting@^1.4.14: apollo-engine-reporting@^1.5.0:
version "1.4.14" version "1.5.0"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.14.tgz#71a6509ebe86385da43df500cd0940525a3e8674" resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.5.0.tgz#6e3746de14fc87e14c289c0776a2d350e6f50918"
integrity sha512-cCG9qDOPwbh87ZjQGHgmnP3oPqhqjIZcNmm/lNtWkWXGTlxV/jmUEqpVi+wsDbE5gR7d1OFk6GqSy2ZQh+S+Bw== integrity sha512-Pe2DelijZ2QHqkqv8E97iOb32l+FIMT2nxpQsuH+nWi+96cCFJJJHjm3RLAPEUuvGOgW9dFYQP3J91EyC5O0tQ==
dependencies: dependencies:
apollo-engine-reporting-protobuf "^0.4.4" apollo-engine-reporting-protobuf "^0.4.4"
apollo-graphql "^0.3.7" apollo-graphql "^0.4.0"
apollo-server-caching "^0.5.1" apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3" apollo-server-env "^2.4.3"
apollo-server-errors "^2.3.4" apollo-server-errors "^2.3.4"
@ -1892,10 +1892,10 @@ apollo-errors@^1.9.0:
assert "^1.4.1" assert "^1.4.1"
extendable-error "^0.1.5" extendable-error "^0.1.5"
apollo-graphql@^0.3.7: apollo-graphql@^0.4.0:
version "0.3.7" version "0.4.0"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.7.tgz#533232ed48b0b6dbcf5635f65e66cf8677a5b768" resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.4.0.tgz#dd0afe31a6241b8e2ded20b906c9ee8dfbe03497"
integrity sha512-ghW16xx9tRcyL38Pw6G5OidMnYn+CNUGZWmvqQgEO2nRy4T0ONPZZBOvGrIMtJQ70oEykNMKGm0zm6PdHdxd8Q== integrity sha512-abCHcKln1EGbzSItW087EjBI5wnluikyUqEn4VsdeWHCtdENWpHCn/MnM0+jJa1prNasxN7tCukp4nMpJYYVqg==
dependencies: dependencies:
apollo-env "^0.6.1" apollo-env "^0.6.1"
lodash.sortby "^4.7.0" lodash.sortby "^4.7.0"
@ -1943,18 +1943,18 @@ apollo-server-caching@^0.5.1:
dependencies: dependencies:
lru-cache "^5.0.0" lru-cache "^5.0.0"
apollo-server-core@^2.9.16: apollo-server-core@^2.10.0:
version "2.9.16" version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.16.tgz#b4c869a6babfa6906fbbf1e6facf3b7231dbf777" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.10.0.tgz#b8d51bdffe6529f0e3ca670ee8f1238765cfade4"
integrity sha512-4ftdjSfs/3aEare9QNTVSF0yUvXETxiohuDLZ7gmMIQxNnZhUjVXiZL1rYKuIZ12uH7xLvh/DwkXRt6nLG/lZA== integrity sha512-x/UK6XvU307W8D/pzTclU04JIjRarcbg5mFPe0nVmO4OTc26uQgKi1WlZkcewXsAUnn+nDwKVn2c2G3dHEgXzQ==
dependencies: dependencies:
"@apollographql/apollo-tools" "^0.4.3" "@apollographql/apollo-tools" "^0.4.3"
"@apollographql/graphql-playground-html" "1.6.24" "@apollographql/graphql-playground-html" "1.6.24"
"@types/graphql-upload" "^8.0.0" "@types/graphql-upload" "^8.0.0"
"@types/ws" "^6.0.0" "@types/ws" "^6.0.0"
apollo-cache-control "^0.8.11" apollo-cache-control "^0.8.11"
apollo-datasource "^0.6.4" apollo-datasource "^0.7.0"
apollo-engine-reporting "^1.4.14" apollo-engine-reporting "^1.5.0"
apollo-server-caching "^0.5.1" apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3" apollo-server-env "^2.4.3"
apollo-server-errors "^2.3.4" apollo-server-errors "^2.3.4"
@ -1983,10 +1983,10 @@ apollo-server-errors@^2.3.4:
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz#b70ef01322f616cbcd876f3e0168a1a86b82db34" resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz#b70ef01322f616cbcd876f3e0168a1a86b82db34"
integrity sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA== integrity sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA==
apollo-server-express@^2.9.16: apollo-server-express@^2.10.0, apollo-server-express@^2.9.16:
version "2.9.16" version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.16.tgz#4c30b1769426c010b37943c0fb7766e5825973a0" resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.10.0.tgz#7d87ff54e378cdcb135eb3d093f20fca7fc0d1bc"
integrity sha512-ZDc7GP+piUm67alJ0DIE9f36tHcCiNm3PHMLIVJlVE/rcGwzRjV5rardRqeslljQiO2J+1IwXKwJ0/kRrZ4JvQ== integrity sha512-adDQts4QmkX2ENU7JibV1EwRl3ESnpnpImXIMvg8Cm7kX2wSbzwm8LecQNujwWJtkAPTCEAcnPBDyKwWjTQ6/g==
dependencies: dependencies:
"@apollographql/graphql-playground-html" "1.6.24" "@apollographql/graphql-playground-html" "1.6.24"
"@types/accepts" "^1.3.5" "@types/accepts" "^1.3.5"
@ -1994,7 +1994,7 @@ apollo-server-express@^2.9.16:
"@types/cors" "^2.8.4" "@types/cors" "^2.8.4"
"@types/express" "4.17.2" "@types/express" "4.17.2"
accepts "^1.3.5" accepts "^1.3.5"
apollo-server-core "^2.9.16" apollo-server-core "^2.10.0"
apollo-server-types "^0.2.10" apollo-server-types "^0.2.10"
body-parser "^1.18.3" body-parser "^1.18.3"
cors "^2.8.4" cors "^2.8.4"
@ -2012,12 +2012,12 @@ apollo-server-plugin-base@^0.6.10:
dependencies: dependencies:
apollo-server-types "^0.2.10" apollo-server-types "^0.2.10"
apollo-server-testing@~2.9.16: apollo-server-testing@~2.10.0:
version "2.9.16" version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.16.tgz#35e9b0b102a11bac8db2fce04281cb43e7993d45" resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.10.0.tgz#c8d7fc2d4e6eaf84232aaa7c125d9fae691fbcf4"
integrity sha512-CLfYZY2Htwzw6iPlFO32/SNXNstWQsvGd5/FQ8KEwRpNfYM4g0rAE98y/THEQTvTh0xPH+qWxA7CVQcc7/FMbQ== integrity sha512-wBJ/CT3ZN5nmSySMqgpAFwX/I3yzsQhRGR8MCK/16MjhEZH6svNaJWzoif6gaocj0NyVBJvOIijuMTecG9+6vg==
dependencies: dependencies:
apollo-server-core "^2.9.16" apollo-server-core "^2.10.0"
apollo-server-types@^0.2.10: apollo-server-types@^0.2.10:
version "0.2.10" version "0.2.10"
@ -2028,13 +2028,13 @@ apollo-server-types@^0.2.10:
apollo-server-caching "^0.5.1" apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3" apollo-server-env "^2.4.3"
apollo-server@~2.9.16: apollo-server@~2.10.0:
version "2.9.16" version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.16.tgz#c0054ed70ecb637cb3f585ff46fb4a060730076f" resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.10.0.tgz#93b924b089f7c4070e88aa29a8b9472c1d5d0f82"
integrity sha512-dqB1shkjl9ne7DfSHXDH5sT70llr9zswLL+/g/4zt4/H+k+2pkD1BShQkNIK7PBYcVa8KvRAHXiHTXZ36GCspA== integrity sha512-ITXkklSgrNfohFh4juvHWrtLz/82iw9CkBUW+G5T8NxHaqxm1Lpus1Ck2VsXmCgNplYi6mODRjUl087qdlU2Rw==
dependencies: dependencies:
apollo-server-core "^2.9.16" apollo-server-core "^2.10.0"
apollo-server-express "^2.9.16" apollo-server-express "^2.10.0"
express "^4.0.0" express "^4.0.0"
graphql-subscriptions "^1.0.0" graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0" graphql-tools "^4.0.0"
@ -2691,10 +2691,10 @@ chrono-node@~1.3.11:
dependencies: dependencies:
moment "2.21.0" moment "2.21.0"
chrono-node@~1.4.2: chrono-node@~1.4.3:
version "1.4.2" version "1.4.3"
resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-1.4.2.tgz#0c7fc1f264e60a660c2b2dab753a3f285dbfd8c9" resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-1.4.3.tgz#4c8e24643ec5e576f6f8fe0429370c3b554491b4"
integrity sha512-fsb82wPDHVZl3xtche8k4ZZtNwf81/ZMueil2ANpSfogUAEa3BuzZAar7ObLXi1ptMjBzdzA6ys/bFq1oBjO8w== integrity sha512-ZyKcnTcr8i7Mt9p4+ixMHEuR6+eMTrjYCL9Rm9TZHviLleCtcZoVzmr2uSc+Vg8MX1YbNCnPbEd4rfV8WvzLcw==
dependencies: dependencies:
dayjs "^1.8.19" dayjs "^1.8.19"
@ -2774,6 +2774,11 @@ clone-response@^1.0.2:
dependencies: dependencies:
mimic-response "^1.0.0" mimic-response "^1.0.0"
cluster-key-slot@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
co@^4.6.0: co@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@ -3254,6 +3259,11 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
denque@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
depd@2.0.0: depd@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@ -3625,10 +3635,10 @@ eslint-plugin-es@^3.0.0:
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
regexpp "^3.0.0" regexpp "^3.0.0"
eslint-plugin-import@~2.20.0: eslint-plugin-import@~2.20.1:
version "2.20.0" version "2.20.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz#d749a7263fb6c29980def8e960d380a6aa6aecaa" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3"
integrity sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ== integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==
dependencies: dependencies:
array-includes "^3.0.3" array-includes "^3.0.3"
array.prototype.flat "^1.2.1" array.prototype.flat "^1.2.1"
@ -3643,13 +3653,12 @@ eslint-plugin-import@~2.20.0:
read-pkg-up "^2.0.0" read-pkg-up "^2.0.0"
resolve "^1.12.0" resolve "^1.12.0"
eslint-plugin-jest@~23.6.0: eslint-plugin-jest@~23.7.0:
version "23.6.0" version "23.7.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.6.0.tgz#508b32f80d44058c8c01257c0ee718cfbd521e9d" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.7.0.tgz#84d5603b6e745b59898cb6750df6a44782a39b04"
integrity sha512-GH8AhcFXspOLqak7fqnddLXEJsrFyvgO8Bm60SexvKSn1+3rWYESnCiWUOCUcBTprNSDSE4CtAZdM4EyV6gPPw== integrity sha512-zkiyGlvJeHNjAEz8FaIxTXNblJJ/zj3waNbYbgflK7K6uy0cpE5zJBt/JpJtOBGM/UGkC6BqsQ4n0y7kQ2HA8w==
dependencies: dependencies:
"@typescript-eslint/experimental-utils" "^2.5.0" "@typescript-eslint/experimental-utils" "^2.5.0"
micromatch "^4.0.2"
eslint-plugin-node@~11.0.0: eslint-plugin-node@~11.0.0:
version "11.0.0" version "11.0.0"
@ -4202,10 +4211,10 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
fs-capacitor@^2.0.4: fs-capacitor@6.0.0, fs-capacitor@^2.0.4:
version "2.0.4" version "6.0.0"
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-2.0.4.tgz#5a22e72d40ae5078b4fe64fe4d08c0d3fc88ad3c" resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-6.0.0.tgz#b4b89e3281d61df1c573e788d9ee6ec4c7c94da4"
integrity sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA== integrity sha512-I+jZLV2q+ivQK/+Mu5FIYAECHgjoo8GBYJsBBQbNeU0aW1m25LU4E+MkLNq0kcJBjrp8Z6fhxpSeS8SyJyGkrw==
fs-minipass@^1.2.5: fs-minipass@^1.2.5:
version "1.2.6" version "1.2.6"
@ -4476,12 +4485,21 @@ graphql-middleware@~4.0.2:
dependencies: dependencies:
graphql-tools "^4.0.5" graphql-tools "^4.0.5"
graphql-shield@~7.0.9: graphql-redis-subscriptions@^2.1.2:
version "7.0.9" version "2.1.2"
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.0.9.tgz#8248916e9636a7e3c05719a52fd13f2d37ccaeb2" resolved "https://registry.yarnpkg.com/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.1.2.tgz#9c1b744bace0c6ba99dd0ebafe0148cad1df3301"
integrity sha512-2Dfddd2hcObCSqAj64c/Aaxvs7gaoD2QU14crj7H486QjS8jIAtEPUyLVyv8SmJ1ZD7jT6wqx6wrB15Npn5Sgw== integrity sha512-l69KbGxyYfVHxvE+Dzv9/hXg/q+Xnjfx1JsrJD6ikePuSsNaCSNxr+MubSTNF3Gt3C/+JZs4FaWImFeK/+X2og==
dependencies: dependencies:
"@types/yup" "0.26.29" iterall "^1.2.2"
optionalDependencies:
ioredis "^4.6.3"
graphql-shield@~7.0.11:
version "7.0.11"
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.0.11.tgz#78d49f346326be71090d35d8f5843da9ee8136e2"
integrity sha512-iWn/aiom2c8NuOj60euWTmsKKUjX1DB4ynBcDitQOLXG3WrWgss2Iolzr553qooJvkR5czeAFgPlZgI+nUgwsQ==
dependencies:
"@types/yup" "0.26.30"
object-hash "^2.0.0" object-hash "^2.0.0"
yup "^0.28.0" yup "^0.28.0"
@ -4492,10 +4510,10 @@ graphql-subscriptions@^1.0.0:
dependencies: dependencies:
iterall "^1.2.1" iterall "^1.2.1"
graphql-tag@^2.9.2, graphql-tag@~2.10.1: graphql-tag@^2.9.2, graphql-tag@~2.10.3:
version "2.10.1" version "2.10.3"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03"
integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg== integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==
graphql-tools@^4.0.0, graphql-tools@^4.0.4, graphql-tools@^4.0.5: graphql-tools@^4.0.0, graphql-tools@^4.0.4, graphql-tools@^4.0.5:
version "4.0.5" version "4.0.5"
@ -4535,7 +4553,7 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0: har-validator@~5.1.3:
version "5.1.3" version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@ -4920,6 +4938,21 @@ invariant@^2.2.2, invariant@^2.2.4:
dependencies: dependencies:
loose-envify "^1.0.0" loose-envify "^1.0.0"
ioredis@^4.14.1, ioredis@^4.6.3:
version "4.14.1"
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.14.1.tgz#b73ded95fcf220f106d33125a92ef6213aa31318"
integrity sha512-94W+X//GHM+1GJvDk6JPc+8qlM7Dul+9K+lg3/aHixPN7ZGkW6qlvX0DG6At9hWtH2v3B32myfZqWoANUJYGJA==
dependencies:
cluster-key-slot "^1.1.0"
debug "^4.1.1"
denque "^1.1.0"
lodash.defaults "^4.2.0"
lodash.flatten "^4.4.0"
redis-commands "1.5.0"
redis-errors "^1.2.0"
redis-parser "^3.0.0"
standard-as-callback "^2.0.1"
ip-regex@^1.0.1: ip-regex@^1.0.1:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd"
@ -5960,11 +5993,21 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
lodash.escaperegexp@^4.1.2: lodash.escaperegexp@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
lodash.includes@^4.3.0: lodash.includes@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
@ -6154,19 +6197,19 @@ merge2@^1.3.0:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
metascraper-audio@^5.10.6: metascraper-audio@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.10.6.tgz#095892445b90d40bc54e54f69536a80e36fd9e4c" resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.10.7.tgz#ba9f8333a7b71d388a0bf88dff64fc4f06595566"
integrity sha512-wTVtYK8Ico82caIi6HlkyGgUaBC21X/vhT2aQ4LKcg+gHoOhJcmWNd5me9VhaRJ7gTV/7yKkL5A54fBcjcn8Kg== integrity sha512-VHZlT21bh/TWnHOQMGret3UcMdJOsyWvagK7MG8rLczYmrPEtvxnJjwPhyrEj1oJC+fz2P//bfQ6gyrD4HrmEQ==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
metascraper-author@^5.10.6: metascraper-author@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.10.6.tgz#1ceaacec776d46629300db25e17657fe35a14a20" resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.10.7.tgz#99b3a2b982b7a63feea41554659be3db7bf7035c"
integrity sha512-L2P/Fp0npaQcowbwi1vHKJbSYc99cio58/yYRm205xGfgMCRMpYOrYB+ecizXgeSSRiv8G8SXLrLXOLJ5K+SdA== integrity sha512-AdNkcqy+eqs2Eeh+6odhIWArR4pWVlrCx3jMaho0BDY6ZnKgJP44HtlPNkghQpBaueoKX6CycGKraITzwjGj1w==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
lodash "~4.17.15" lodash "~4.17.15"
metascraper-clearbit-logo@^5.3.0: metascraper-clearbit-logo@^5.3.0:
@ -6176,26 +6219,26 @@ metascraper-clearbit-logo@^5.3.0:
dependencies: dependencies:
got "~9.6.0" got "~9.6.0"
metascraper-date@^5.10.6: metascraper-date@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-date/-/metascraper-date-5.10.6.tgz#dbdc0ddb4f4220ad1ea412b4a686900c1b138cf6" resolved "https://registry.yarnpkg.com/metascraper-date/-/metascraper-date-5.10.7.tgz#580891d98f14438658610d951e5454a0658eb5f3"
integrity sha512-WfZw7WhkMKrrq96ZcAxS01/YjiDBpAPt5e3ggnCfLi2ZzC370w9J0INUo7gAtujaNZvgTTSEcrDD7AbTVMSYKA== integrity sha512-S1ZsvYrOccS6EGvlAJT7Ph08uRgIE+aYlTRSCR8wG8P0j0Ta/srUKbPySxv+xXqDXuRIChtErSIsBgORg8uNjg==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
metascraper-description@^5.10.6: metascraper-description@^5.11.0:
version "5.10.6" version "5.11.0"
resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.10.6.tgz#ebb4459a4e1acdc473534d1b898b7958b1769eb6" resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.11.0.tgz#1f7edfae7ffc353512975cafaa5b862fc5141709"
integrity sha512-d3d6UMsNnD8Dy7gxA05iTOj5QmJrFQTw1+IrW9CiOfdNsYq0H+m265a9lRaXcyJdqkzmGnv/d52C0BtUDOrkRw== integrity sha512-or62L9EbIwBxjsu6gShTKi1z1XQ4Hvml8MrdydL4tiSUJpwf+lXc6gVOP8+nlKPPRkiAhXt2M9k4mfkGoX0erQ==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
metascraper-image@^5.10.6: metascraper-image@^5.11.1:
version "5.10.6" version "5.11.1"
resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.10.6.tgz#b23ec4bfab0467342b294f3d049c7b7e9a1dd071" resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.11.1.tgz#e63e9ff045441783f9aa8c684e04927cac289e44"
integrity sha512-/+m0VaaqnBgNREun/8jcq+clc4s9Z0FAuMO/TQf/mrz9SbcmpHeTD0WpiGJgEa9zYSbuEQYHdBkxhpw+SdJPCA== integrity sha512-Nz2ZTecj2V0KgK2QE390dOSedppaG2PtUBrTz/oaFLMZEReBtMVrcygYm9VuuTpa6XwkubvuBaouCRah12zdfg==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.11.1"
metascraper-lang-detector@^4.10.2: metascraper-lang-detector@^4.10.2:
version "4.10.2" version "4.10.2"
@ -6206,74 +6249,74 @@ metascraper-lang-detector@^4.10.2:
franc "~4.0.0" franc "~4.0.0"
iso-639-3 "~1.1.0" iso-639-3 "~1.1.0"
metascraper-lang@^5.10.6: metascraper-lang@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.10.6.tgz#d4130257f6604095e9af8d796a6bde5815f6c667" resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.10.7.tgz#fe07c359b757ef3c5ba0a330da97173945196fde"
integrity sha512-JDhNbP1iSnPV7d6PklIIdBSzlwqbtvH+n810Isa5/PGuvUkJzNkTAUN+eTM1i6YcTlMp1N2gYsQG9uwfpMwFog== integrity sha512-ulLq7g+X4F9XzoScNflkhwWUuoSycbOqJ8j7vT6E/sHYPid+BdUnw2yaxAqXOdsrsbUEWzkiDwrJiq37XWCpDA==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
metascraper-logo@^5.10.6: metascraper-logo@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.10.6.tgz#22223ce79e4017f159b2a9ddc311d2eb636043b5" resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.10.7.tgz#335812b9aae5d814b22294cdd402d62fa14a46f1"
integrity sha512-/uGW+X43T6Oj5DxWqAhANII9BdhQuM+e7O6/Vu116uLqW6cOJ/RDp5qp7ngKF41L0zCLd8Q2Xw2nduHi6tC4Uw== integrity sha512-Ic9WgvRqm3pUlsMfSirzCK9+qmQ9pbv/u9APn8PM5y66zNJoSCOVWbEIoEA0bVPHbGKGKFvsgrOm3VkMoVUF0Q==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
metascraper-publisher@^5.10.6: metascraper-publisher@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.10.6.tgz#95dde6ecef3c7b890ac625893ab374096bebe1f4" resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.10.7.tgz#7add40be1625a4215e2ec36dedaaaf213c1870e2"
integrity sha512-4jTOpbIwXBADl6z39UzQ4DZLeVoj4Q+5dcHbEgGn9MQ9878FgxiJKyrHzYvqfe9fRNd0PcaFMuuwLyhz58haoA== integrity sha512-aRws5ksH+gzb49nc41oJdoJS2jG/2vC3GCi68n4Evy/TL7TYCnbZXqcgT5U0ne1kFxCZlHB6rl0DcXkx/JlT5Q==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
metascraper-soundcloud@^5.10.6: metascraper-soundcloud@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.10.6.tgz#7aff2e17214b6939719ea726a0f5a5fe2a48c5a5" resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.10.7.tgz#64a5518324ea69576ab9c206f29a60160fb89e91"
integrity sha512-WO+B81e04Hng4/YOtq4dpNv9CrGWVemrNuZk3iIJU+B+gF3YpzdERxS4aIpM7KHcY/c8xu3xDM3LrSUWXWjM3g== integrity sha512-KPM/g+l1m0tp4YOu8qF1RUT7yondaY1S/0aieUUFck/iE3VA1i3MJTLLygIc+67fcbHqcz60qfPTOvYbl6sIPw==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
tldts "~5.6.3" tldts "~5.6.3"
metascraper-title@^5.10.6: metascraper-title@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.10.6.tgz#0fd9a9bed7a0b990663086cdab45d11cd8cd3c7d" resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.10.7.tgz#037aaa8cbdc79d1dde186887eb2bba542281315e"
integrity sha512-x4P8zr0x6Gh3gt26tf2xfjikG9xNS9MC3z4N2VP+OrYNuCc7Vz6TU+DR/DLAeZphsb1flgTd3P4iUfPUcWVTEQ== integrity sha512-iQYaMdGpBPj6dyk7rbP+zYo7EroC/1yY27jocAqUnMRTfrHXTR7kGucR0vi4y14BiFRLBTLSNbZbM4KAUQsFjg==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
lodash "~4.17.15" lodash "~4.17.15"
metascraper-url@^5.10.6: metascraper-url@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.10.6.tgz#45f0ea173fecfe56d60b3cddd3c018f9f4fd9b92" resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.10.7.tgz#42b71c8540c13baafb7757972ea672721d63e019"
integrity sha512-7F6uAsI27iVXxUMwwzXH0ret81CX1jgtoGCMz+TvZkyS0z4aUs0r8QpYRYEQuXrW+JawRVik0up54F/ScslObQ== integrity sha512-z1LBPTupU5cF36/i/iGe0rzLbO7iGBSdbgEOztLcnIhnMC8Nl9xjvIrlvNciKTMxDyr3JGrvFFWugFzwMzVoQg==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
metascraper-video@^5.10.6: metascraper-video@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.10.6.tgz#8425d2dfc378b20612e8ef9324989a33bc0341b3" resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.10.7.tgz#2892819a3613ddac115ada7fb1b28d74a646f974"
integrity sha512-DzWBCe/z86QFv6mN9ZDmvk32FMWv+nPDSkyMEL7RCU6VeQOFFAOjwhDglp2qBMs8Xif358bQ4H/0akLZpDUfvw== integrity sha512-+fjiL/Vq0DGd7dMvBTdFKrOK2YH2myHssSChRZVZLl3gzyo4YEWpnOWBhfuoky0caOcs9+RVAxF9pE1TdTFmbA==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
lodash "~4.17.15" lodash "~4.17.15"
metascraper-youtube@^5.10.6: metascraper-youtube@^5.10.7:
version "5.10.6" version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.10.6.tgz#6cacabb1791b06ed98a7da69aa00c1c6c50a2dfe" resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.10.7.tgz#6c3313563ee57cb71c496fa99c129ad41fe22291"
integrity sha512-Yl5kEFawqpSGmVSG2yTVZj7mGfRSFGQ2A4cxpqSbaPIUCGJwG9BUJkMzyUG0m6jGrg0zI5CmeZGNBAXzgKGz4g== integrity sha512-T4ZawYpW/2lyoVUY9RM92YCxkuyhNDXFxg8XAG9u2hoNZ5elrHLOv67ao5zMHa7IXZY3A7IGDOBd1NfoShnToA==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
get-video-id "~3.1.4" get-video-id "~3.1.4"
is-reachable "~4.0.0" is-reachable "~4.0.0"
p-locate "~4.1.0" p-locate "~4.1.0"
metascraper@^5.10.6: metascraper@^5.11.0:
version "5.10.6" version "5.11.0"
resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.10.6.tgz#d1249577a768566b86bd099cc2256df45cf05181" resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.11.0.tgz#bbb25eb055c0ec03992df99c10c48057751ee56b"
integrity sha512-mPEDvoyHLWb0AlTE05W43vfKGgBJ99s5AYAhB4IiRgGR9uq8j7/ktTZaS/+pyFopYrEoe71L/k4KbYgNPQRASA== integrity sha512-IocQqdSQnOpbai0X9Cu37w/AKeSfU513MCfaFzzcvWgI8s6mGQ1DWRFPvkx0ahtixpP0/ifE4t7ycODrtepRxQ==
dependencies: dependencies:
"@metascraper/helpers" "^5.10.6" "@metascraper/helpers" "^5.10.7"
cheerio "~1.0.0-rc.3" cheerio "~1.0.0-rc.3"
cheerio-advanced-selectors "~2.0.1" cheerio-advanced-selectors "~2.0.1"
lodash "~4.17.15" lodash "~4.17.15"
@ -6325,24 +6368,12 @@ migrate@^1.6.2:
mkdirp "^0.5.1" mkdirp "^0.5.1"
slug "^0.9.2" slug "^0.9.2"
mime-db@1.40.0:
version "1.40.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
mime-db@1.43.0: mime-db@1.43.0:
version "1.43.0" version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.22, mime-types@~2.1.24: mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.22, mime-types@~2.1.24, mime-types@~2.1.26:
version "2.1.24"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
dependencies:
mime-db "1.40.0"
mime-types@~2.1.26:
version "2.1.26" version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
@ -6702,10 +6733,10 @@ normalize-url@~4.2.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.2.0.tgz#e747f16b58e6d7f391495fd86415fa04ec7c9897" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.2.0.tgz#e747f16b58e6d7f391495fd86415fa04ec7c9897"
integrity sha512-n69+KXI+kZApR+sPwSkoAXpGlNkaiYyoHHqKOFPjJWvwZpew/EjKvuPE4+tStNgb42z5yLtdakgZCQI+LalSPg== integrity sha512-n69+KXI+kZApR+sPwSkoAXpGlNkaiYyoHHqKOFPjJWvwZpew/EjKvuPE4+tStNgb42z5yLtdakgZCQI+LalSPg==
normalize-url@~4.5.0: normalize-url@~5.0.0:
version "4.5.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-5.0.0.tgz#f46c9dc20670495e4e18fbd1b4396e41d199f63c"
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== integrity sha512-bAEm2fx8Dq/a35Z6PIRkkBBJvR56BbEJvhpNtvCZ4W9FyORSna77fn+xtYFjqk5JpBS+fMnAOG/wFgkQBmB7hw==
npm-bundled@^1.0.1: npm-bundled@^1.0.1:
version "1.0.6" version "1.0.6"
@ -7328,7 +7359,7 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.24, psl@^1.1.28: psl@^1.1.28:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd" resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd"
integrity sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag== integrity sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==
@ -7351,11 +7382,6 @@ punycode2@~1.0.0:
resolved "https://registry.yarnpkg.com/punycode2/-/punycode2-1.0.0.tgz#e2b4b9a9a8ff157d0b84438e203181ee7892dfd8" resolved "https://registry.yarnpkg.com/punycode2/-/punycode2-1.0.0.tgz#e2b4b9a9a8ff157d0b84438e203181ee7892dfd8"
integrity sha1-4rS5qaj/FX0LhEOOIDGB7niS39g= integrity sha1-4rS5qaj/FX0LhEOOIDGB7niS39g=
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0, punycode@^2.1.1: punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@ -7506,6 +7532,23 @@ realpath-native@^1.1.0:
dependencies: dependencies:
util.promisify "^1.0.0" util.promisify "^1.0.0"
redis-commands@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
redis-errors@^1.0.0, redis-errors@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
redis-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
dependencies:
redis-errors "^1.0.0"
referrer-policy@1.2.0: referrer-policy@1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e"
@ -7628,10 +7671,10 @@ request-promise-native@^1.0.7, request-promise-native@^1.0.8:
stealthy-require "^1.1.1" stealthy-require "^1.1.1"
tough-cookie "^2.3.3" tough-cookie "^2.3.3"
request@^2.88.0, request@~2.88.0: request@^2.88.0, request@~2.88.2:
version "2.88.0" version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies: dependencies:
aws-sign2 "~0.7.0" aws-sign2 "~0.7.0"
aws4 "^1.8.0" aws4 "^1.8.0"
@ -7640,7 +7683,7 @@ request@^2.88.0, request@~2.88.0:
extend "~3.0.2" extend "~3.0.2"
forever-agent "~0.6.1" forever-agent "~0.6.1"
form-data "~2.3.2" form-data "~2.3.2"
har-validator "~5.1.0" har-validator "~5.1.3"
http-signature "~1.2.0" http-signature "~1.2.0"
is-typedarray "~1.0.0" is-typedarray "~1.0.0"
isstream "~0.1.2" isstream "~0.1.2"
@ -7650,7 +7693,7 @@ request@^2.88.0, request@~2.88.0:
performance-now "^2.1.0" performance-now "^2.1.0"
qs "~6.5.2" qs "~6.5.2"
safe-buffer "^5.1.2" safe-buffer "^5.1.2"
tough-cookie "~2.4.3" tough-cookie "~2.5.0"
tunnel-agent "^0.6.0" tunnel-agent "^0.6.0"
uuid "^3.3.2" uuid "^3.3.2"
@ -7754,6 +7797,11 @@ rimraf@^3.0.0:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rosie@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/rosie/-/rosie-2.0.1.tgz#c250c4787ce450b72aa9eff26509f68589814fa2"
integrity sha1-wlDEeHzkULcqqe/yZQn2hYmBT6I=
router-ips@^1.0.0: router-ips@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/router-ips/-/router-ips-1.0.0.tgz#44e00858ebebc0133d58e40b2cd8a1fbb04203f5" resolved "https://registry.yarnpkg.com/router-ips/-/router-ips-1.0.0.tgz#44e00858ebebc0133d58e40b2cd8a1fbb04203f5"
@ -8208,6 +8256,11 @@ stacktrace-js@^2.0.0:
stack-generator "^2.0.1" stack-generator "^2.0.1"
stacktrace-gps "^3.0.1" stacktrace-gps "^3.0.1"
standard-as-callback@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126"
integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==
static-extend@^0.1.1: static-extend@^0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@ -8673,7 +8726,7 @@ touch@^3.1.0:
dependencies: dependencies:
nopt "~1.0.10" nopt "~1.0.10"
tough-cookie@^2.3.3: tough-cookie@^2.3.3, tough-cookie@~2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@ -8690,14 +8743,6 @@ tough-cookie@^3.0.1:
psl "^1.1.28" psl "^1.1.28"
punycode "^2.1.1" punycode "^2.1.1"
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
tr46@^1.0.1: tr46@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -14,9 +14,8 @@ Feature: Tags and Categories
looking at the popularity of a tag. looking at the popularity of a tag.
Background: Background:
Given my user account has the role "admin" Given I am logged in with a "admin" role
And we have a selection of tags and categories as well as posts And we have a selection of tags and categories as well as posts
And I am logged in
Scenario: See an overview of categories Scenario: See an overview of categories
When I navigate to the administration dashboard When I navigate to the administration dashboard

View File

@ -1,5 +1,8 @@
import { When, Then } from "cypress-cucumber-preprocessor/steps"; import { When, Then } from "cypress-cucumber-preprocessor/steps";
import locales from '../../../webapp/locales'
import orderBy from 'lodash/orderBy'
const languages = orderBy(locales, 'name')
const narratorAvatar = const narratorAvatar =
"https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg"; "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg";
@ -84,3 +87,82 @@ And("the post with title {string} has a ribbon for pinned posts", (title) => {
Then("I see a toaster with {string}", (title) => { Then("I see a toaster with {string}", (title) => {
cy.get(".iziToast-message").should("contain", title); cy.get(".iziToast-message").should("contain", title);
}) })
Then("I should be able to {string} a teaser image", condition => {
cy.reload()
const teaserImageUpload = (condition === 'change') ? "humanconnection.png" : "onourjourney.png";
cy.fixture(teaserImageUpload).as('postTeaserImage').then(function() {
cy.get("#postdropzone").upload(
{ fileContent: this.postTeaserImage, fileName: teaserImageUpload, mimeType: "image/png" },
{ subjectType: "drag-n-drop", force: true }
);
})
})
Then('confirm crop', () => {
cy.get('.crop-confirm')
.click()
})
Then("I add all required fields", () => {
cy.get('input[name="title"]')
.type('new post')
.get(".editor .ProseMirror")
.type('new post content')
.get(".categories-select .base-button")
.first()
.click()
.get('.ds-flex-item > .ds-form-item .ds-select ')
.click()
.get('.ds-select-option')
.eq(languages.findIndex(l => l.code === 'en'))
.click()
})
Then("the post was saved successfully with the {string} teaser image", condition => {
cy.get(".ds-card-content > .ds-heading")
.should("contain", condition === 'updated' ? 'to be updated' : 'new post')
.get(".content")
.should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
.get('.post-page img')
.should("have.attr", "src")
.and("contains", condition === 'updated' ? 'humanconnection' : 'onourjourney')
})
Then("the first image should be removed from the preview", () => {
cy.fixture("humanconnection.png").as('postTeaserImage').then(function() {
cy.get("#postdropzone")
.children()
.get('img.thumbnail-preview')
.should('have.length', 1)
.and('have.attr', 'src')
.and('contain', this.postTeaserImage)
})
})
Then('the {string} post was saved successfully without a teaser image', condition => {
cy.get(".ds-card-content > .ds-heading")
.should("contain", condition === 'updated' ? 'to be updated' : 'new post')
.get(".content")
.should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
.get('.post-page')
.should('exist')
.get('.post-page img.ds-card-image')
.should('not.exist')
})
Then('I should be able to remove it', () => {
cy.get('.crop-cancel')
.click()
})
When('my post has a teaser image', () => {
cy.get('.contribution-image')
.should('exist')
.and('have.attr', 'src')
})
Then('I should be able to remove the image', () => {
cy.get('.delete-image')
.click()
})

View File

@ -30,16 +30,24 @@ Given("I see David Irving's post on the post page", page => {
}) })
Given('I am logged in with a {string} role', role => { Given('I am logged in with a {string} role', role => {
cy.factory().create('User', { cy.factory().build('user', {
termsAndConditionsAgreedVersion: VERSION,
role,
name: `${role} is my name`
}, {
email: `${role}@example.org`, email: `${role}@example.org`,
password: '1234', password: '1234',
termsAndConditionsAgreedVersion: VERSION,
role
})
cy.login({
email: `${role}@example.org`,
password: '1234'
}) })
cy.neode()
.first("User", {
name: `${role} is my name`,
})
.then(user => {
return new Cypress.Promise((resolve, reject) => {
return user.toJson().then((user) => resolve(user))
})
})
.then(user => cy.login(user))
}) })
When('I click on "Report Post" from the content menu of the post', () => { When('I click on "Report Post" from the content menu of the post', () => {
@ -127,7 +135,7 @@ Given('somebody reported the following posts:', table => {
password: '1234' password: '1234'
} }
cy.factory() cy.factory()
.create('User', submitter) .build('user', {}, submitter)
.authenticateAs(submitter) .authenticateAs(submitter)
.mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) { .mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) { fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
@ -166,8 +174,9 @@ Then('I can visit the post page', () => {
When("they have a post someone has reported", () => { When("they have a post someone has reported", () => {
cy.factory() cy.factory()
.create("Post", { .build("post", {
authorId: 'annnoying-user',
title, title,
}, {
authorId: 'annnoying-user',
}); });
}) })

View File

@ -25,7 +25,6 @@ const narratorParams = {
name: "Peter Pan", name: "Peter Pan",
slug: "peter-pan", slug: "peter-pan",
avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg", avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg",
...loginCredentials,
...termsAndConditionsAgreedVersion, ...termsAndConditionsAgreedVersion,
}; };
@ -33,65 +32,82 @@ const annoyingParams = {
email: "spammy-spammer@example.org", email: "spammy-spammer@example.org",
slug: 'spammy-spammer', slug: 'spammy-spammer',
password: "1234", password: "1234",
...termsAndConditionsAgreedVersion
}; };
Given("I am logged in", () => { Given("I am logged in", () => {
cy.login(loginCredentials); cy.neode()
.first("User", {
name: narratorParams.name
})
.then(user => {
return new Cypress.Promise((resolve, reject) => {
return user.toJson().then((user) => resolve(user))
})
})
.then(user => cy.login(user))
}); });
Given("I log in as {string}", name => {
cy.logout()
cy.neode()
.first("User", {
name
})
.then(user => {
return new Cypress.Promise((resolve, reject) => {
return user.toJson().then((user) => resolve(user))
})
})
.then(user => cy.login(user))
})
Given("the {string} user searches for {string}", (_, postTitle) => { Given("the {string} user searches for {string}", (_, postTitle) => {
cy.logout() cy.logout()
.login({ email: annoyingParams.email, password: '1234' }) cy.neode()
.get(".searchable-input .ds-select input") .first("User", {
id: "annoying-user"
})
.then(user => {
return new Cypress.Promise((resolve, reject) => {
return user.toJson().then((user) => resolve(user))
})
})
.then(user => cy.login(user))
cy.get(".searchable-input .ds-select input")
.focus() .focus()
.type(postTitle); .type(postTitle);
}); });
Given("we have a selection of categories", () => { Given("we have a selection of categories", () => {
cy.createCategories("cat0", "just-for-fun"); cy.factory().build('category', { id: "cat0", slug: "just-for-fun" });
}); });
Given("we have a selection of tags and categories as well as posts", () => { Given("we have a selection of tags and categories as well as posts", () => {
cy.createCategories("cat12")
.factory()
.create("Tag", {
id: "Ecology"
})
.create("Tag", {
id: "Nature"
})
.create("Tag", {
id: "Democracy"
});
cy.factory() cy.factory()
.create("User", { .build('category', { id: 'cat12', name: "Just For Fun", icon: "smile", })
id: 'a1' .build('category', { id: 'cat121', name: "Happiness & Values", icon: "heart-o"})
}) .build('category', { id: 'cat122', name: "Health & Wellbeing", icon: "medkit"})
.create("Post", { .build("tag", { id: "Ecology" })
.build("tag", { id: "Nature" })
.build("tag", { id: "Democracy" })
.build("user", { id: 'a1' })
.build("post", {}, {
authorId: 'a1', authorId: 'a1',
tagIds: ["Ecology", "Nature", "Democracy"], tagIds: ["Ecology", "Nature", "Democracy"],
categoryIds: ["cat12"] categoryIds: ["cat12"]
}) })
.create("Post", { .build("post", {}, {
authorId: 'a1', authorId: 'a1',
tagIds: ["Nature", "Democracy"], tagIds: ["Nature", "Democracy"],
categoryIds: ["cat121"] categoryIds: ["cat121"]
});
cy.factory()
.create("User", {
id: 'a2'
}) })
.create("Post", { .build("user", { id: 'a2' })
.build("post", {}, {
authorId: 'a2', authorId: 'a2',
tagIds: ['Nature', 'Democracy'], tagIds: ['Nature', 'Democracy'],
categoryIds: ["cat12"] categoryIds: ["cat12"]
}); })
cy.factory() .build("post", {}, {
.create("Post", {
authorId: narratorParams.id,
tagIds: ['Democracy'], tagIds: ['Democracy'],
categoryIds: ["cat122"] categoryIds: ["cat122"]
}) })
@ -99,23 +115,22 @@ Given("we have a selection of tags and categories as well as posts", () => {
Given("we have the following user accounts:", table => { Given("we have the following user accounts:", table => {
table.hashes().forEach(params => { table.hashes().forEach(params => {
cy.factory().create("User", { cy.factory().build("user", {
...params, ...params,
...termsAndConditionsAgreedVersion ...termsAndConditionsAgreedVersion
}); }, params);
}); });
}); });
Given("I have a user account", () => { Given("I have a user account", () => {
cy.factory().create("User", narratorParams); cy.factory().build("user", narratorParams, loginCredentials);
}); });
Given("my user account has the role {string}", role => { Given("my user account has the role {string}", role => {
cy.factory().create("User", { cy.factory().build("user", {
role, role,
...loginCredentials,
...termsAndConditionsAgreedVersion, ...termsAndConditionsAgreedVersion,
}); }, loginCredentials);
}); });
When("I log out", cy.logout); When("I log out", cy.logout);
@ -130,8 +145,17 @@ When("I visit the {string} page", page => {
When("a blocked user visits the post page of one of my authored posts", () => { When("a blocked user visits the post page of one of my authored posts", () => {
cy.logout() cy.logout()
.login({ email: annoyingParams.email, password: annoyingParams.password }) cy.neode()
.openPage('/post/previously-created-post') .first("User", {
name: 'Harassing User'
})
.then(user => {
return new Cypress.Promise((resolve, reject) => {
return user.toJson().then((user) => resolve(user))
})
})
.then(user => cy.login(user))
cy.openPage('post/previously-created-post')
}) })
Given("I am on the {string} page", page => { Given("I am on the {string} page", page => {
@ -139,7 +163,7 @@ Given("I am on the {string} page", page => {
}); });
When("I fill in my email and password combination and click submit", () => { When("I fill in my email and password combination and click submit", () => {
cy.login(loginCredentials); cy.manualLogin(loginCredentials);
}); });
When(/(?:when )?I refresh the page/, () => { When(/(?:when )?I refresh the page/, () => {
@ -203,33 +227,29 @@ When("I press {string}", label => {
cy.contains(label).click(); cy.contains(label).click();
}); });
Given("we have this user in our database:", table => { Given("we have the following comments in our database:", table => {
const [firstRow] = table.hashes() table.hashes().forEach((attributesOrOptions, i) => {
cy.factory().create('User', firstRow) cy.factory().build("comment", {
}) ...attributesOrOptions,
}, {
Given("we have the following posts in our database:", table => { ...attributesOrOptions,
cy.factory().create('Category', { });
id: `cat-456`,
name: "Just For Fun",
slug: `just-for-fun`,
icon: "smile"
})
table.hashes().forEach(({
...postAttributes
}, i) => {
postAttributes = {
...postAttributes,
deleted: Boolean(postAttributes.deleted),
disabled: Boolean(postAttributes.disabled),
pinned: Boolean(postAttributes.pinned),
categoryIds: ['cat-456']
}
cy.factory().create("Post", postAttributes);
}) })
}); });
Given("we have the following posts in our database:", table => {
table.hashes().forEach((attributesOrOptions, i) => {
cy.factory().build("post", {
...attributesOrOptions,
deleted: Boolean(attributesOrOptions.deleted),
disabled: Boolean(attributesOrOptions.disabled),
pinned: Boolean(attributesOrOptions.pinned),
}, {
...attributesOrOptions,
});
})
})
Then("I see a success message:", message => { Then("I see a success message:", message => {
cy.contains(message); cy.contains(message);
}); });
@ -242,15 +262,20 @@ When(
"I click on the big plus icon in the bottom right corner to create post", "I click on the big plus icon in the bottom right corner to create post",
() => { () => {
cy.get(".post-add-button").click(); cy.get(".post-add-button").click();
cy.location("pathname").should('eq', '/post/create')
} }
); );
Given("I previously created a post", () => { Given("I previously created a post", () => {
lastPost.authorId = narratorParams.id lastPost = {
lastPost.title = "previously created post"; lastPost,
lastPost.content = "with some content"; title: "previously created post",
content: "with some content",
};
cy.factory() cy.factory()
.create("Post", lastPost); .build("post", lastPost, {
authorId: narratorParams.id
});
}); });
When("I choose {string} as the title of the post", title => { When("I choose {string} as the title of the post", title => {
@ -311,17 +336,27 @@ Then(
cy.visit(route, { cy.visit(route, {
failOnStatusCode: false failOnStatusCode: false
}); });
cy.get(".error").should("contain", message); cy.get(".error-message").should("contain", message);
} }
); );
Given("my user account has the following login credentials:", table => { Given("I am logged in with these credentials:", table => {
loginCredentials = table.hashes()[0]; loginCredentials = table.hashes()[0];
cy.debug(); cy.debug();
cy.factory().create("User", { cy.factory().build("user", {
...termsAndConditionsAgreedVersion, ...termsAndConditionsAgreedVersion,
...loginCredentials name: loginCredentials.email,
}); }, loginCredentials);
cy.neode()
.first("User", {
name: loginCredentials.email,
})
.then(user => {
return new Cypress.Promise((resolve, reject) => {
return user.toJson().then((user) => resolve(user))
})
})
.then(user => cy.login(user))
}); });
When("I fill the password form with:", table => { When("I fill the password form with:", table => {
@ -340,45 +375,16 @@ When("submit the form", () => {
Then("I cannot login anymore with password {string}", password => { Then("I cannot login anymore with password {string}", password => {
cy.reload(); cy.reload();
const { const { email } = loginCredentials
email cy.manualLogin({ email, password })
} = loginCredentials; .get(".iziToast-wrapper").should("contain", "Incorrect email address or password.");
cy.visit(`/login`);
cy.get("input[name=email]")
.trigger("focus")
.type(email);
cy.get("input[name=password]")
.trigger("focus")
.type(password);
cy.get("button[name=submit]")
.as("submitButton")
.click();
cy.get(".iziToast-wrapper").should(
"contain",
"Incorrect email address or password."
);
}); });
Then("I can login successfully with password {string}", password => { Then("I can login successfully with password {string}", password => {
cy.reload(); cy.reload();
cy.login({ const { email } = loginCredentials
...loginCredentials, cy.manualLogin({ email, password })
...{ .get(".iziToast-wrapper").should("contain", "You are logged in!");
password
}
});
cy.get(".iziToast-wrapper").should("contain", "You are logged in!");
});
When("I log in with the following credentials:", table => {
const {
email,
password
} = table.hashes()[0];
cy.login({
email,
password
});
}); });
When("open the notification menu and click on the first item", () => { When("open the notification menu and click on the first item", () => {
@ -428,12 +434,11 @@ Then("there are no notifications in the top menu", () => {
}); });
Given("there is an annoying user called {string}", name => { Given("there is an annoying user called {string}", name => {
cy.factory().create("User", { cy.factory().build("user", {
...annoyingParams,
id: "annoying-user", id: "annoying-user",
name, name,
...termsAndConditionsAgreedVersion, ...termsAndConditionsAgreedVersion,
}); }, annoyingParams);
}); });
Given("there is an annoying user who has muted me", () => { Given("there is an annoying user who has muted me", () => {
@ -451,15 +456,15 @@ Given("there is an annoying user who has muted me", () => {
}); });
Given("I am on the profile page of the annoying user", name => { Given("I am on the profile page of the annoying user", name => {
cy.openPage("/profile/annoying-user/spammy-spammer"); cy.openPage("profile/annoying-user/spammy-spammer");
}); });
When("I visit the profile page of the annoying user", name => { When("I visit the profile page of the annoying user", name => {
cy.openPage("/profile/annoying-user"); cy.openPage("profile/annoying-user");
}); });
When("I ", name => { When("I ", name => {
cy.openPage("/profile/annoying-user"); cy.openPage("profile/annoying-user");
}); });
When( When(
@ -498,12 +503,11 @@ Given("I follow the user {string}", name => {
}); });
Given('{string} wrote a post {string}', (_, title) => { Given('{string} wrote a post {string}', (_, title) => {
cy.createCategories("cat21") cy.factory()
.factory() .build("post", {
.create("Post", {
authorId: 'annoying-user',
title, title,
categoryIds: ["cat21"] }, {
authorId: 'annoying-user',
}); });
}); });
@ -521,12 +525,11 @@ Then("I get removed from his follower collection", () => {
}); });
Given("I wrote a post {string}", title => { Given("I wrote a post {string}", title => {
cy.createCategories(`cat213`, title) cy.factory()
.factory() .build("post", {
.create("Post", {
authorId: narratorParams.id,
title, title,
categoryIds: ["cat213"] }, {
authorId: narratorParams.id,
}); });
}); });
@ -552,22 +555,24 @@ When("I block the user {string}", name => {
.then(blockedUser => { .then(blockedUser => {
cy.neode() cy.neode()
.first("User", { .first("User", {
name: narratorParams.name id: narratorParams.id
}) })
.relateTo(blockedUser, "blocked"); .relateTo(blockedUser, "blocked");
}); });
}); });
When("I log in with:", table => { When("a user has blocked me", () => {
const [firstRow] = table.hashes(); cy.neode()
const { .first("User", {
Email, name: narratorParams.name
Password })
} = firstRow; .then(blockedUser => {
cy.login({ cy.neode()
email: Email, .first("User", {
password: Password name: 'Harassing User'
}); })
.relateTo(blockedUser, "blocked");
});
}); });
Then("I see only one post with the title {string}", title => { Then("I see only one post with the title {string}", title => {
@ -581,6 +586,27 @@ Then("they should not see the comment from", () => {
cy.get(".base-card").children().should('not.have.class', 'comment-form') cy.get(".base-card").children().should('not.have.class', 'comment-form')
}) })
Then("they should see a text explaining commenting is not possible", () => { Then("they should see a text explaining why commenting is not possible", () => {
cy.get('.ds-placeholder').should('contain', "Commenting is not possible at this time on this post.") cy.get('.ds-placeholder').should('contain', "Commenting is not possible at this time on this post.")
}) })
Then("I should see no users in my blocked users list", () => {
cy.get('.ds-placeholder')
.should('contain', "So far, you have not blocked anybody.")
})
Then("I {string} see {string} from the content menu in the user info box", (condition, link) => {
cy.get(".user-content-menu .base-button").click()
cy.get(".popover .ds-menu-item-link")
.should(condition === 'should' ? 'contain' : 'not.contain', link)
})
Then('I should not see {string} button', button => {
cy.get('.ds-card-content .action-buttons')
.should('have.length', 1)
})
Then('I should see the {string} button', button => {
cy.get('.ds-card-content .action-buttons .base-button')
.should('contain', button)
})

View File

@ -62,9 +62,8 @@ Feature: Report and Moderate
Given somebody reported the following posts: Given somebody reported the following posts:
| submitterEmail | resourceId | reasonCategory | reasonDescription | | submitterEmail | resourceId | reasonCategory | reasonDescription |
| p2.submitter@example.org | p2 | other | Offensive content | | p2.submitter@example.org | p2 | other | Offensive content |
And my user account has the role "moderator" And I am logged in with a "moderator" role
And there is an annoying user who has muted me And there is an annoying user who has muted me
And I am logged in
When I click on the avatar menu in the top right corner When I click on the avatar menu in the top right corner
And I click on "Moderation" And I click on "Moderation"
Then I see all the reported posts including from the user who muted me Then I see all the reported posts including from the user who muted me

View File

@ -11,9 +11,7 @@ Feature: Notification for a mention
| Matt Rider | matt-rider | matt@example.org | 4321 | | Matt Rider | matt-rider | matt@example.org | 4321 |
Scenario: Mention another user, re-login as this user and see notifications Scenario: Mention another user, re-login as this user and see notifications
Given I log in with the following credentials: Given I log in as "Wolle aus Hamburg"
| email | password |
| wolle@example.org | 1234 |
And I start to write a new post with the title "Hey Matt" beginning with: And I start to write a new post with the title "Hey Matt" beginning with:
""" """
Big shout to our fellow contributor Big shout to our fellow contributor
@ -23,9 +21,7 @@ Feature: Notification for a mention
And I choose "en" as the language for the post And I choose "en" as the language for the post
And I click on "Save" And I click on "Save"
When I log out When I log out
And I log in with the following credentials: And I log in as "Matt Rider"
| email | password |
| matt@example.org | 4321 |
And see 1 unread notifications in the top menu And see 1 unread notifications in the top menu
And open the notification menu and click on the first item And open the notification menu and click on the first item
Then I get to the post page of ".../hey-matt" Then I get to the post page of ".../hey-matt"

View File

@ -6,8 +6,11 @@ Feature: Post Comment
Background: Background:
Given I have a user account Given I have a user account
And we have the following posts in our database: And we have the following posts in our database:
| id | title | slug | authorId | commentContent | | id | title | slug | authorId |
| bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | id-of-peter-pan | @peter-pan reply to me | | bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | id-of-peter-pan |
And we have the following comments in our database:
| postId | content | authorId |
| bWBjpkTKZp | @peter-pan reply to me | id-of-peter-pan |
And I am logged in And I am logged in
Scenario: Comment creation Scenario: Comment creation

View File

@ -0,0 +1,19 @@
Feature: Delete Teaser Image
As a user
I would like to be able to remove an image I have previously added to my Post
So that I have control over the content of my Post
Background:
Given I have a user account
Given I am logged in
Given we have the following posts in our database:
| authorId | id | title | content |
| id-of-peter-pan | p1 | Post to be updated | successfully updated |
Scenario: Delete existing image
Given I am on the 'post/edit/p1' page
And my post has a teaser image
Then I should be able to remove the image
And I click on "Save"
Then I get redirected to ".../post-to-be-updated"
And the "updated" post was saved successfully without a teaser image

View File

@ -0,0 +1,47 @@
Feature: Upload Teaser Image
As a user
I would like to be able to add a teaser image to my Post
So that I can personalize my posts
Background:
Given I have a user account
Given I am logged in
Given we have the following posts in our database:
| authorId | id | title | content |
| id-of-peter-pan | p1 | Post to be updated | successfully updated |
Scenario: Create a Post with a Teaser Image
When I click on the big plus icon in the bottom right corner to create post
Then I should be able to "add" a teaser image
And confirm crop
And I add all required fields
And I click on "Save"
Then I get redirected to ".../new-post"
And the post was saved successfully with the "new" teaser image
Scenario: Update a Post to add an image
Given I am on the 'post/edit/p1' page
And I should be able to "change" a teaser image
And confirm crop
And I click on "Save"
Then I see a toaster with "Saved!"
And I get redirected to ".../post-to-be-updated"
Then the post was saved successfully with the "updated" teaser image
Scenario: Add image, then add a different image
When I click on the big plus icon in the bottom right corner to create post
Then I should be able to "add" a teaser image
And confirm crop
And I should be able to "change" a teaser image
And confirm crop
And the first image should be removed from the preview
Scenario: Add image, then delete it
When I click on the big plus icon in the bottom right corner to create post
Then I should be able to "add" a teaser image
And I should be able to remove it
And I add all required fields
And I click on "Save"
Then I get redirected to ".../new-post"
And the "new" post was saved successfully without a teaser image

View File

@ -9,10 +9,9 @@ Feature: Change password
password or just out of an good habit, you want to change your password. password or just out of an good habit, you want to change your password.
Background: Background:
Given my user account has the following login credentials: Given I am logged in with these credentials:
| email | password | | email | password |
| user@example.org | exposed | | user@example.org | exposed |
And I am logged in
Scenario: Change my password Scenario: Change my password
Given I am on the "settings" page Given I am on the "settings" page

View File

@ -7,7 +7,7 @@ Feature: Authentication
Given I have a user account Given I have a user account
Scenario: Log in Scenario: Log in
When I visit the "/login" page When I visit the "login" page
And I fill in my email and password combination and click submit And I fill in my email and password combination and click submit
Then I can click on my profile picture in the top right corner Then I can click on my profile picture in the top right corner
And I can see my name "Peter Lustig" in the dropdown menu And I can see my name "Peter Lustig" in the dropdown menu

View File

@ -11,6 +11,7 @@ Feature: Block a User
Scenario: Block a user Scenario: Block a user
Given I am on the profile page of the annoying user Given I am on the profile page of the annoying user
When I click on "Block user" from the content menu in the user info box When I click on "Block user" from the content menu in the user info box
And I "should" see "Unblock user" from the content menu in the user info box
And I navigate to my "Blocked users" settings page And I navigate to my "Blocked users" settings page
Then I can see the following table: Then I can see the following table:
| Avatar | Name | | Avatar | Name |
@ -20,14 +21,15 @@ Feature: Block a User
Given I block the user "Harassing User" Given I block the user "Harassing User"
And I previously created a post And I previously created a post
And a blocked user visits the post page of one of my authored posts And a blocked user visits the post page of one of my authored posts
Then they should not see the comment from Then they should see a text explaining why commenting is not possible
And they should see a text explaining commenting is not possible And they should not see the comment form
Scenario: Block a previously followed user Scenario: Block a previously followed user
Given I follow the user "Harassing User" Given I follow the user "Harassing User"
When I visit the profile page of the annoying user When I visit the profile page of the annoying user
And I click on "Block user" from the content menu in the user info box And I click on "Block user" from the content menu in the user info box
And I get removed from his follower collection And I get removed from his follower collection
And I "should" see "Unblock user" from the content menu in the user info box
Scenario: Posts of blocked users are not filtered from search results Scenario: Posts of blocked users are not filtered from search results
Given "Harassing User" wrote a post "You can still see my posts" Given "Harassing User" wrote a post "You can still see my posts"
@ -44,3 +46,15 @@ Feature: Block a User
Then I should see the following posts in the select dropdown: Then I should see the following posts in the select dropdown:
| title | | title |
| previously created post | | previously created post |
Scenario: Blocked users cannot see they are blocked in their list
Given a user has blocked me
And I navigate to my "Blocked users" settings page
Then I should see no users in my blocked users list
Scenario: Blocked users should not see link or button to unblock, only blocking users
Given a user has blocked me
When I visit the profile page of the annoying user
And I "should not" see "Unblock user" from the content menu in the user info box
And I should see the "Follow" button
And I should not see "Unblock user" button

View File

@ -23,7 +23,7 @@ module.exports = (on, config) => {
config.env.NEO4J_URI = parsed.NEO4J_URI config.env.NEO4J_URI = parsed.NEO4J_URI
config.env.NEO4J_USERNAME = parsed.NEO4J_USERNAME config.env.NEO4J_USERNAME = parsed.NEO4J_USERNAME
config.env.NEO4J_PASSWORD = parsed.NEO4J_PASSWORD config.env.NEO4J_PASSWORD = parsed.NEO4J_PASSWORD
config.env.JWT_SECRET = parsed.JWT_SECRET
on('file:preprocessor', cucumber()) on('file:preprocessor', cucumber())
return config return config
} }

View File

@ -18,20 +18,24 @@ import helpers from "./helpers";
import { GraphQLClient, request } from 'graphql-request' import { GraphQLClient, request } from 'graphql-request'
import { gql } from '../../backend/src/helpers/jest' import { gql } from '../../backend/src/helpers/jest'
import config from '../../backend/src/config' import config from '../../backend/src/config'
import encode from '../../backend/src/jwt/encode'
const switchLang = name => { const switchLang = name => {
cy.get(".locale-menu").click(); cy.get(".locale-menu").click();
cy.contains(".locale-menu-popover a", name).click(); cy.contains(".locale-menu-popover a", name).click();
}; };
const authenticatedHeaders = async (variables) => { const authenticatedHeaders = (variables) => {
const mutation = gql` const mutation = gql`
mutation($email: String!, $password: String!) { mutation($email: String!, $password: String!) {
login(email: $email, password: $password) login(email: $email, password: $password)
} }
` `
const response = await request(config.GRAPHQL_URI, mutation, variables) return new Cypress.Promise((resolve, reject) => {
return { authorization: `Bearer ${response.login}` } request(config.GRAPHQL_URI, mutation, variables).then((response) => {
resolve({ authorization: `Bearer ${response.login}` })
})
})
} }
Cypress.Commands.add("switchLanguage", (name, force) => { Cypress.Commands.add("switchLanguage", (name, force) => {
@ -47,7 +51,13 @@ Cypress.Commands.add("switchLanguage", (name, force) => {
} }
}); });
Cypress.Commands.add("login", ({ email, password }) => { Cypress.Commands.add("login", user => {
const token = encode(user)
cy.setCookie('human-connection-token', token)
.visit("/")
});
Cypress.Commands.add("manualLogin", ({ email, password }) => {
cy.visit(`/login`); cy.visit(`/login`);
cy.get("input[name=email]") cy.get("input[name=email]")
.trigger("focus") .trigger("focus")
@ -58,11 +68,9 @@ Cypress.Commands.add("login", ({ email, password }) => {
cy.get("button[name=submit]") cy.get("button[name=submit]")
.as("submitButton") .as("submitButton")
.click(); .click();
cy.get(".iziToast-message").should("contain", "You are logged in!");
cy.location("pathname").should("eq", "/");
}); });
Cypress.Commands.add("logout", (email, password) => { Cypress.Commands.add("logout", () => {
cy.visit(`/logout`); cy.visit(`/logout`);
cy.location("pathname").should("contain", "/login"); // we're out cy.location("pathname").should("contain", "/login"); // we're out
}); });
@ -74,43 +82,24 @@ Cypress.Commands.add("openPage", page => {
cy.visit(`/${page}`); cy.visit(`/${page}`);
}); });
Cypress.Commands.add("createCategories", (id, slug) => {
cy.neode()
.create("Category", {
id: `${id}`,
name: "Just For Fun",
slug: `${slug}`,
icon: "smile"
})
.create("Category", {
id: `${id}1`,
name: "Happiness & Values",
icon: "heart-o"
})
.create("Category", {
id: `${id}2`,
name: "Health & Wellbeing",
icon: "medkit"
});
});
Cypress.Commands.add( Cypress.Commands.add(
'authenticateAs', 'authenticateAs',
async ({email, password}) => { ({email, password}) => {
const headers = await authenticatedHeaders({ email, password }) return new Cypress.Promise((resolve, reject) => {
return new GraphQLClient(config.GRAPHQL_URI, { headers }) authenticatedHeaders({ email, password }).then((headers) => {
} resolve(new GraphQLClient(config.GRAPHQL_URI, { headers }))
) })
})
})
Cypress.Commands.add( Cypress.Commands.add(
'mutate', 'mutate',
{ prevSubject: true }, { prevSubject: true },
async (graphQLClient, mutation, variables) => { (graphQLClient, mutation, variables) => {
await graphQLClient.request(mutation, variables) return new Cypress.Promise((resolve, reject) => {
return graphQLClient graphQLClient.request(mutation, variables).then(() => resolve(graphQLClient))
} })
) })
// //
// //

View File

@ -1,4 +1,4 @@
import Factory from '../../backend/src/factories' import Factory, { cleanDatabase } from '../../backend/src/db/factories'
import { getDriver, getNeode } from '../../backend/src/db/neo4j' import { getDriver, getNeode } from '../../backend/src/db/neo4j'
const neo4jConfigs = { const neo4jConfigs = {
@ -6,51 +6,38 @@ const neo4jConfigs = {
username: Cypress.env('NEO4J_USERNAME'), username: Cypress.env('NEO4J_USERNAME'),
password: Cypress.env('NEO4J_PASSWORD') password: Cypress.env('NEO4J_PASSWORD')
} }
const neo4jDriver = getDriver(neo4jConfigs)
const neodeInstance = getNeode(neo4jConfigs) const neodeInstance = getNeode(neo4jConfigs)
const factoryOptions = { neo4jDriver, neodeInstance }
const factory = Factory(factoryOptions)
beforeEach(async () => { beforeEach(() => cleanDatabase())
await factory.cleanDatabase()
})
Cypress.Commands.add('neode', () => { Cypress.Commands.add('neode', () => {
return neodeInstance return neodeInstance
}) })
Cypress.Commands.add( Cypress.Commands.add(
'first', 'first',
{ prevSubject: true }, { prevSubject: true },
async (neode, model, properties) => { (neode, model, properties) => {
return neode.first(model, properties) return neode.first(model, properties)
} }
) )
Cypress.Commands.add( Cypress.Commands.add(
'relateTo', 'relateTo',
{ prevSubject: true }, { prevSubject: true },
async (node, otherNode, relationship) => { (node, otherNode, relationship) => {
return node.relateTo(otherNode, relationship) return node.relateTo(otherNode, relationship)
} }
) )
Cypress.Commands.add('factory', () => { Cypress.Commands.add('factory', () => Factory)
return Factory(factoryOptions)
})
Cypress.Commands.add( Cypress.Commands.add(
'create', 'build',
{ prevSubject: true }, { prevSubject: true },
async (factory, node, properties) => { (factory, name, atrributes, options) => {
await factory.create(node, properties) return new Cypress.Promise((resolve, reject) => {
return factory return factory.build(name, atrributes, options).then(() => resolve(factory))
})
} }
) )
Cypress.Commands.add(
'relate',
{ prevSubject: true },
async (factory, node, relationship, properties) => {
await factory.relate(node, relationship, properties)
return factory
}
)

View File

@ -2,3 +2,5 @@ secrets.yaml
configmap.yaml configmap.yaml
**/secrets.yaml **/secrets.yaml
**/configmap.yaml **/configmap.yaml
**/staging-values.yaml
**/production-values.yaml

View File

@ -1,11 +1,10 @@
# Human-Connection Nitro \| Deployment Configuration # Human-Connection Nitro \| Deployment Configuration
We deploy with [kubernetes](https://kubernetes.io/). In order to deploy your own There are a couple different ways we have tested to deploy an instance of Human Connection, with [kubernetes](https://kubernetes.io/) and via [Helm](https://helm.sh/docs/). In order to manage your own
network you have to [install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) network, you have to [install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/), [install Helm](https://helm.sh/docs/intro/install/) (optional, but the preferred way),
and get a kubernetes cluster. and set up a kubernetes cluster. Since there are many different options to host your cluster, we won't go into specifics here.
We have tested two different kubernetes providers: [Minikube](./minikube/README.md) We have tested two different kubernetes providers: [Minikube](./minikube/README.md)
and [Digital Ocean](./digital-ocean/README.md). and [Digital Ocean](./digital-ocean/README.md).
Check out the specific documentation for your provider. After that, learn how Check out the specific documentation for your provider. After that, choose whether you want to go with the recommended deploy option [Helm](./helm/README.md), or use kubernetes to apply the configuration for [Human Connection](./human-connection/README.md).
to apply the specific kubernetes configuration for [Human Connection](./human-connection/README.md).

View File

@ -0,0 +1,22 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "0.3.1"
description: A Helm chart for Human Connection
name: human-connection
version: 0.1.0

View File

@ -0,0 +1,72 @@
# Helm installation of Human Connection
Deploying Human Connection with Helm is very straight forward. All you have to
do is to change certain parameters, like domain names and API keys, then you
just install our provided Helm chart to your cluster.
## Configuration
You can customize the network with your configuration by changing the `values.yaml`, all variables will be available as
environment variables in your deployed kubernetes pods.
Probably you want to change this environment variable to your actual domain:
```bash
# in folder /deployment/helm
CLIENT_URI: "https://develop.human-connection.org"
```
If you want to edit secrets, you have to `base64` encode them. See [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret-manually). You can also use `helm-secrets`, but we have yet to test it.
```bash
# example how to base64 a string:
$ echo -n 'admin' | base64
YWRtaW4=
```
Those secrets get `base64` decoded and are available as environment variables in
your deployed kubernetes pods.
# https
If you start with setting up the `https`, when you install the app, it will automatically take care of the certificates for you.
First check that you are using `Helm v3`, this is important since it removes the need for `Tiller`. See, [FAQ](https://helm.sh/docs/faq/#removal-of-tiller)
```bash
$ helm version
# output should look similar to this:
#version.BuildInfo{Version:"v3.0.2", GitCommit:"19e47ee3283ae98139d98460de796c1be1e3975f", GitTreeState:"clean", GoVersion:"go1.13.5"}
```
Apply cert-manager CRDs before installing (or it will fail)
```bash
$ kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.13.0/deploy/manifests/00-crds.yaml
```
Next, create the `cert-manager` namespace
```bash
$ kubectl create namespace cert-manager
```
Add the `jetstack` repo and update
```bash
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
```
Install cert-manager
```bash
$ helm install cert-manager --namespace cert-manager --version v0.13.0 jetstack/cert-manager
```
# Deploy
Once you are satisfied with the configuration, you can install the app.
```bash
# in folder /deployment/helm/human-connection
$ helm install develop ./ --namespace human-connection
```
Where `develop` is the release name, in this case develop for our develop server and `human-connection` is the namespace, again customize for your needs. The release name can be anything you want. Just keep in mind that it is used in the templates to prepend the `CLIENT_URI` and other places.
This will set up everything you need for the network, including `deployments`, and their `pods`, `services`, `ingress`, `volumes`(PersitentVolumes), `PersistentVolumeClaims`, and even `ClusterIssuers` for https certificates.

View File

@ -0,0 +1,20 @@
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: {{ .Values.supportEmail }}
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx

View File

@ -0,0 +1,20 @@
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: {{ .Values.supportEmail }}
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginx

View File

@ -0,0 +1,58 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-backend
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
replicas: 1
minReadySeconds: 15
progressDeadlineSeconds: 60
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: "100%"
selector:
matchLabels:
human-connection.org/selector: deployment-backend
template:
metadata:
name: deployment-backend
annotations:
backup.velero.io/backup-volumes: uploads
labels:
human-connection.org/commit: {{ .Values.commit }}
human-connection.org/selector: deployment-backend
spec:
containers:
- name: backend
image: "{{ .Values.backendImage }}:{{ .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- configMapRef:
name: {{ .Release.Name }}-configmap
- secretRef:
name: {{ .Release.Name }}-secrets
ports:
- containerPort: 4000
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /nitro-backend/public/uploads
name: uploads
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- name: uploads
persistentVolumeClaim:
claimName: uploads-claim
status: {}

View File

@ -0,0 +1,40 @@
{{- if .Values.developmentMailserverDomain }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-mailserver
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
replicas: 1
minReadySeconds: 15
progressDeadlineSeconds: 60
selector:
matchLabels:
human-connection.org/selector: deployment-mailserver
template:
metadata:
labels:
human-connection.org/selector: deployment-mailserver
name: mailserver
spec:
containers:
- name: mailserver
image: djfarrelly/maildev
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 80
- containerPort: 25
envFrom:
- configMapRef:
name: {{ .Release.Name }}-configmap
- secretRef:
name: {{ .Release.Name }}-secrets
restartPolicy: Always
terminationGracePeriodSeconds: 30
status: {}
{{- end}}

View File

@ -0,0 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-maintenance
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
selector:
matchLabels:
human-connection.org/selector: deployment-maintenance
template:
metadata:
labels:
human-connection.org/commit: {{ .Values.commit }}
human-connection.org/selector: deployment-maintenance
name: maintenance
spec:
containers:
- name: maintenance
env:
- name: HOST
value: 0.0.0.0
image: "{{ .Values.maintenanceImage }}:{{ .Chart.AppVersion }}"
ports:
- containerPort: 80
imagePullPolicy: Always
restartPolicy: Always
terminationGracePeriodSeconds: 30

View File

@ -0,0 +1,52 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-neo4j
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
replicas: 1
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: "100%"
selector:
matchLabels:
human-connection.org/selector: deployment-neo4j
template:
metadata:
name: neo4j
annotations:
backup.velero.io/backup-volumes: neo4j-data
labels:
human-connection.org/commit: {{ .Values.commit }}
human-connection.org/selector: deployment-neo4j
spec:
containers:
- name: neo4j
image: "{{ .Values.neo4jImage }}:{{ .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 7687
- containerPort: 7474
resources:
requests:
memory: {{ .Values.neo4jResourceRequestsMemory | default "1G" | quote }}
limits:
memory: {{ .Values.neo4jResourceLimitsMemory | default "1G" | quote }}
envFrom:
- configMapRef:
name: {{ .Release.Name }}-configmap
volumeMounts:
- mountPath: /data/
name: neo4j-data
volumes:
- name: neo4j-data
persistentVolumeClaim:
claimName: neo4j-data-claim
restartPolicy: Always
terminationGracePeriodSeconds: 30

View File

@ -0,0 +1,43 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-webapp
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
replicas: 2
minReadySeconds: 15
progressDeadlineSeconds: 60
selector:
matchLabels:
human-connection.org/selector: deployment-webapp
template:
metadata:
name: webapp
labels:
human-connection.org/commit: {{ .Values.commit }}
human-connection.org/selector: deployment-webapp
spec:
containers:
- name: webapp
image: "{{ .Values.webappImage }}:{{ .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- configMapRef:
name: {{ .Release.Name }}-configmap
- secretRef:
name: {{ .Release.Name }}-secrets
env:
- name: HOST
value: 0.0.0.0
ports:
- containerPort: 3000
resources: {}
imagePullPolicy: Always
restartPolicy: Always
terminationGracePeriodSeconds: 30
status: {}

View File

@ -0,0 +1,36 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ .Release.Name }}-ingress
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: {{ .Values.letsencryptIssuer }}
nginx.ingress.kubernetes.io/proxy-body-size: 10m
spec:
tls:
- hosts:
- {{ .Values.domain }}
secretName: tls
rules:
- host: {{ .Values.domain }}
http:
paths:
- path: /
backend:
serviceName: {{ .Release.Name }}-webapp
servicePort: 3000
{{- if .Values.developmentMailserverDomain }}
- host: {{ .Values.developmentMailserverDomain }}
http:
paths:
- path: /
backend:
serviceName: {{ .Release.Name }}-mailserver
servicePort: 80
{{- end }}

View File

@ -0,0 +1,29 @@
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-db-migrations
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: human-connection
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
annotations:
"helm.sh/hook": post-upgrade
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": hook-succeeded, hook-failed
spec:
template:
metadata:
name: {{ .Release.Name }}
spec:
restartPolicy: Never
containers:
- name: db-migrations-job
image: "{{ .Values.backendImage }}:latest"
command: ["/bin/sh", "-c", "{{ .Values.dbMigrations }}"]
envFrom:
- configMapRef:
name: {{ .Release.Name }}-configmap
- secretRef:
name: {{ .Release.Name }}-secrets

Some files were not shown because too many files have changed in this diff Show More