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 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"
# Miscellaneous
- ./scripts/translations/sort.sh
- ./scripts/translations/missing-keys.sh
# Backend
- docker-compose exec backend yarn run lint
- docker-compose exec backend yarn run test --ci --verbose=false --coverage
@ -69,7 +72,3 @@ deploy:
script: bash scripts/deploy.sh
on:
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).
#### [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)
> 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)
- 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)
@ -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)
- 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)
- refactor: Make `db:setup` init stage of `migrate` [`b063847`](https://github.com/Human-Connection/Human-Connection/commit/b063847849a84db885337dc8e84e75ddaf87011f)
- Improve styling per @alina-beck review [`bcc1ab1`](https://github.com/Human-Connection/Human-Connection/commit/bcc1ab167e8b1dfdac1ec0a05a0c14e8234bcabc)
- test(cypress): Cover "Pinned post" feature [`d49afc2`](https://github.com/Human-Connection/Human-Connection/commit/d49afc25cfa1c1f98ed04f78dd3ff826cd85ae25)
- Get rid of different factory files [`fc36729`](https://github.com/Human-Connection/Human-Connection/commit/fc367297e3e054f09b7f8f31788ab68d87f6babf)
- Refactor factory for comments [`2fc71d7`](https://github.com/Human-Connection/Human-Connection/commit/2fc71d75a5d5eab9c3467e94e00257ef6dd7d8a0)
- 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)

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { cleanDatabase } from '../factories'
import { cleanDatabase } from '../db/factories'
if (process.env.NODE_ENV === 'production') {
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()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
}

View File

@ -34,12 +34,11 @@ export function up(next) {
return txc
.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})
DELETE previousRelationship
WITH oldUser, oldEmail, user, email
CALL apoc.refactor.mergeNodes([user, oldUser], { properties: '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([user, oldUser], { properties: { createdAt: 'overwrite', \`.*\`: 'discard' }, mergeRels: true }) YIELD node as mergedUser
CALL apoc.refactor.mergeNodes([email, oldEmail], { properties: { createdAt: 'overwrite', verifiedAt: 'overwrite', \`.*\`: 'discard' }, mergeRels: true }) YIELD node as mergedEmail
RETURN user {.*}, email {.*}
`,
{ 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 CONFIG from './config'
const { app } = createServer()
const { server, httpServer } = createServer()
const url = new URL(CONFIG.GRAPHQL_URI)
app.listen({ port: url.port }, () => {
httpServer.listen({ port: url.port }, () => {
/* 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 decode from './decode'
const factory = Factory()
const driver = getDriver()
const neode = getNeode()
@ -26,7 +25,7 @@ export const validAuthorizationHeader =
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc'
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('decode', () => {
@ -65,14 +64,19 @@ describe('decode', () => {
describe('and corresponding user in the database', () => {
let user
beforeEach(async () => {
user = await factory.create('User', {
role: 'user',
name: 'Jenny Rostock',
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sasha_shestakov/128.jpg',
id: 'u3',
email: 'user@example.org',
slug: 'jenny-rostock',
})
user = await Factory.build(
'user',
{
role: 'user',
name: 'Jenny Rostock',
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sasha_shestakov/128.jpg',
id: 'u3',
slug: 'jenny-rostock',
},
{
email: 'user@example.org',
},
)
})
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
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',
issuer: CONFIG.GRAPHQL_URI,
audience: CONFIG.CLIENT_URI,
subject: user.id.toString(),
})
// jwt.verifySignature(token, CONFIG.JWT_SECRET, (err, data) => {
// console.log('token verification:', err, data)
// })
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 Factory from '../../factories'
import { cleanDatabase } from '../../db/factories'
import { createTestClient } from 'apollo-server-testing'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'
@ -9,7 +9,6 @@ let query
let mutate
let hashtagingUser
let authenticatedUser
const factory = Factory()
const driver = getDriver()
const neode = getNeode()
const categoryIds = ['cat9']
@ -48,13 +47,18 @@ beforeAll(() => {
})
beforeEach(async () => {
hashtagingUser = await neode.create('User', {
id: 'you',
name: 'Al Capone',
slug: 'al-capone',
email: 'test@example.org',
password: '1234',
})
hashtagingUser = await neode.create(
'User',
{
id: 'you',
name: 'Al Capone',
slug: 'al-capone',
},
{
password: '1234',
email: 'test@example.org',
},
)
await neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
@ -63,7 +67,7 @@ beforeEach(async () => {
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('hashtags', () => {

View File

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

View File

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

View File

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

View File

@ -1,10 +1,9 @@
import { createTestClient } from 'apollo-server-testing'
import createServer from '../server'
import Factory from '../factories'
import Factory, { cleanDatabase } from '../db/factories'
import { gql } from '../helpers/jest'
import { getDriver, getNeode } from '../db/neo4j'
const factory = Factory()
const instance = getNeode()
const driver = getDriver()
@ -20,7 +19,7 @@ const userQuery = gql`
describe('authorization', () => {
beforeAll(async () => {
await factory.cleanDatabase()
await cleanDatabase()
const { server } = createServer({
context: () => ({
driver,
@ -34,34 +33,54 @@ describe('authorization', () => {
describe('given two existing users', () => {
beforeEach(async () => {
;[owner, anotherRegularUser, administrator, moderator] = await Promise.all([
factory.create('User', {
email: 'owner@example.org',
name: 'Owner',
password: 'iamtheowner',
}),
factory.create('User', {
email: 'another.regular.user@example.org',
name: 'Another Regular User',
password: 'else',
}),
factory.create('User', {
email: 'admin@example.org',
name: 'Admin',
password: 'admin',
role: 'admin',
}),
factory.create('User', {
email: 'moderator@example.org',
name: 'Moderator',
password: 'moderator',
role: 'moderator',
}),
Factory.build(
'user',
{
name: 'Owner',
},
{
email: 'owner@example.org',
password: 'iamtheowner',
},
),
Factory.build(
'user',
{
name: 'Another Regular User',
},
{
email: 'another.regular.user@example.org',
password: 'else',
},
),
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 = {}
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
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 { getNeode, getDriver } from '../db/neo4j'
import createServer from '../server'
import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
let mutate
let authenticatedUser
let variables
@ -28,14 +26,18 @@ beforeAll(() => {
beforeEach(async () => {
variables = {}
const admin = await factory.create('User', {
const admin = await Factory.build('user', {
role: 'admin',
})
await factory.create('User', {
email: 'someone@example.org',
password: '1234',
})
await factory.create('Category', {
await Factory.build(
'user',
{},
{
email: 'someone@example.org',
password: '1234',
},
)
await Factory.build('category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
@ -44,7 +46,7 @@ beforeEach(async () => {
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('slugifyMiddleware', () => {
@ -84,12 +86,17 @@ describe('slugifyMiddleware', () => {
describe('if slug exists', () => {
beforeEach(async () => {
await factory.create('Post', {
title: 'Pre-existing post',
slug: 'pre-existing-post',
content: 'as Someone else content',
categoryIds,
})
await Factory.build(
'post',
{
title: 'Pre-existing post',
slug: 'pre-existing-post',
content: 'as Someone else content',
},
{
categoryIds,
},
)
})
it('chooses another slug', async () => {
@ -190,7 +197,7 @@ describe('slugifyMiddleware', () => {
describe('given a user has signed up with their email address', () => {
beforeEach(async () => {
await factory.create('EmailAddress', {
await Factory.build('emailAddress', {
email: '123@example.org',
nonce: '123456',
verifiedAt: null,
@ -214,7 +221,7 @@ describe('slugifyMiddleware', () => {
describe('if slug exists', () => {
beforeEach(async () => {
await factory.create('User', {
await Factory.build('user', {
name: '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 { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
const neode = getNeode()
const driver = getDriver()
@ -18,13 +17,18 @@ const action = () => {
beforeAll(async () => {
// For performance reasons we do this only once
const users = await Promise.all([
factory.create('User', { id: 'u1', role: 'user' }),
factory.create('User', {
id: 'm1',
role: 'moderator',
password: '1234',
}),
factory.create('User', {
Factory.build('user', { id: 'u1', role: 'user' }),
Factory.build(
'user',
{
id: 'm1',
role: 'moderator',
},
{
password: '1234',
},
),
Factory.build('user', {
id: 'u2',
role: 'user',
name: 'Offensive Name',
@ -45,48 +49,73 @@ beforeAll(async () => {
await Promise.all([
user.relateTo(troll, 'following'),
factory.create('Post', {
author: user,
id: 'p1',
title: 'Deleted post',
slug: 'deleted-post',
deleted: true,
categoryIds,
}),
factory.create('Post', {
author: user,
id: 'p3',
title: 'Publicly visible post',
slug: 'publicly-visible-post',
deleted: false,
categoryIds,
}),
Factory.build(
'post',
{
id: 'p1',
title: 'Deleted post',
slug: 'deleted-post',
deleted: true,
},
{
author: user,
categoryIds,
},
),
Factory.build(
'post',
{
id: 'p3',
title: 'Publicly visible post',
slug: 'publicly-visible-post',
deleted: false,
},
{
author: user,
categoryIds,
},
),
])
const resources = await Promise.all([
factory.create('Comment', {
author: user,
id: 'c2',
postId: 'p3',
content: 'Enabled comment on public post',
}),
factory.create('Post', {
id: 'p2',
author: troll,
title: 'Disabled post',
content: 'This is an offensive post content',
contentExcerpt: 'This is an offensive post content',
image: '/some/offensive/image.jpg',
deleted: false,
categoryIds,
}),
factory.create('Comment', {
id: 'c1',
author: troll,
postId: 'p3',
content: 'Disabled comment',
contentExcerpt: 'Disabled comment',
}),
Factory.build(
'comment',
{
id: 'c2',
content: 'Enabled comment on public post',
},
{
author: user,
postId: 'p3',
},
),
Factory.build(
'post',
{
id: 'p2',
title: 'Disabled post',
content: 'This is an offensive post content',
contentExcerpt: 'This is an offensive post content',
image: '/some/offensive/image.jpg',
deleted: false,
},
{
author: troll,
categoryIds,
},
),
Factory.build(
'comment',
{
id: 'c1',
content: 'Disabled comment',
contentExcerpt: 'Disabled comment',
},
{
author: troll,
postId: 'p3',
},
),
])
const { server } = createServer({
@ -105,9 +134,9 @@ beforeAll(async () => {
const trollingComment = resources[2]
const reports = await Promise.all([
factory.create('Report'),
factory.create('Report'),
factory.create('Report'),
Factory.build('report'),
Factory.build('report'),
Factory.build('report'),
])
const reportAgainstTroll = reports[0]
const reportAgainstTrollingPost = reports[1]
@ -154,7 +183,7 @@ beforeAll(async () => {
})
afterAll(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('softDeleteMiddleware', () => {

View File

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

View File

@ -52,5 +52,4 @@ export default {
},
},
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'
const factory = Factory()
const neode = getNeode()
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('role', () => {

View File

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

View File

@ -1,11 +1,10 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../factories'
import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'
let mutate, query, authenticatedUser, variables
const factory = Factory()
const instance = getNeode()
const driver = getDriver()
@ -33,7 +32,7 @@ const donationsQuery = gql`
describe('donations', () => {
let currentUser, newlyCreatedDonations
beforeAll(async () => {
await factory.cleanDatabase()
await cleanDatabase()
authenticatedUser = undefined
const { server } = createServer({
context: () => {
@ -50,11 +49,11 @@ describe('donations', () => {
beforeEach(async () => {
variables = {}
newlyCreatedDonations = await factory.create('Donations')
newlyCreatedDonations = await Factory.build('donations')
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('query for donations', () => {
@ -68,7 +67,7 @@ describe('donations', () => {
describe('authenticated', () => {
beforeEach(async () => {
currentUser = await factory.create('User', {
currentUser = await Factory.build('user', {
id: 'normal-user',
role: 'user',
})
@ -102,7 +101,7 @@ describe('donations', () => {
describe('authenticated', () => {
describe('as a normal user', () => {
beforeEach(async () => {
currentUser = await factory.create('User', {
currentUser = await Factory.build('user', {
id: 'normal-user',
role: 'user',
})
@ -121,7 +120,7 @@ describe('donations', () => {
describe('as a moderator', () => {
beforeEach(async () => {
currentUser = await factory.create('User', {
currentUser = await Factory.build('user', {
id: 'moderator',
role: 'moderator',
})
@ -140,7 +139,7 @@ describe('donations', () => {
describe('as an admin', () => {
beforeEach(async () => {
currentUser = await factory.create('User', {
currentUser = await Factory.build('user', {
id: '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 { getDriver, getNeode } from '../../db/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
const neode = getNeode()
let mutate
@ -31,7 +30,7 @@ beforeAll(() => {
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('AddEmailAddress', () => {
@ -63,7 +62,7 @@ describe('AddEmailAddress', () => {
describe('authenticated', () => {
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()
})
@ -110,7 +109,7 @@ describe('AddEmailAddress', () => {
describe('if another `UnverifiedEmailAddress` node already exists with that email', () => {
it('throws no unique constraint violation error', async () => {
await factory.create('UnverifiedEmailAddress', {
await Factory.build('unverifiedEmailAddress', {
createdAt: '2019-09-24T14:00:01.565Z',
email: 'new-email@example.org',
})
@ -128,7 +127,7 @@ describe('AddEmailAddress', () => {
describe('but if another user owns an `EmailAddress` already with that email', () => {
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({
data: { AddEmailAddress: null },
errors: [{ message: 'A user account with this email already exists.' }],
@ -169,7 +168,7 @@ describe('VerifyEmailAddress', () => {
describe('authenticated', () => {
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()
})
@ -185,7 +184,7 @@ describe('VerifyEmailAddress', () => {
describe('given a `UnverifiedEmailAddress`', () => {
let emailAddress
beforeEach(async () => {
emailAddress = await factory.create('UnverifiedEmailAddress', {
emailAddress = await Factory.build('unverifiedEmailAddress', {
nonce: 'abcdef',
verifiedAt: null,
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', () => {
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 () => {

View File

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

View File

@ -1,5 +1,7 @@
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', () => {
let params
let uploadCallback
@ -13,7 +15,7 @@ describe('fileUpload', () => {
createReadStream: jest.fn(),
},
}
uploadCallback = jest.fn()
uploadCallback = jest.fn(({ uniqueFilename }) => uniqueFilename)
})
it('calls uploadCallback', async () => {
@ -24,20 +26,13 @@ describe('fileUpload', () => {
describe('file name', () => {
it('saves the upload url in params[url]', async () => {
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(params.attribute).toMatch(/^\/uploads\/\d+-avatar$/)
})
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/)
expect(params.attribute).toMatch(new RegExp(`^/uploads/${uuid}-avatar.jpg`))
})
it('creates a url safe name', async () => {
params.uploadAttribute.filename =
'/path/to/awkward?/ file-location/?foo- bar-avatar.jpg?foo- bar'
params.uploadAttribute.filename = '/path/to/awkward?/ file-location/?foo- bar-avatar.jpg'
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', () => {
@ -50,7 +45,6 @@ describe('fileUpload', () => {
uploadCallback,
)
await new Promise(resolve => setTimeout(resolve, 1000))
const { attribute: second } = await fileUpload(
{
...params,

View File

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

View File

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

View File

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

View File

@ -1,21 +1,18 @@
import log from './helpers/databaseLogger'
const resourceTypes = ['Post', 'Comment']
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,
},
}
}
import { withFilter } from 'graphql-subscriptions'
import { pubsub, NOTIFICATION_ADDED } from '../../server'
export default {
Subscription: {
notificationAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator(NOTIFICATION_ADDED),
(payload, variables) => {
return payload.notificationAdded.to.id === variables.userId
},
),
},
},
Query: {
notifications: async (_parent, args, context, _resolveInfo) => {
const { user: currentUser } = context
@ -51,10 +48,10 @@ export default {
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
${whereClause}
WITH user, notification, resource,
[(resource)<-[:WROTE]-(author:User) | author {.*}] as authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts
[(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
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
RETURN notification {.*, from: finalResource, to: properties(user)}
${orderByClause}
${offset} ${limit}
@ -81,12 +78,19 @@ export default {
`
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
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 },
)
log(markNotificationAsReadTransactionResponse)
return markNotificationAsReadTransactionResponse.records.map(transformReturnType)
return markNotificationAsReadTransactionResponse.records.map(record =>
record.get('notification'),
)
})
try {
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 { getDriver } from '../../db/neo4j'
import { createTestClient } from 'apollo-server-testing'
import createServer from '../.././server'
const factory = Factory()
const driver = getDriver()
let authenticatedUser
let user
@ -32,52 +31,77 @@ beforeEach(async () => {
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('given some notifications', () => {
beforeEach(async () => {
const categoryIds = ['cat1']
author = await factory.create('User', { id: 'author' })
user = await factory.create('User', { id: 'you' })
author = await Factory.build('user', { id: 'author' })
user = await Factory.build('user', { id: 'you' })
const [neighbor] = await Promise.all([
factory.create('User', { id: 'neighbor' }),
factory.create('Category', { id: 'cat1' }),
Factory.build('user', { id: 'neighbor' }),
Factory.build('category', { id: 'cat1' }),
])
const [post1, post2, post3] = await Promise.all([
factory.create('Post', { author, id: 'p1', categoryIds, content: 'Not for you' }),
factory.create('Post', {
author,
id: 'p2',
categoryIds,
content: 'Already seen post mention',
}),
factory.create('Post', {
author,
id: 'p3',
categoryIds,
content: 'You have been mentioned in a post',
}),
Factory.build('post', { id: 'p1', content: 'Not for you' }, { author, categoryIds }),
Factory.build(
'post',
{
id: 'p2',
content: 'Already seen post mention',
},
{
author,
categoryIds,
},
),
Factory.build(
'post',
{
id: 'p3',
content: 'You have been mentioned in a post',
},
{
author,
categoryIds,
},
),
])
const [comment1, comment2, comment3] = await Promise.all([
factory.create('Comment', {
author,
postId: 'p3',
id: 'c1',
content: 'You have seen this comment mentioning already',
}),
factory.create('Comment', {
author,
postId: 'p3',
id: 'c2',
content: 'You have been mentioned in a comment',
}),
factory.create('Comment', {
author,
postId: 'p3',
id: 'c3',
content: 'Somebody else was mentioned in a comment',
}),
Factory.build(
'comment',
{
id: 'c1',
content: 'You have seen this comment mentioning already',
},
{
author,
postId: 'p3',
},
),
Factory.build(
'comment',
{
id: 'c2',
content: 'You have been 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([
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 { getNeode, getDriver } from '../../db/neo4j'
import createPasswordReset from './helpers/createPasswordReset'
@ -7,7 +7,6 @@ import { createTestClient } from 'apollo-server-testing'
const neode = getNeode()
const driver = getDriver()
const factory = Factory()
let mutate
let authenticatedUser
@ -39,15 +38,19 @@ beforeAll(() => {
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('passwordReset', () => {
describe('given a user', () => {
beforeEach(async () => {
await factory.create('User', {
email: 'user@example.org',
})
await Factory.build(
'user',
{},
{
email: 'user@example.org',
},
)
})
describe('requestPasswordReset', () => {
@ -123,11 +126,16 @@ describe('resetPassword', () => {
describe('given a user', () => {
beforeEach(async () => {
await factory.create('User', {
email: 'user@example.org',
role: 'user',
password: '1234',
})
await Factory.build(
'user',
{
role: 'user',
},
{
email: 'user@example.org',
password: '1234',
},
)
})
describe('invalid email', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,19 @@
import jwt from 'jsonwebtoken'
import CONFIG from './../../config'
import Factory from '../../factories'
import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest'
import { createTestClient } from 'apollo-server-testing'
import createServer, { context } from '../../server'
import encode from '../../jwt/encode'
import { getNeode } from '../../db/neo4j'
const factory = Factory()
const neode = getNeode()
let query, mutate, variables, req, user
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 reportAgainstUser = await factory.create('Report')
const reportAgainstUser = await Factory.build('report')
await Promise.all([
reportAgainstUser.relateTo(moderator, 'filed', {
resourceId: id,
@ -48,7 +47,7 @@ beforeAll(() => {
})
afterEach(async () => {
await factory.cleanDatabase()
await cleanDatabase()
})
describe('isLoggedIn', () => {
@ -69,7 +68,7 @@ describe('isLoggedIn', () => {
describe('authenticated', () => {
beforeEach(async () => {
user = await factory.create('User', { id: 'u3' })
user = await Factory.build('user', { id: 'u3' })
const userBearerToken = encode({ id: 'u3' })
req = { headers: { authorization: `Bearer ${userBearerToken}` } }
})
@ -127,15 +126,20 @@ describe('currentUser', () => {
describe('authenticated', () => {
describe('and corresponding user in the database', () => {
beforeEach(async () => {
await factory.create('User', {
id: 'u3',
// 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',
email: 'test@example.org',
name: 'Matilde Hermiston',
slug: 'matilde-hermiston',
role: 'user',
})
await Factory.build(
'user',
{
id: 'u3',
// 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',
name: 'Matilde Hermiston',
slug: 'matilde-hermiston',
role: 'user',
},
{
email: 'test@example.org',
},
)
const userBearerToken = encode({ id: 'u3' })
req = { headers: { authorization: `Bearer ${userBearerToken}` } }
})
@ -172,10 +176,13 @@ describe('login', () => {
beforeEach(async () => {
variables = { email: 'test@example.org', password: '1234' }
user = await factory.create('User', {
...variables,
id: 'acb2d923-f3af-479e-9f00-61b12e864666',
})
user = await Factory.build(
'user',
{
id: 'acb2d923-f3af-479e-9f00-61b12e864666',
},
variables,
)
})
describe('ask for a `token`', () => {
@ -185,7 +192,9 @@ describe('login', () => {
data: { login: token },
} = await mutate({ mutation: loginMutation, variables })
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()
done()
})
@ -295,7 +304,7 @@ describe('change password', () => {
describe('authenticated', () => {
beforeEach(async () => {
await factory.create('User', { id: 'u3' })
await Factory.build('user', { id: 'u3' })
const userBearerToken = encode({ id: 'u3' })
req = { headers: { authorization: `Bearer ${userBearerToken}` } }
})

View File

@ -252,9 +252,11 @@ export default {
followedByCurrentUser:
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
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:
'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: {
contributionsCount:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import express from 'express'
import http from 'http'
import helmet from 'helmet'
import { ApolloServer } from 'apollo-server-express'
import CONFIG from './config'
@ -7,11 +8,35 @@ import { getNeode, getDriver } from './db/neo4j'
import decode from './jwt/decode'
import schema from './schema'
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 neode = getNeode()
export const context = async ({ req }) => {
const getContext = async req => {
const user = await decode(driver, req.headers.authorization)
return {
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 defaults = {
context,
schema: middleware(schema),
subscriptions: {
onConnect: (connectionParams, webSocket) => {
return getContext(connectionParams)
},
},
debug: !!CONFIG.DEBUG,
tracing: !!CONFIG.DEBUG,
formatError: error => {
@ -45,9 +83,13 @@ const createServer = options => {
app.use(helmet())
app.use('/.well-known/', webfinger())
app.use(express.static('public'))
app.use(bodyParser.json({ limit: '10mb' }))
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }))
server.applyMiddleware({ app, path: '/' })
const httpServer = http.createServer(app)
server.installSubscriptionHandlers(httpServer)
return { server, app }
return { server, httpServer, app }
}
export default createServer

View File

@ -3,18 +3,18 @@ import { Given, When, Then, AfterAll } from 'cucumber'
import { expect } from 'chai'
// import { client } from '../../../src/activitypub/apollo-client'
import { GraphQLClient } from 'graphql-request'
import Factory from '../../../src/factories'
import Factory from '../../../src/db/factories'
const debug = require('debug')('ea:test:steps')
const factory = Factory()
const client = new GraphQLClient(host)
function createUser (slug) {
debug(`creating user ${slug}`)
return factory.create('User', {
return Factory.build('user', {
name: slug,
}, {
password: '1234',
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"
video-extensions "~1.1.0"
"@metascraper/helpers@^5.10.6":
version "5.10.6"
resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.10.6.tgz#0b786607212925a577926fd0cd0313a49de3499c"
integrity sha512-/jvhlM3RKGYMoUK8D8S1r3tN03/EYizCqWF7zDx0aBMC8Ihp33DRGs9oNdsgkgwzVF7O/YpDm55l9K+qVJlsyQ==
"@metascraper/helpers@^5.10.7", "@metascraper/helpers@^5.11.1":
version "5.11.1"
resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.11.1.tgz#227fdd0caf1d33f4b24a85298a355ce7ebb451df"
integrity sha512-oES/e6bwKBlT7WGa2ni3xbJMDx2rbFxSzbUhRX8D+Kylb8H2ThP07c7f+VXMPXWx5CPrNMai/Oyp5IczCf3v8g==
dependencies:
audio-extensions "0.0.0"
chrono-node "~1.4.2"
chrono-node "~1.4.3"
condense-whitespace "~2.0.0"
entities "~2.0.0"
file-extension "~4.0.5"
@ -1194,7 +1194,7 @@
lodash "~4.17.15"
memoize-one "~5.1.1"
mime-types "~2.1.26"
normalize-url "~4.5.0"
normalize-url "~5.0.0"
smartquotes "~2.3.1"
title "~3.4.1"
truncate "~2.1.0"
@ -1275,83 +1275,83 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@sentry/apm@5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.2.tgz#35961b9d2319ad21ae91f1b697998a8c523f1919"
integrity sha512-qn4HiSZ+6b1Gg+DlXdHVpiPPEbRu4IicGSbI8HTJLzrlsjoaBQPPkDwtuQUBVq21tU3RYXnTwrl9m45KuX6alA==
"@sentry/apm@5.12.3":
version "5.12.3"
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.12.3.tgz#23a5e9c771a8748f59426a1d0f8b1fbb9b72a717"
integrity sha512-OSGEeo4b1Gsu/TUcWMx9vmgSnQvR+zM+1Iwq5xFQAK2ET3Y4gBFqZ1iRt2hxlzr8KCQmQTQc1mao1X0tmidFQg==
dependencies:
"@sentry/browser" "5.11.2"
"@sentry/hub" "5.11.2"
"@sentry/minimal" "5.11.2"
"@sentry/types" "5.11.0"
"@sentry/utils" "5.11.1"
"@sentry/browser" "5.12.1"
"@sentry/hub" "5.12.0"
"@sentry/minimal" "5.12.0"
"@sentry/types" "5.12.0"
"@sentry/utils" "5.12.0"
tslib "^1.9.3"
"@sentry/browser@5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.11.2.tgz#f0b19bd97e9f09a20e9f93a9835339ed9ab1f5a4"
integrity sha512-ls6ARX5m+23ld8OsuoPnR+kehjR5ketYWRcDYlmJDX2VOq5K4EzprujAo8waDB0o5a92yLXQ0ZSoK/zzAV2VoA==
"@sentry/browser@5.12.1":
version "5.12.1"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.12.1.tgz#dc1f268595269fb7277f55eb625c7e92d76dc01b"
integrity sha512-Zl7VdppUxctyaoqMSEhnDJp2rrupx8n8N2n3PSooH74yhB2Z91nt84mouczprBsw3JU1iggGyUw9seRFzDI1hw==
dependencies:
"@sentry/core" "5.11.2"
"@sentry/types" "5.11.0"
"@sentry/utils" "5.11.1"
"@sentry/core" "5.12.0"
"@sentry/types" "5.12.0"
"@sentry/utils" "5.12.0"
tslib "^1.9.3"
"@sentry/core@5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.11.2.tgz#f2d9d37940d291dbcb9a9e4a012f76919474bdf6"
integrity sha512-IFCXGy7ebqIq/Kb8RVryCo/SjwhPcrfBmOjkicr4+DxN1UybLre2N3p9bejQMPIteOfDVHlySLYeipjTf+mxZw==
"@sentry/core@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.12.0.tgz#d6380c4ef7beee5f418ac1d0e5be86a2de2af449"
integrity sha512-wY4rsoX71QsGpcs9tF+OxKgDPKzIFMRvFiSRcJoPMfhFsTilQ/CBMn/c3bDtWQd9Bnr/ReQIL6NbnIjUsPHA4Q==
dependencies:
"@sentry/hub" "5.11.2"
"@sentry/minimal" "5.11.2"
"@sentry/types" "5.11.0"
"@sentry/utils" "5.11.1"
"@sentry/hub" "5.12.0"
"@sentry/minimal" "5.12.0"
"@sentry/types" "5.12.0"
"@sentry/utils" "5.12.0"
tslib "^1.9.3"
"@sentry/hub@5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.11.2.tgz#a3b7ec27cd4cea2cddd75c372fbf1b4bc04c6aae"
integrity sha512-5BiDin6ZPsaiTm29rCC41MAjP1vOaKniqfjtXHVPm7FeOBA2bpHm95ncjLkshKGJTPfPZHXTpX/1IZsHrfGVEA==
"@sentry/hub@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.12.0.tgz#5e8c8f249f5bdbeb8cc4ec02c2ccc53a67f2cc02"
integrity sha512-3k7yE8BEVJsKx8mR4LcI4IN0O8pngmq44OcJ/fRUUBAPqsT38jsJdP2CaWhdlM1jiNUzUDB1ktBv6/lY+VgcoQ==
dependencies:
"@sentry/types" "5.11.0"
"@sentry/utils" "5.11.1"
"@sentry/types" "5.12.0"
"@sentry/utils" "5.12.0"
tslib "^1.9.3"
"@sentry/minimal@5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.11.2.tgz#ae417699342266ecd109a97e53cd9519c0893b21"
integrity sha512-oNuJuz3EZhVtamzABmPdr6lcYo06XHLWb2LvgnoNaYcMD1ExUSvhepOSyZ2h5STCMbmVgGVfXBNPV9RUTp8GZg==
"@sentry/minimal@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.12.0.tgz#2611e2aa520c1edb7999e6de51bd65ec66341757"
integrity sha512-fk73meyz4k4jCg9yzbma+WkggsfEIQWI2e2TWfYsRGcrV3RnlSrXyM4D91/A8Bjx10SNezHPUFHjasjlHXOkyA==
dependencies:
"@sentry/hub" "5.11.2"
"@sentry/types" "5.11.0"
"@sentry/hub" "5.12.0"
"@sentry/types" "5.12.0"
tslib "^1.9.3"
"@sentry/node@^5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.11.2.tgz#575c320b624c218d2155183f6bbe82b732bfb1f2"
integrity sha512-jYq9u76BdAbOKPuYg39Xh/+797MevzjMkCIC9cw/bQxAm6nHc3FXeKqd79O33jO4Jag0JL+Bz/0JidgrKgKgXg==
"@sentry/node@^5.12.3":
version "5.12.3"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.12.3.tgz#4a4934b04c5163fc340312eaf0d152990aa7140f"
integrity sha512-QwqN+i6IC3/YrNq7kqxH7YiXtZYY8tBuJqFi84WbiMHF7MAqxMSPNQJGfX2GJuMHKHwn6IZdgSE8+FkfN9zGLQ==
dependencies:
"@sentry/apm" "5.11.2"
"@sentry/core" "5.11.2"
"@sentry/hub" "5.11.2"
"@sentry/types" "5.11.0"
"@sentry/utils" "5.11.1"
"@sentry/apm" "5.12.3"
"@sentry/core" "5.12.0"
"@sentry/hub" "5.12.0"
"@sentry/types" "5.12.0"
"@sentry/utils" "5.12.0"
cookie "^0.3.1"
https-proxy-agent "^4.0.0"
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/types@5.11.0":
version "5.11.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.11.0.tgz#40f0f3174362928e033ddd9725d55e7c5cb7c5b6"
integrity sha512-1Uhycpmeo1ZK2GLvrtwZhTwIodJHcyIS6bn+t4IMkN9MFoo6ktbAfhvexBDW/IDtdLlCGJbfm8nIZerxy0QUpg==
"@sentry/types@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.12.0.tgz#5367e53c74261beea01502e3f7b6f3d822682a31"
integrity sha512-aZbBouBLrKB8wXlztriIagZNmsB+wegk1Jkl6eprqRW/w24Sl/47tiwH8c5S4jYTxdAiJk+SAR10AAuYmIN3zg==
"@sentry/utils@5.11.1":
version "5.11.1"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.11.1.tgz#aa19fcc234cf632257b2281261651d2fac967607"
integrity sha512-O0Zl4R2JJh8cTkQ8ZL2cDqGCmQdpA5VeXpuBbEl1v78LQPkBDISi35wH4mKmLwMsLBtTVpx2UeUHBj0KO5aLlA==
"@sentry/utils@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.12.0.tgz#62967f934a3ee6d21472eac0219084e37225933e"
integrity sha512-fYUadGLbfTCbs4OG5hKCOtv2jrNE4/8LHNABy9DwNJ/t5DVtGqWAZBnxsC+FG6a3nVqCpxjFI9AHlYsJ2wsf7Q==
dependencies:
"@sentry/types" "5.11.0"
"@sentry/types" "5.12.0"
tslib "^1.9.3"
"@sindresorhus/is@^0.14.0":
@ -1607,10 +1607,10 @@
dependencies:
"@types/yargs-parser" "*"
"@types/yup@0.26.29":
version "0.26.29"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.29.tgz#5a533ad6f74e442436698e20b1441c68a7a1c931"
integrity sha512-M81oZOgLap0b0I/BySnpLwHjOj1BFxUKV1ytG2Kqj3jmkh8F3H11PEnk658UniftpjTXdueloOL+KZYn+SMQ9w==
"@types/yup@0.26.30":
version "0.26.30"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.30.tgz#0d6066505bb67e7b9b161b2082c4cdfcdafd6a6b"
integrity sha512-b/uklO68T/eShWnxjlgwOJlEFOQ11ib3i1wQQiLmHqFJTxDMvz+tb4XzThGQISzOcelMnoSdb1Po4s69YkEQeg==
"@types/zen-observable@^0.8.0":
version "0.8.0"
@ -1845,10 +1845,10 @@ apollo-client@~2.6.8:
tslib "^1.10.0"
zen-observable "^0.8.0"
apollo-datasource@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.6.4.tgz#c0d1604b1a97e004844d4b61bd819a9a6b0a409f"
integrity sha512-u4eu6Q94q6KuZacZfdo4vCevA81F4QWeTYEXUvoksQMJpiacPHHe0DJrofKVKvxngUp5kCi1RnPXSc6kBY+/oA==
apollo-datasource@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.7.0.tgz#2a6d82edb2eba21b4ddf21877009ba39ff821945"
integrity sha512-Yja12BgNQhzuFGG/5Nw2MQe0hkuQy2+9er09HxeEyAf2rUDIPnhPrn1MDoZTB8MU7UGfjwITC+1ofzKkkrZobA==
dependencies:
apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3"
@ -1860,13 +1860,13 @@ apollo-engine-reporting-protobuf@^0.4.4:
dependencies:
"@apollo/protobufjs" "^1.0.3"
apollo-engine-reporting@^1.4.14:
version "1.4.14"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.14.tgz#71a6509ebe86385da43df500cd0940525a3e8674"
integrity sha512-cCG9qDOPwbh87ZjQGHgmnP3oPqhqjIZcNmm/lNtWkWXGTlxV/jmUEqpVi+wsDbE5gR7d1OFk6GqSy2ZQh+S+Bw==
apollo-engine-reporting@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.5.0.tgz#6e3746de14fc87e14c289c0776a2d350e6f50918"
integrity sha512-Pe2DelijZ2QHqkqv8E97iOb32l+FIMT2nxpQsuH+nWi+96cCFJJJHjm3RLAPEUuvGOgW9dFYQP3J91EyC5O0tQ==
dependencies:
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-env "^2.4.3"
apollo-server-errors "^2.3.4"
@ -1892,10 +1892,10 @@ apollo-errors@^1.9.0:
assert "^1.4.1"
extendable-error "^0.1.5"
apollo-graphql@^0.3.7:
version "0.3.7"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.7.tgz#533232ed48b0b6dbcf5635f65e66cf8677a5b768"
integrity sha512-ghW16xx9tRcyL38Pw6G5OidMnYn+CNUGZWmvqQgEO2nRy4T0ONPZZBOvGrIMtJQ70oEykNMKGm0zm6PdHdxd8Q==
apollo-graphql@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.4.0.tgz#dd0afe31a6241b8e2ded20b906c9ee8dfbe03497"
integrity sha512-abCHcKln1EGbzSItW087EjBI5wnluikyUqEn4VsdeWHCtdENWpHCn/MnM0+jJa1prNasxN7tCukp4nMpJYYVqg==
dependencies:
apollo-env "^0.6.1"
lodash.sortby "^4.7.0"
@ -1943,18 +1943,18 @@ apollo-server-caching@^0.5.1:
dependencies:
lru-cache "^5.0.0"
apollo-server-core@^2.9.16:
version "2.9.16"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.16.tgz#b4c869a6babfa6906fbbf1e6facf3b7231dbf777"
integrity sha512-4ftdjSfs/3aEare9QNTVSF0yUvXETxiohuDLZ7gmMIQxNnZhUjVXiZL1rYKuIZ12uH7xLvh/DwkXRt6nLG/lZA==
apollo-server-core@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.10.0.tgz#b8d51bdffe6529f0e3ca670ee8f1238765cfade4"
integrity sha512-x/UK6XvU307W8D/pzTclU04JIjRarcbg5mFPe0nVmO4OTc26uQgKi1WlZkcewXsAUnn+nDwKVn2c2G3dHEgXzQ==
dependencies:
"@apollographql/apollo-tools" "^0.4.3"
"@apollographql/graphql-playground-html" "1.6.24"
"@types/graphql-upload" "^8.0.0"
"@types/ws" "^6.0.0"
apollo-cache-control "^0.8.11"
apollo-datasource "^0.6.4"
apollo-engine-reporting "^1.4.14"
apollo-datasource "^0.7.0"
apollo-engine-reporting "^1.5.0"
apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3"
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"
integrity sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA==
apollo-server-express@^2.9.16:
version "2.9.16"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.16.tgz#4c30b1769426c010b37943c0fb7766e5825973a0"
integrity sha512-ZDc7GP+piUm67alJ0DIE9f36tHcCiNm3PHMLIVJlVE/rcGwzRjV5rardRqeslljQiO2J+1IwXKwJ0/kRrZ4JvQ==
apollo-server-express@^2.10.0, apollo-server-express@^2.9.16:
version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.10.0.tgz#7d87ff54e378cdcb135eb3d093f20fca7fc0d1bc"
integrity sha512-adDQts4QmkX2ENU7JibV1EwRl3ESnpnpImXIMvg8Cm7kX2wSbzwm8LecQNujwWJtkAPTCEAcnPBDyKwWjTQ6/g==
dependencies:
"@apollographql/graphql-playground-html" "1.6.24"
"@types/accepts" "^1.3.5"
@ -1994,7 +1994,7 @@ apollo-server-express@^2.9.16:
"@types/cors" "^2.8.4"
"@types/express" "4.17.2"
accepts "^1.3.5"
apollo-server-core "^2.9.16"
apollo-server-core "^2.10.0"
apollo-server-types "^0.2.10"
body-parser "^1.18.3"
cors "^2.8.4"
@ -2012,12 +2012,12 @@ apollo-server-plugin-base@^0.6.10:
dependencies:
apollo-server-types "^0.2.10"
apollo-server-testing@~2.9.16:
version "2.9.16"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.16.tgz#35e9b0b102a11bac8db2fce04281cb43e7993d45"
integrity sha512-CLfYZY2Htwzw6iPlFO32/SNXNstWQsvGd5/FQ8KEwRpNfYM4g0rAE98y/THEQTvTh0xPH+qWxA7CVQcc7/FMbQ==
apollo-server-testing@~2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.10.0.tgz#c8d7fc2d4e6eaf84232aaa7c125d9fae691fbcf4"
integrity sha512-wBJ/CT3ZN5nmSySMqgpAFwX/I3yzsQhRGR8MCK/16MjhEZH6svNaJWzoif6gaocj0NyVBJvOIijuMTecG9+6vg==
dependencies:
apollo-server-core "^2.9.16"
apollo-server-core "^2.10.0"
apollo-server-types@^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-env "^2.4.3"
apollo-server@~2.9.16:
version "2.9.16"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.16.tgz#c0054ed70ecb637cb3f585ff46fb4a060730076f"
integrity sha512-dqB1shkjl9ne7DfSHXDH5sT70llr9zswLL+/g/4zt4/H+k+2pkD1BShQkNIK7PBYcVa8KvRAHXiHTXZ36GCspA==
apollo-server@~2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.10.0.tgz#93b924b089f7c4070e88aa29a8b9472c1d5d0f82"
integrity sha512-ITXkklSgrNfohFh4juvHWrtLz/82iw9CkBUW+G5T8NxHaqxm1Lpus1Ck2VsXmCgNplYi6mODRjUl087qdlU2Rw==
dependencies:
apollo-server-core "^2.9.16"
apollo-server-express "^2.9.16"
apollo-server-core "^2.10.0"
apollo-server-express "^2.10.0"
express "^4.0.0"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
@ -2691,10 +2691,10 @@ chrono-node@~1.3.11:
dependencies:
moment "2.21.0"
chrono-node@~1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-1.4.2.tgz#0c7fc1f264e60a660c2b2dab753a3f285dbfd8c9"
integrity sha512-fsb82wPDHVZl3xtche8k4ZZtNwf81/ZMueil2ANpSfogUAEa3BuzZAar7ObLXi1ptMjBzdzA6ys/bFq1oBjO8w==
chrono-node@~1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-1.4.3.tgz#4c8e24643ec5e576f6f8fe0429370c3b554491b4"
integrity sha512-ZyKcnTcr8i7Mt9p4+ixMHEuR6+eMTrjYCL9Rm9TZHviLleCtcZoVzmr2uSc+Vg8MX1YbNCnPbEd4rfV8WvzLcw==
dependencies:
dayjs "^1.8.19"
@ -2774,6 +2774,11 @@ clone-response@^1.0.2:
dependencies:
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:
version "4.6.0"
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"
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:
version "2.0.0"
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"
regexpp "^3.0.0"
eslint-plugin-import@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz#d749a7263fb6c29980def8e960d380a6aa6aecaa"
integrity sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ==
eslint-plugin-import@~2.20.1:
version "2.20.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3"
integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==
dependencies:
array-includes "^3.0.3"
array.prototype.flat "^1.2.1"
@ -3643,13 +3653,12 @@ eslint-plugin-import@~2.20.0:
read-pkg-up "^2.0.0"
resolve "^1.12.0"
eslint-plugin-jest@~23.6.0:
version "23.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.6.0.tgz#508b32f80d44058c8c01257c0ee718cfbd521e9d"
integrity sha512-GH8AhcFXspOLqak7fqnddLXEJsrFyvgO8Bm60SexvKSn1+3rWYESnCiWUOCUcBTprNSDSE4CtAZdM4EyV6gPPw==
eslint-plugin-jest@~23.7.0:
version "23.7.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.7.0.tgz#84d5603b6e745b59898cb6750df6a44782a39b04"
integrity sha512-zkiyGlvJeHNjAEz8FaIxTXNblJJ/zj3waNbYbgflK7K6uy0cpE5zJBt/JpJtOBGM/UGkC6BqsQ4n0y7kQ2HA8w==
dependencies:
"@typescript-eslint/experimental-utils" "^2.5.0"
micromatch "^4.0.2"
eslint-plugin-node@~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"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
fs-capacitor@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-2.0.4.tgz#5a22e72d40ae5078b4fe64fe4d08c0d3fc88ad3c"
integrity sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==
fs-capacitor@6.0.0, fs-capacitor@^2.0.4:
version "6.0.0"
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-6.0.0.tgz#b4b89e3281d61df1c573e788d9ee6ec4c7c94da4"
integrity sha512-I+jZLV2q+ivQK/+Mu5FIYAECHgjoo8GBYJsBBQbNeU0aW1m25LU4E+MkLNq0kcJBjrp8Z6fhxpSeS8SyJyGkrw==
fs-minipass@^1.2.5:
version "1.2.6"
@ -4476,12 +4485,21 @@ graphql-middleware@~4.0.2:
dependencies:
graphql-tools "^4.0.5"
graphql-shield@~7.0.9:
version "7.0.9"
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.0.9.tgz#8248916e9636a7e3c05719a52fd13f2d37ccaeb2"
integrity sha512-2Dfddd2hcObCSqAj64c/Aaxvs7gaoD2QU14crj7H486QjS8jIAtEPUyLVyv8SmJ1ZD7jT6wqx6wrB15Npn5Sgw==
graphql-redis-subscriptions@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.1.2.tgz#9c1b744bace0c6ba99dd0ebafe0148cad1df3301"
integrity sha512-l69KbGxyYfVHxvE+Dzv9/hXg/q+Xnjfx1JsrJD6ikePuSsNaCSNxr+MubSTNF3Gt3C/+JZs4FaWImFeK/+X2og==
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"
yup "^0.28.0"
@ -4492,10 +4510,10 @@ graphql-subscriptions@^1.0.0:
dependencies:
iterall "^1.2.1"
graphql-tag@^2.9.2, graphql-tag@~2.10.1:
version "2.10.1"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02"
integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg==
graphql-tag@^2.9.2, graphql-tag@~2.10.3:
version "2.10.3"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03"
integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==
graphql-tools@^4.0.0, graphql-tools@^4.0.4, graphql-tools@^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"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0:
har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@ -4920,6 +4938,21 @@ invariant@^2.2.2, invariant@^2.2.4:
dependencies:
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:
version "1.0.3"
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"
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:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
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:
version "4.3.0"
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"
integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
metascraper-audio@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.10.6.tgz#095892445b90d40bc54e54f69536a80e36fd9e4c"
integrity sha512-wTVtYK8Ico82caIi6HlkyGgUaBC21X/vhT2aQ4LKcg+gHoOhJcmWNd5me9VhaRJ7gTV/7yKkL5A54fBcjcn8Kg==
metascraper-audio@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.10.7.tgz#ba9f8333a7b71d388a0bf88dff64fc4f06595566"
integrity sha512-VHZlT21bh/TWnHOQMGret3UcMdJOsyWvagK7MG8rLczYmrPEtvxnJjwPhyrEj1oJC+fz2P//bfQ6gyrD4HrmEQ==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
metascraper-author@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.10.6.tgz#1ceaacec776d46629300db25e17657fe35a14a20"
integrity sha512-L2P/Fp0npaQcowbwi1vHKJbSYc99cio58/yYRm205xGfgMCRMpYOrYB+ecizXgeSSRiv8G8SXLrLXOLJ5K+SdA==
metascraper-author@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.10.7.tgz#99b3a2b982b7a63feea41554659be3db7bf7035c"
integrity sha512-AdNkcqy+eqs2Eeh+6odhIWArR4pWVlrCx3jMaho0BDY6ZnKgJP44HtlPNkghQpBaueoKX6CycGKraITzwjGj1w==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
lodash "~4.17.15"
metascraper-clearbit-logo@^5.3.0:
@ -6176,26 +6219,26 @@ metascraper-clearbit-logo@^5.3.0:
dependencies:
got "~9.6.0"
metascraper-date@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-date/-/metascraper-date-5.10.6.tgz#dbdc0ddb4f4220ad1ea412b4a686900c1b138cf6"
integrity sha512-WfZw7WhkMKrrq96ZcAxS01/YjiDBpAPt5e3ggnCfLi2ZzC370w9J0INUo7gAtujaNZvgTTSEcrDD7AbTVMSYKA==
metascraper-date@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-date/-/metascraper-date-5.10.7.tgz#580891d98f14438658610d951e5454a0658eb5f3"
integrity sha512-S1ZsvYrOccS6EGvlAJT7Ph08uRgIE+aYlTRSCR8wG8P0j0Ta/srUKbPySxv+xXqDXuRIChtErSIsBgORg8uNjg==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
metascraper-description@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.10.6.tgz#ebb4459a4e1acdc473534d1b898b7958b1769eb6"
integrity sha512-d3d6UMsNnD8Dy7gxA05iTOj5QmJrFQTw1+IrW9CiOfdNsYq0H+m265a9lRaXcyJdqkzmGnv/d52C0BtUDOrkRw==
metascraper-description@^5.11.0:
version "5.11.0"
resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.11.0.tgz#1f7edfae7ffc353512975cafaa5b862fc5141709"
integrity sha512-or62L9EbIwBxjsu6gShTKi1z1XQ4Hvml8MrdydL4tiSUJpwf+lXc6gVOP8+nlKPPRkiAhXt2M9k4mfkGoX0erQ==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
metascraper-image@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.10.6.tgz#b23ec4bfab0467342b294f3d049c7b7e9a1dd071"
integrity sha512-/+m0VaaqnBgNREun/8jcq+clc4s9Z0FAuMO/TQf/mrz9SbcmpHeTD0WpiGJgEa9zYSbuEQYHdBkxhpw+SdJPCA==
metascraper-image@^5.11.1:
version "5.11.1"
resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.11.1.tgz#e63e9ff045441783f9aa8c684e04927cac289e44"
integrity sha512-Nz2ZTecj2V0KgK2QE390dOSedppaG2PtUBrTz/oaFLMZEReBtMVrcygYm9VuuTpa6XwkubvuBaouCRah12zdfg==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.11.1"
metascraper-lang-detector@^4.10.2:
version "4.10.2"
@ -6206,74 +6249,74 @@ metascraper-lang-detector@^4.10.2:
franc "~4.0.0"
iso-639-3 "~1.1.0"
metascraper-lang@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.10.6.tgz#d4130257f6604095e9af8d796a6bde5815f6c667"
integrity sha512-JDhNbP1iSnPV7d6PklIIdBSzlwqbtvH+n810Isa5/PGuvUkJzNkTAUN+eTM1i6YcTlMp1N2gYsQG9uwfpMwFog==
metascraper-lang@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.10.7.tgz#fe07c359b757ef3c5ba0a330da97173945196fde"
integrity sha512-ulLq7g+X4F9XzoScNflkhwWUuoSycbOqJ8j7vT6E/sHYPid+BdUnw2yaxAqXOdsrsbUEWzkiDwrJiq37XWCpDA==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
metascraper-logo@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.10.6.tgz#22223ce79e4017f159b2a9ddc311d2eb636043b5"
integrity sha512-/uGW+X43T6Oj5DxWqAhANII9BdhQuM+e7O6/Vu116uLqW6cOJ/RDp5qp7ngKF41L0zCLd8Q2Xw2nduHi6tC4Uw==
metascraper-logo@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.10.7.tgz#335812b9aae5d814b22294cdd402d62fa14a46f1"
integrity sha512-Ic9WgvRqm3pUlsMfSirzCK9+qmQ9pbv/u9APn8PM5y66zNJoSCOVWbEIoEA0bVPHbGKGKFvsgrOm3VkMoVUF0Q==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
metascraper-publisher@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.10.6.tgz#95dde6ecef3c7b890ac625893ab374096bebe1f4"
integrity sha512-4jTOpbIwXBADl6z39UzQ4DZLeVoj4Q+5dcHbEgGn9MQ9878FgxiJKyrHzYvqfe9fRNd0PcaFMuuwLyhz58haoA==
metascraper-publisher@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.10.7.tgz#7add40be1625a4215e2ec36dedaaaf213c1870e2"
integrity sha512-aRws5ksH+gzb49nc41oJdoJS2jG/2vC3GCi68n4Evy/TL7TYCnbZXqcgT5U0ne1kFxCZlHB6rl0DcXkx/JlT5Q==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
metascraper-soundcloud@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.10.6.tgz#7aff2e17214b6939719ea726a0f5a5fe2a48c5a5"
integrity sha512-WO+B81e04Hng4/YOtq4dpNv9CrGWVemrNuZk3iIJU+B+gF3YpzdERxS4aIpM7KHcY/c8xu3xDM3LrSUWXWjM3g==
metascraper-soundcloud@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.10.7.tgz#64a5518324ea69576ab9c206f29a60160fb89e91"
integrity sha512-KPM/g+l1m0tp4YOu8qF1RUT7yondaY1S/0aieUUFck/iE3VA1i3MJTLLygIc+67fcbHqcz60qfPTOvYbl6sIPw==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
tldts "~5.6.3"
metascraper-title@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.10.6.tgz#0fd9a9bed7a0b990663086cdab45d11cd8cd3c7d"
integrity sha512-x4P8zr0x6Gh3gt26tf2xfjikG9xNS9MC3z4N2VP+OrYNuCc7Vz6TU+DR/DLAeZphsb1flgTd3P4iUfPUcWVTEQ==
metascraper-title@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.10.7.tgz#037aaa8cbdc79d1dde186887eb2bba542281315e"
integrity sha512-iQYaMdGpBPj6dyk7rbP+zYo7EroC/1yY27jocAqUnMRTfrHXTR7kGucR0vi4y14BiFRLBTLSNbZbM4KAUQsFjg==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
lodash "~4.17.15"
metascraper-url@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.10.6.tgz#45f0ea173fecfe56d60b3cddd3c018f9f4fd9b92"
integrity sha512-7F6uAsI27iVXxUMwwzXH0ret81CX1jgtoGCMz+TvZkyS0z4aUs0r8QpYRYEQuXrW+JawRVik0up54F/ScslObQ==
metascraper-url@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.10.7.tgz#42b71c8540c13baafb7757972ea672721d63e019"
integrity sha512-z1LBPTupU5cF36/i/iGe0rzLbO7iGBSdbgEOztLcnIhnMC8Nl9xjvIrlvNciKTMxDyr3JGrvFFWugFzwMzVoQg==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
metascraper-video@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.10.6.tgz#8425d2dfc378b20612e8ef9324989a33bc0341b3"
integrity sha512-DzWBCe/z86QFv6mN9ZDmvk32FMWv+nPDSkyMEL7RCU6VeQOFFAOjwhDglp2qBMs8Xif358bQ4H/0akLZpDUfvw==
metascraper-video@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.10.7.tgz#2892819a3613ddac115ada7fb1b28d74a646f974"
integrity sha512-+fjiL/Vq0DGd7dMvBTdFKrOK2YH2myHssSChRZVZLl3gzyo4YEWpnOWBhfuoky0caOcs9+RVAxF9pE1TdTFmbA==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
lodash "~4.17.15"
metascraper-youtube@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.10.6.tgz#6cacabb1791b06ed98a7da69aa00c1c6c50a2dfe"
integrity sha512-Yl5kEFawqpSGmVSG2yTVZj7mGfRSFGQ2A4cxpqSbaPIUCGJwG9BUJkMzyUG0m6jGrg0zI5CmeZGNBAXzgKGz4g==
metascraper-youtube@^5.10.7:
version "5.10.7"
resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.10.7.tgz#6c3313563ee57cb71c496fa99c129ad41fe22291"
integrity sha512-T4ZawYpW/2lyoVUY9RM92YCxkuyhNDXFxg8XAG9u2hoNZ5elrHLOv67ao5zMHa7IXZY3A7IGDOBd1NfoShnToA==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
get-video-id "~3.1.4"
is-reachable "~4.0.0"
p-locate "~4.1.0"
metascraper@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.10.6.tgz#d1249577a768566b86bd099cc2256df45cf05181"
integrity sha512-mPEDvoyHLWb0AlTE05W43vfKGgBJ99s5AYAhB4IiRgGR9uq8j7/ktTZaS/+pyFopYrEoe71L/k4KbYgNPQRASA==
metascraper@^5.11.0:
version "5.11.0"
resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.11.0.tgz#bbb25eb055c0ec03992df99c10c48057751ee56b"
integrity sha512-IocQqdSQnOpbai0X9Cu37w/AKeSfU513MCfaFzzcvWgI8s6mGQ1DWRFPvkx0ahtixpP0/ifE4t7ycODrtepRxQ==
dependencies:
"@metascraper/helpers" "^5.10.6"
"@metascraper/helpers" "^5.10.7"
cheerio "~1.0.0-rc.3"
cheerio-advanced-selectors "~2.0.1"
lodash "~4.17.15"
@ -6325,24 +6368,12 @@ migrate@^1.6.2:
mkdirp "^0.5.1"
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:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.22, mime-types@~2.1.24:
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:
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.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
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"
integrity sha512-n69+KXI+kZApR+sPwSkoAXpGlNkaiYyoHHqKOFPjJWvwZpew/EjKvuPE4+tStNgb42z5yLtdakgZCQI+LalSPg==
normalize-url@~4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
normalize-url@~5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-5.0.0.tgz#f46c9dc20670495e4e18fbd1b4396e41d199f63c"
integrity sha512-bAEm2fx8Dq/a35Z6PIRkkBBJvR56BbEJvhpNtvCZ4W9FyORSna77fn+xtYFjqk5JpBS+fMnAOG/wFgkQBmB7hw==
npm-bundled@^1.0.1:
version "1.0.6"
@ -7328,7 +7359,7 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.24, psl@^1.1.28:
psl@^1.1.28:
version "1.3.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd"
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"
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:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@ -7506,6 +7532,23 @@ realpath-native@^1.1.0:
dependencies:
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:
version "1.2.0"
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"
tough-cookie "^2.3.3"
request@^2.88.0, request@~2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
request@^2.88.0, request@~2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@ -7640,7 +7683,7 @@ request@^2.88.0, request@~2.88.0:
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.0"
har-validator "~5.1.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
@ -7650,7 +7693,7 @@ request@^2.88.0, request@~2.88.0:
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.4.3"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
@ -7754,6 +7797,11 @@ rimraf@^3.0.0:
dependencies:
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:
version "1.0.0"
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"
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:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@ -8673,7 +8726,7 @@ touch@^3.1.0:
dependencies:
nopt "~1.0.10"
tough-cookie@^2.3.3:
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@ -8690,14 +8743,6 @@ tough-cookie@^3.0.1:
psl "^1.1.28"
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:
version "1.0.1"
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.
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 I am logged in
Scenario: See an overview of categories
When I navigate to the administration dashboard

View File

@ -1,5 +1,8 @@
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 =
"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) => {
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 => {
cy.factory().create('User', {
cy.factory().build('user', {
termsAndConditionsAgreedVersion: VERSION,
role,
name: `${role} is my name`
}, {
email: `${role}@example.org`,
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', () => {
@ -127,7 +135,7 @@ Given('somebody reported the following posts:', table => {
password: '1234'
}
cy.factory()
.create('User', submitter)
.build('user', {}, submitter)
.authenticateAs(submitter)
.mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
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", () => {
cy.factory()
.create("Post", {
authorId: 'annnoying-user',
.build("post", {
title,
}, {
authorId: 'annnoying-user',
});
})

View File

@ -25,7 +25,6 @@ const narratorParams = {
name: "Peter Pan",
slug: "peter-pan",
avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg",
...loginCredentials,
...termsAndConditionsAgreedVersion,
};
@ -33,65 +32,82 @@ const annoyingParams = {
email: "spammy-spammer@example.org",
slug: 'spammy-spammer',
password: "1234",
...termsAndConditionsAgreedVersion
};
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) => {
cy.logout()
.login({ email: annoyingParams.email, password: '1234' })
.get(".searchable-input .ds-select input")
cy.neode()
.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()
.type(postTitle);
});
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", () => {
cy.createCategories("cat12")
.factory()
.create("Tag", {
id: "Ecology"
})
.create("Tag", {
id: "Nature"
})
.create("Tag", {
id: "Democracy"
});
cy.factory()
.create("User", {
id: 'a1'
})
.create("Post", {
.build('category', { id: 'cat12', name: "Just For Fun", icon: "smile", })
.build('category', { id: 'cat121', name: "Happiness & Values", icon: "heart-o"})
.build('category', { id: 'cat122', name: "Health & Wellbeing", icon: "medkit"})
.build("tag", { id: "Ecology" })
.build("tag", { id: "Nature" })
.build("tag", { id: "Democracy" })
.build("user", { id: 'a1' })
.build("post", {}, {
authorId: 'a1',
tagIds: ["Ecology", "Nature", "Democracy"],
categoryIds: ["cat12"]
})
.create("Post", {
.build("post", {}, {
authorId: 'a1',
tagIds: ["Nature", "Democracy"],
categoryIds: ["cat121"]
});
cy.factory()
.create("User", {
id: 'a2'
})
.create("Post", {
.build("user", { id: 'a2' })
.build("post", {}, {
authorId: 'a2',
tagIds: ['Nature', 'Democracy'],
categoryIds: ["cat12"]
});
cy.factory()
.create("Post", {
authorId: narratorParams.id,
})
.build("post", {}, {
tagIds: ['Democracy'],
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 => {
table.hashes().forEach(params => {
cy.factory().create("User", {
cy.factory().build("user", {
...params,
...termsAndConditionsAgreedVersion
});
}, params);
});
});
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 => {
cy.factory().create("User", {
cy.factory().build("user", {
role,
...loginCredentials,
...termsAndConditionsAgreedVersion,
});
}, loginCredentials);
});
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", () => {
cy.logout()
.login({ email: annoyingParams.email, password: annoyingParams.password })
.openPage('/post/previously-created-post')
cy.neode()
.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 => {
@ -139,7 +163,7 @@ Given("I am on the {string} page", page => {
});
When("I fill in my email and password combination and click submit", () => {
cy.login(loginCredentials);
cy.manualLogin(loginCredentials);
});
When(/(?:when )?I refresh the page/, () => {
@ -203,33 +227,29 @@ When("I press {string}", label => {
cy.contains(label).click();
});
Given("we have this user in our database:", table => {
const [firstRow] = table.hashes()
cy.factory().create('User', firstRow)
})
Given("we have the following posts in our database:", table => {
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 comments in our database:", table => {
table.hashes().forEach((attributesOrOptions, i) => {
cy.factory().build("comment", {
...attributesOrOptions,
}, {
...attributesOrOptions,
});
})
});
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 => {
cy.contains(message);
});
@ -242,15 +262,20 @@ When(
"I click on the big plus icon in the bottom right corner to create post",
() => {
cy.get(".post-add-button").click();
cy.location("pathname").should('eq', '/post/create')
}
);
Given("I previously created a post", () => {
lastPost.authorId = narratorParams.id
lastPost.title = "previously created post";
lastPost.content = "with some content";
lastPost = {
lastPost,
title: "previously created post",
content: "with some content",
};
cy.factory()
.create("Post", lastPost);
.build("post", lastPost, {
authorId: narratorParams.id
});
});
When("I choose {string} as the title of the post", title => {
@ -311,17 +336,27 @@ Then(
cy.visit(route, {
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];
cy.debug();
cy.factory().create("User", {
cy.factory().build("user", {
...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 => {
@ -340,45 +375,16 @@ When("submit the form", () => {
Then("I cannot login anymore with password {string}", password => {
cy.reload();
const {
email
} = loginCredentials;
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."
);
const { email } = loginCredentials
cy.manualLogin({ email, password })
.get(".iziToast-wrapper").should("contain", "Incorrect email address or password.");
});
Then("I can login successfully with password {string}", password => {
cy.reload();
cy.login({
...loginCredentials,
...{
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
});
const { email } = loginCredentials
cy.manualLogin({ email, password })
.get(".iziToast-wrapper").should("contain", "You are logged in!");
});
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 => {
cy.factory().create("User", {
...annoyingParams,
cy.factory().build("user", {
id: "annoying-user",
name,
...termsAndConditionsAgreedVersion,
});
}, annoyingParams);
});
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 => {
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 => {
cy.openPage("/profile/annoying-user");
cy.openPage("profile/annoying-user");
});
When("I ", name => {
cy.openPage("/profile/annoying-user");
cy.openPage("profile/annoying-user");
});
When(
@ -498,12 +503,11 @@ Given("I follow the user {string}", name => {
});
Given('{string} wrote a post {string}', (_, title) => {
cy.createCategories("cat21")
.factory()
.create("Post", {
authorId: 'annoying-user',
cy.factory()
.build("post", {
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 => {
cy.createCategories(`cat213`, title)
.factory()
.create("Post", {
authorId: narratorParams.id,
cy.factory()
.build("post", {
title,
categoryIds: ["cat213"]
}, {
authorId: narratorParams.id,
});
});
@ -552,22 +555,24 @@ When("I block the user {string}", name => {
.then(blockedUser => {
cy.neode()
.first("User", {
name: narratorParams.name
id: narratorParams.id
})
.relateTo(blockedUser, "blocked");
});
});
When("I log in with:", table => {
const [firstRow] = table.hashes();
const {
Email,
Password
} = firstRow;
cy.login({
email: Email,
password: Password
});
When("a user has blocked me", () => {
cy.neode()
.first("User", {
name: narratorParams.name
})
.then(blockedUser => {
cy.neode()
.first("User", {
name: 'Harassing User'
})
.relateTo(blockedUser, "blocked");
});
});
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')
})
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.")
})
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:
| submitterEmail | resourceId | reasonCategory | reasonDescription |
| 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 I am logged in
When I click on the avatar menu in the top right corner
And I click on "Moderation"
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 |
Scenario: Mention another user, re-login as this user and see notifications
Given I log in with the following credentials:
| email | password |
| wolle@example.org | 1234 |
Given I log in as "Wolle aus Hamburg"
And I start to write a new post with the title "Hey Matt" beginning with:
"""
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 click on "Save"
When I log out
And I log in with the following credentials:
| email | password |
| matt@example.org | 4321 |
And I log in as "Matt Rider"
And see 1 unread notifications in the top menu
And open the notification menu and click on the first item
Then I get to the post page of ".../hey-matt"

View File

@ -6,8 +6,11 @@ Feature: Post Comment
Background:
Given I have a user account
And we have the following posts in our database:
| id | title | slug | authorId | commentContent |
| bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | id-of-peter-pan | @peter-pan reply to me |
| id | title | slug | authorId |
| 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
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.
Background:
Given my user account has the following login credentials:
Given I am logged in with these credentials:
| email | password |
| user@example.org | exposed |
And I am logged in
Scenario: Change my password
Given I am on the "settings" page

View File

@ -7,7 +7,7 @@ Feature: Authentication
Given I have a user account
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
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

View File

@ -11,6 +11,7 @@ Feature: Block a User
Scenario: Block a 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
And I "should" see "Unblock user" from the content menu in the user info box
And I navigate to my "Blocked users" settings page
Then I can see the following table:
| Avatar | Name |
@ -20,14 +21,15 @@ Feature: Block a User
Given I block the user "Harassing User"
And I previously created a post
And a blocked user visits the post page of one of my authored posts
Then they should not see the comment from
And they should see a text explaining commenting is not possible
Then they should see a text explaining why commenting is not possible
And they should not see the comment form
Scenario: Block a previously followed user
Given I follow the user "Harassing 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 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
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:
| title |
| 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_USERNAME = parsed.NEO4J_USERNAME
config.env.NEO4J_PASSWORD = parsed.NEO4J_PASSWORD
config.env.JWT_SECRET = parsed.JWT_SECRET
on('file:preprocessor', cucumber())
return config
}

View File

@ -18,20 +18,24 @@ import helpers from "./helpers";
import { GraphQLClient, request } from 'graphql-request'
import { gql } from '../../backend/src/helpers/jest'
import config from '../../backend/src/config'
import encode from '../../backend/src/jwt/encode'
const switchLang = name => {
cy.get(".locale-menu").click();
cy.contains(".locale-menu-popover a", name).click();
};
const authenticatedHeaders = async (variables) => {
const authenticatedHeaders = (variables) => {
const mutation = gql`
mutation($email: String!, $password: String!) {
login(email: $email, password: $password)
}
`
const response = await request(config.GRAPHQL_URI, mutation, variables)
return { authorization: `Bearer ${response.login}` }
return new Cypress.Promise((resolve, reject) => {
request(config.GRAPHQL_URI, mutation, variables).then((response) => {
resolve({ authorization: `Bearer ${response.login}` })
})
})
}
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.get("input[name=email]")
.trigger("focus")
@ -58,11 +68,9 @@ Cypress.Commands.add("login", ({ email, password }) => {
cy.get("button[name=submit]")
.as("submitButton")
.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.location("pathname").should("contain", "/login"); // we're out
});
@ -74,43 +82,24 @@ Cypress.Commands.add("openPage", 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(
'authenticateAs',
async ({email, password}) => {
const headers = await authenticatedHeaders({ email, password })
return new GraphQLClient(config.GRAPHQL_URI, { headers })
}
)
({email, password}) => {
return new Cypress.Promise((resolve, reject) => {
authenticatedHeaders({ email, password }).then((headers) => {
resolve(new GraphQLClient(config.GRAPHQL_URI, { headers }))
})
})
})
Cypress.Commands.add(
'mutate',
{ prevSubject: true },
async (graphQLClient, mutation, variables) => {
await graphQLClient.request(mutation, variables)
return graphQLClient
}
)
(graphQLClient, mutation, variables) => {
return new Cypress.Promise((resolve, reject) => {
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'
const neo4jConfigs = {
@ -6,51 +6,38 @@ const neo4jConfigs = {
username: Cypress.env('NEO4J_USERNAME'),
password: Cypress.env('NEO4J_PASSWORD')
}
const neo4jDriver = getDriver(neo4jConfigs)
const neodeInstance = getNeode(neo4jConfigs)
const factoryOptions = { neo4jDriver, neodeInstance }
const factory = Factory(factoryOptions)
beforeEach(async () => {
await factory.cleanDatabase()
})
beforeEach(() => cleanDatabase())
Cypress.Commands.add('neode', () => {
return neodeInstance
})
Cypress.Commands.add(
'first',
{ prevSubject: true },
async (neode, model, properties) => {
(neode, model, properties) => {
return neode.first(model, properties)
}
)
Cypress.Commands.add(
'relateTo',
{ prevSubject: true },
async (node, otherNode, relationship) => {
(node, otherNode, relationship) => {
return node.relateTo(otherNode, relationship)
}
)
Cypress.Commands.add('factory', () => {
return Factory(factoryOptions)
})
Cypress.Commands.add('factory', () => Factory)
Cypress.Commands.add(
'create',
'build',
{ prevSubject: true },
async (factory, node, properties) => {
await factory.create(node, properties)
return factory
(factory, name, atrributes, options) => {
return new Cypress.Promise((resolve, reject) => {
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
**/secrets.yaml
**/configmap.yaml
**/staging-values.yaml
**/production-values.yaml

View File

@ -1,11 +1,10 @@
# Human-Connection Nitro \| Deployment Configuration
We deploy with [kubernetes](https://kubernetes.io/). In order to deploy your own
network you have to [install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
and get a kubernetes cluster.
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/), [install Helm](https://helm.sh/docs/intro/install/) (optional, but the preferred way),
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)
and [Digital Ocean](./digital-ocean/README.md).
Check out the specific documentation for your provider. After that, learn how
to apply the specific kubernetes configuration for [Human Connection](./human-connection/README.md).
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).

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