mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of https://github.com/Human-Connection/Human-Connection into 552-update_comment
This commit is contained in:
commit
5e9f46405e
@ -29,7 +29,7 @@ script:
|
|||||||
- docker-compose exec backend yarn run test:jest --ci --verbose=false --coverage
|
- docker-compose exec backend yarn run test:jest --ci --verbose=false --coverage
|
||||||
- docker-compose exec backend yarn run db:reset
|
- docker-compose exec backend yarn run db:reset
|
||||||
- docker-compose exec backend yarn run db:seed
|
- docker-compose exec backend yarn run db:seed
|
||||||
- docker-compose exec backend yarn run test:cucumber
|
- docker-compose exec backend yarn run test:cucumber --tags "not @wip"
|
||||||
- docker-compose exec backend yarn run db:reset
|
- docker-compose exec backend yarn run db:reset
|
||||||
- docker-compose exec backend yarn run db:seed
|
- docker-compose exec backend yarn run db:seed
|
||||||
# Frontend
|
# Frontend
|
||||||
@ -37,9 +37,7 @@ script:
|
|||||||
- docker-compose exec webapp yarn run test --ci --verbose=false --coverage
|
- docker-compose exec webapp yarn run test --ci --verbose=false --coverage
|
||||||
- docker-compose exec -d backend yarn run test:before:seeder
|
- docker-compose exec -d backend yarn run test:before:seeder
|
||||||
# Fullstack
|
# Fullstack
|
||||||
# Disable recording cypress tests if we just update dependencies. This is to
|
- yarn run cypress:run
|
||||||
# avoid running out of quota.
|
|
||||||
- if [[ $BRANCH == *"dependabot"* ]]; then yarn run cypress:run; else yarn run cypress:run --record; fi
|
|
||||||
# Coverage
|
# Coverage
|
||||||
- codecov
|
- codecov
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
* [Kubernetes Dashboard](deployment/digital-ocean/dashboard/README.md)
|
* [Kubernetes Dashboard](deployment/digital-ocean/dashboard/README.md)
|
||||||
* [HTTPS](deployment/digital-ocean/https/README.md)
|
* [HTTPS](deployment/digital-ocean/https/README.md)
|
||||||
* [Human Connection](deployment/human-connection/README.md)
|
* [Human Connection](deployment/human-connection/README.md)
|
||||||
|
* [Mailserver](deployment/human-connection/mailserver/README.md)
|
||||||
* [Volumes](deployment/volumes/README.md)
|
* [Volumes](deployment/volumes/README.md)
|
||||||
* [Neo4J Offline-Backups](deployment/volumes/neo4j-offline-backup/README.md)
|
* [Neo4J Offline-Backups](deployment/volumes/neo4j-offline-backup/README.md)
|
||||||
* [Volume Snapshots](deployment/volumes/volume-snapshots/README.md)
|
* [Volume Snapshots](deployment/volumes/volume-snapshots/README.md)
|
||||||
|
|||||||
@ -5,6 +5,11 @@ GRAPHQL_PORT=4000
|
|||||||
GRAPHQL_URI=http://localhost:4000
|
GRAPHQL_URI=http://localhost:4000
|
||||||
CLIENT_URI=http://localhost:3000
|
CLIENT_URI=http://localhost:3000
|
||||||
MOCKS=false
|
MOCKS=false
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=
|
||||||
|
SMTP_IGNORE_TLS=true
|
||||||
|
SMTP_USERNAME=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
|
||||||
JWT_SECRET="b/&&7b78BF&fv/Vd"
|
JWT_SECRET="b/&&7b78BF&fv/Vd"
|
||||||
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
|
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
|
||||||
|
|||||||
@ -44,6 +44,9 @@ or start the backend in production environment with:
|
|||||||
yarn run start
|
yarn run start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For e-mail delivery, please configure at least `SMTP_HOST` and `SMTP_PORT` in
|
||||||
|
your `.env` configuration file.
|
||||||
|
|
||||||
Your backend is up and running at [http://localhost:4000/](http://localhost:4000/)
|
Your backend is up and running at [http://localhost:4000/](http://localhost:4000/)
|
||||||
This will start the GraphQL service \(by default on localhost:4000\) where you
|
This will start the GraphQL service \(by default on localhost:4000\) where you
|
||||||
can issue GraphQL requests or access GraphQL Playground in the browser.
|
can issue GraphQL requests or access GraphQL Playground in the browser.
|
||||||
|
|||||||
@ -44,34 +44,35 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"activitystrea.ms": "~2.1.3",
|
"activitystrea.ms": "~2.1.3",
|
||||||
"apollo-cache-inmemory": "~1.6.2",
|
"apollo-cache-inmemory": "~1.6.2",
|
||||||
"apollo-client": "~2.6.2",
|
"apollo-client": "~2.6.3",
|
||||||
"apollo-link-context": "~1.0.14",
|
"apollo-link-context": "~1.0.18",
|
||||||
"apollo-link-http": "~1.5.14",
|
"apollo-link-http": "~1.5.15",
|
||||||
"apollo-server": "~2.6.2",
|
"apollo-server": "~2.6.6",
|
||||||
"bcryptjs": "~2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
"cheerio": "~1.0.0-rc.3",
|
"cheerio": "~1.0.0-rc.3",
|
||||||
"cors": "~2.8.5",
|
"cors": "~2.8.5",
|
||||||
"cross-env": "~5.2.0",
|
"cross-env": "~5.2.0",
|
||||||
"date-fns": "2.0.0-alpha.31",
|
"date-fns": "2.0.0-alpha.37",
|
||||||
"debug": "~4.1.1",
|
"debug": "~4.1.1",
|
||||||
"dotenv": "~8.0.0",
|
"dotenv": "~8.0.0",
|
||||||
"express": "~4.17.1",
|
"express": "~4.17.1",
|
||||||
"faker": "~4.1.0",
|
"faker": "Marak/faker.js#master",
|
||||||
"graphql": "~14.3.1",
|
"graphql": "~14.3.1",
|
||||||
"graphql-custom-directives": "~0.2.14",
|
"graphql-custom-directives": "~0.2.14",
|
||||||
"graphql-iso-date": "~3.6.1",
|
"graphql-iso-date": "~3.6.1",
|
||||||
"graphql-middleware": "~3.0.2",
|
"graphql-middleware": "~3.0.2",
|
||||||
"graphql-shield": "~5.3.8",
|
"graphql-shield": "~5.7.1",
|
||||||
"graphql-tag": "~2.10.1",
|
"graphql-tag": "~2.10.1",
|
||||||
"graphql-yoga": "~1.17.4",
|
"graphql-yoga": "~1.18.0",
|
||||||
"helmet": "~3.18.0",
|
"helmet": "~3.18.0",
|
||||||
"jsonwebtoken": "~8.5.1",
|
"jsonwebtoken": "~8.5.1",
|
||||||
"linkifyjs": "~2.1.8",
|
"linkifyjs": "~2.1.8",
|
||||||
"lodash": "~4.17.11",
|
"lodash": "~4.17.11",
|
||||||
"merge-graphql-schemas": "^1.5.8",
|
"merge-graphql-schemas": "^1.5.8",
|
||||||
"neo4j-driver": "~1.7.4",
|
"neo4j-driver": "~1.7.4",
|
||||||
"neo4j-graphql-js": "git+https://github.com/Human-Connection/neo4j-graphql-js.git#temporary_fixes",
|
"neo4j-graphql-js": "^2.6.3",
|
||||||
"node-fetch": "~2.6.0",
|
"node-fetch": "~2.6.0",
|
||||||
|
"nodemailer": "^6.2.1",
|
||||||
"npm-run-all": "~4.1.5",
|
"npm-run-all": "~4.1.5",
|
||||||
"request": "~2.88.0",
|
"request": "~2.88.0",
|
||||||
"sanitize-html": "~1.20.1",
|
"sanitize-html": "~1.20.1",
|
||||||
@ -87,20 +88,20 @@
|
|||||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||||
"@babel/preset-env": "~7.4.5",
|
"@babel/preset-env": "~7.4.5",
|
||||||
"@babel/register": "~7.4.4",
|
"@babel/register": "~7.4.4",
|
||||||
"apollo-server-testing": "~2.6.2",
|
"apollo-server-testing": "~2.6.6",
|
||||||
"babel-core": "~7.0.0-0",
|
"babel-core": "~7.0.0-0",
|
||||||
"babel-eslint": "~10.0.1",
|
"babel-eslint": "~10.0.2",
|
||||||
"babel-jest": "~24.8.0",
|
"babel-jest": "~24.8.0",
|
||||||
"chai": "~4.2.0",
|
"chai": "~4.2.0",
|
||||||
"cucumber": "~5.1.0",
|
"cucumber": "~5.1.0",
|
||||||
"eslint": "~5.16.0",
|
"eslint": "~6.0.1",
|
||||||
"eslint-config-prettier": "~4.3.0",
|
"eslint-config-prettier": "~5.0.0",
|
||||||
"eslint-config-standard": "~12.0.0",
|
"eslint-config-standard": "~12.0.0",
|
||||||
"eslint-plugin-import": "~2.17.3",
|
"eslint-plugin-import": "~2.18.0",
|
||||||
"eslint-plugin-jest": "~22.6.4",
|
"eslint-plugin-jest": "~22.7.1",
|
||||||
"eslint-plugin-node": "~9.1.0",
|
"eslint-plugin-node": "~9.1.0",
|
||||||
"eslint-plugin-prettier": "~3.1.0",
|
"eslint-plugin-prettier": "~3.1.0",
|
||||||
"eslint-plugin-promise": "~4.1.1",
|
"eslint-plugin-promise": "~4.2.1",
|
||||||
"eslint-plugin-standard": "~4.0.0",
|
"eslint-plugin-standard": "~4.0.0",
|
||||||
"graphql-request": "~1.8.2",
|
"graphql-request": "~1.8.2",
|
||||||
"jest": "~24.8.0",
|
"jest": "~24.8.0",
|
||||||
|
|||||||
@ -2,23 +2,33 @@ import dotenv from 'dotenv'
|
|||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
export const requiredConfigs = {
|
const {
|
||||||
MAPBOX_TOKEN: process.env.MAPBOX_TOKEN,
|
MAPBOX_TOKEN,
|
||||||
JWT_SECRET: process.env.JWT_SECRET,
|
JWT_SECRET,
|
||||||
PRIVATE_KEY_PASSPHRASE: process.env.PRIVATE_KEY_PASSPHRASE,
|
PRIVATE_KEY_PASSPHRASE,
|
||||||
}
|
SMTP_IGNORE_TLS = true,
|
||||||
|
SMTP_HOST,
|
||||||
|
SMTP_PORT,
|
||||||
|
SMTP_USERNAME,
|
||||||
|
SMTP_PASSWORD,
|
||||||
|
NEO4J_URI = 'bolt://localhost:7687',
|
||||||
|
NEO4J_USERNAME = 'neo4j',
|
||||||
|
NEO4J_PASSWORD = 'neo4j',
|
||||||
|
GRAPHQL_PORT = 4000,
|
||||||
|
CLIENT_URI = 'http://localhost:3000',
|
||||||
|
GRAPHQL_URI = 'http://localhost:4000',
|
||||||
|
} = process.env
|
||||||
|
|
||||||
export const neo4jConfigs = {
|
export const requiredConfigs = { MAPBOX_TOKEN, JWT_SECRET, PRIVATE_KEY_PASSPHRASE }
|
||||||
NEO4J_URI: process.env.NEO4J_URI || 'bolt://localhost:7687',
|
export const smtpConfigs = {
|
||||||
NEO4J_USERNAME: process.env.NEO4J_USERNAME || 'neo4j',
|
SMTP_HOST,
|
||||||
NEO4J_PASSWORD: process.env.NEO4J_PASSWORD || 'neo4j',
|
SMTP_PORT,
|
||||||
}
|
SMTP_IGNORE_TLS,
|
||||||
|
SMTP_USERNAME,
|
||||||
export const serverConfigs = {
|
SMTP_PASSWORD,
|
||||||
GRAPHQL_PORT: process.env.GRAPHQL_PORT || 4000,
|
|
||||||
CLIENT_URI: process.env.CLIENT_URI || 'http://localhost:3000',
|
|
||||||
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000',
|
|
||||||
}
|
}
|
||||||
|
export const neo4jConfigs = { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD }
|
||||||
|
export const serverConfigs = { GRAPHQL_PORT, CLIENT_URI, GRAPHQL_URI }
|
||||||
|
|
||||||
export const developmentConfigs = {
|
export const developmentConfigs = {
|
||||||
DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'true',
|
DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'true',
|
||||||
@ -29,6 +39,7 @@ export const developmentConfigs = {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
...requiredConfigs,
|
...requiredConfigs,
|
||||||
|
...smtpConfigs,
|
||||||
...neo4jConfigs,
|
...neo4jConfigs,
|
||||||
...serverConfigs,
|
...serverConfigs,
|
||||||
...developmentConfigs,
|
...developmentConfigs,
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
const legacyUrls = [
|
|
||||||
'https://api-alpha.human-connection.org',
|
|
||||||
'https://staging-api.human-connection.org',
|
|
||||||
'http://localhost:3000',
|
|
||||||
]
|
|
||||||
|
|
||||||
export const fixUrl = url => {
|
|
||||||
legacyUrls.forEach(legacyUrl => {
|
|
||||||
url = url.replace(legacyUrl, '')
|
|
||||||
})
|
|
||||||
if (!url.startsWith('/')) {
|
|
||||||
url = `/${url}`
|
|
||||||
}
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkUrl = thing => {
|
|
||||||
return (
|
|
||||||
thing &&
|
|
||||||
typeof thing === 'string' &&
|
|
||||||
legacyUrls.find(legacyUrl => {
|
|
||||||
return thing.indexOf(legacyUrl) === 0
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fixImageURLs = (result, recursive) => {
|
|
||||||
if (checkUrl(result)) {
|
|
||||||
result = fixUrl(result)
|
|
||||||
} else if (result && Array.isArray(result)) {
|
|
||||||
result.forEach((res, index) => {
|
|
||||||
result[index] = fixImageURLs(result[index], true)
|
|
||||||
})
|
|
||||||
} else if (result && typeof result === 'object') {
|
|
||||||
Object.keys(result).forEach(key => {
|
|
||||||
result[key] = fixImageURLs(result[key], true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
Mutation: async (resolve, root, args, context, info) => {
|
|
||||||
const result = await resolve(root, args, context, info)
|
|
||||||
return fixImageURLs(result)
|
|
||||||
},
|
|
||||||
Query: async (resolve, root, args, context, info) => {
|
|
||||||
let result = await resolve(root, args, context, info)
|
|
||||||
return fixImageURLs(result)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import { fixImageURLs } from './fixImageUrlsMiddleware'
|
|
||||||
|
|
||||||
describe('fixImageURLs', () => {
|
|
||||||
describe('edge case: image url is exact match of legacy url', () => {
|
|
||||||
it('replaces it with `/`', () => {
|
|
||||||
const url = 'https://api-alpha.human-connection.org'
|
|
||||||
expect(fixImageURLs(url)).toEqual('/')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('image url of legacy alpha', () => {
|
|
||||||
it('removes domain', () => {
|
|
||||||
const url =
|
|
||||||
'https://api-alpha.human-connection.org/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png'
|
|
||||||
expect(fixImageURLs(url)).toEqual(
|
|
||||||
'/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('image url of legacy staging', () => {
|
|
||||||
it('removes domain', () => {
|
|
||||||
const url =
|
|
||||||
'https://staging-api.human-connection.org/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg'
|
|
||||||
expect(fixImageURLs(url)).toEqual(
|
|
||||||
'/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('object', () => {
|
|
||||||
it('returns untouched', () => {
|
|
||||||
const object = { some: 'thing' }
|
|
||||||
expect(fixImageURLs(object)).toEqual(object)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('some string', () => {
|
|
||||||
it('returns untouched', () => {})
|
|
||||||
const string = "Yeah I'm a String"
|
|
||||||
expect(fixImageURLs(string)).toEqual(string)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -3,7 +3,6 @@ import activityPub from './activityPubMiddleware'
|
|||||||
import password from './passwordMiddleware'
|
import password from './passwordMiddleware'
|
||||||
import softDelete from './softDeleteMiddleware'
|
import softDelete from './softDeleteMiddleware'
|
||||||
import sluggify from './sluggifyMiddleware'
|
import sluggify from './sluggifyMiddleware'
|
||||||
import fixImageUrls from './fixImageUrlsMiddleware'
|
|
||||||
import excerpt from './excerptMiddleware'
|
import excerpt from './excerptMiddleware'
|
||||||
import dateTime from './dateTimeMiddleware'
|
import dateTime from './dateTimeMiddleware'
|
||||||
import xss from './xssMiddleware'
|
import xss from './xssMiddleware'
|
||||||
@ -25,7 +24,6 @@ export default schema => {
|
|||||||
excerpt: excerpt,
|
excerpt: excerpt,
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
xss: xss,
|
xss: xss,
|
||||||
fixImageUrls: fixImageUrls,
|
|
||||||
softDelete: softDelete,
|
softDelete: softDelete,
|
||||||
user: user,
|
user: user,
|
||||||
includedFields: includedFields,
|
includedFields: includedFields,
|
||||||
@ -42,7 +40,6 @@ export default schema => {
|
|||||||
'excerpt',
|
'excerpt',
|
||||||
'notifications',
|
'notifications',
|
||||||
'xss',
|
'xss',
|
||||||
'fixImageUrls',
|
|
||||||
'softDelete',
|
'softDelete',
|
||||||
'user',
|
'user',
|
||||||
'includedFields',
|
'includedFields',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { rule, shield, allow, or } from 'graphql-shield'
|
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: implement
|
* TODO: implement
|
||||||
@ -16,6 +16,12 @@ const isAdmin = rule()(async (parent, args, { user }, info) => {
|
|||||||
return user && user.role === 'admin'
|
return user && user.role === 'admin'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onlyYourself = rule({
|
||||||
|
cache: 'no_cache',
|
||||||
|
})(async (parent, args, context, info) => {
|
||||||
|
return context.user.id === args.id
|
||||||
|
})
|
||||||
|
|
||||||
const isMyOwn = rule({
|
const isMyOwn = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (parent, args, context, info) => {
|
})(async (parent, args, context, info) => {
|
||||||
@ -48,6 +54,13 @@ const belongsToMe = rule({
|
|||||||
return Boolean(notification)
|
return Boolean(notification)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* TODO: decide if we want to remove this check: the check
|
||||||
|
* `onlyEnabledContent` throws authorization errors only if you have
|
||||||
|
* arguments for `disabled` or `deleted` assuming these are filter
|
||||||
|
* parameters. Soft-delete middleware obfuscates data on its way out
|
||||||
|
* anyways. Furthermore, `neo4j-graphql-js` offers many ways to filter for
|
||||||
|
* data so I believe, this is not a good check anyways.
|
||||||
|
*/
|
||||||
const onlyEnabledContent = rule({
|
const onlyEnabledContent = rule({
|
||||||
cache: 'strict',
|
cache: 'strict',
|
||||||
})(async (parent, args, ctx, info) => {
|
})(async (parent, args, ctx, info) => {
|
||||||
@ -80,16 +93,35 @@ const isAuthor = rule({
|
|||||||
return authorId === user.id
|
return authorId === user.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isDeletingOwnAccount = rule({
|
||||||
|
cache: 'no_cache',
|
||||||
|
})(async (parent, args, context, info) => {
|
||||||
|
return context.user.id === args.id
|
||||||
|
})
|
||||||
|
|
||||||
// Permissions
|
// Permissions
|
||||||
const permissions = shield({
|
const permissions = shield(
|
||||||
|
{
|
||||||
Query: {
|
Query: {
|
||||||
|
'*': deny,
|
||||||
|
findPosts: allow,
|
||||||
|
Category: isAdmin,
|
||||||
|
Tag: isAdmin,
|
||||||
|
Report: isModerator,
|
||||||
Notification: isAdmin,
|
Notification: isAdmin,
|
||||||
statistics: allow,
|
statistics: allow,
|
||||||
currentUser: allow,
|
currentUser: allow,
|
||||||
Post: or(onlyEnabledContent, isModerator),
|
Post: or(onlyEnabledContent, isModerator),
|
||||||
|
Comment: allow,
|
||||||
|
User: allow,
|
||||||
|
isLoggedIn: allow,
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
|
'*': deny,
|
||||||
|
login: allow,
|
||||||
UpdateNotification: belongsToMe,
|
UpdateNotification: belongsToMe,
|
||||||
|
CreateUser: isAdmin,
|
||||||
|
UpdateUser: onlyYourself,
|
||||||
CreatePost: isAuthenticated,
|
CreatePost: isAuthenticated,
|
||||||
UpdatePost: isAuthor,
|
UpdatePost: isAuthor,
|
||||||
DeletePost: isAuthor,
|
DeletePost: isAuthor,
|
||||||
@ -115,13 +147,19 @@ const permissions = shield({
|
|||||||
CreateComment: isAuthenticated,
|
CreateComment: isAuthenticated,
|
||||||
UpdateComment: isAuthor,
|
UpdateComment: isAuthor,
|
||||||
DeleteComment: isAuthor,
|
DeleteComment: isAuthor,
|
||||||
// CreateUser: allow,
|
DeleteUser: isDeletingOwnAccount,
|
||||||
|
requestPasswordReset: allow,
|
||||||
|
resetPassword: allow,
|
||||||
},
|
},
|
||||||
User: {
|
User: {
|
||||||
email: isMyOwn,
|
email: isMyOwn,
|
||||||
password: isMyOwn,
|
password: isMyOwn,
|
||||||
privateKey: isMyOwn,
|
privateKey: isMyOwn,
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
fallbackRule: allow,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
export default permissions
|
export default permissions
|
||||||
|
|||||||
@ -7,12 +7,14 @@ let headers
|
|||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.create('User', { email: 'user@example.org', password: '1234' })
|
const adminParams = { role: 'admin', email: 'admin@example.org', password: '1234' }
|
||||||
|
await factory.create('User', adminParams)
|
||||||
await factory.create('User', {
|
await factory.create('User', {
|
||||||
email: 'someone@example.org',
|
email: 'someone@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
})
|
})
|
||||||
headers = await login({ email: 'user@example.org', password: '1234' })
|
// we need to be an admin, otherwise we're not authorized to create a user
|
||||||
|
headers = await login(adminParams)
|
||||||
authenticatedClient = new GraphQLClient(host, { headers })
|
authenticatedClient = new GraphQLClient(host, { headers })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -38,22 +38,41 @@ export default {
|
|||||||
if (!post) {
|
if (!post) {
|
||||||
throw new UserInputError(NO_POST_ERR_MESSAGE)
|
throw new UserInputError(NO_POST_ERR_MESSAGE)
|
||||||
}
|
}
|
||||||
const comment = await neo4jgraphql(object, params, context, resolveInfo, false)
|
const commentWithoutRelationships = await neo4jgraphql(
|
||||||
|
object,
|
||||||
|
params,
|
||||||
|
context,
|
||||||
|
resolveInfo,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
await session.run(
|
let transactionRes = await session.run(
|
||||||
`
|
`
|
||||||
MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}), (author:User {id: $userId})
|
MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}), (author:User {id: $userId})
|
||||||
MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
|
MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
|
||||||
RETURN post`,
|
RETURN comment, author`,
|
||||||
{
|
{
|
||||||
userId: context.user.id,
|
userId: context.user.id,
|
||||||
postId,
|
postId,
|
||||||
commentId: comment.id,
|
commentId: commentWithoutRelationships.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
session.close()
|
|
||||||
|
|
||||||
return comment
|
const [commentWithAuthor] = transactionRes.records.map(record => {
|
||||||
|
return {
|
||||||
|
comment: record.get('comment'),
|
||||||
|
author: record.get('author'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { comment, author } = commentWithAuthor
|
||||||
|
|
||||||
|
const commentReturnedWithAuthor = {
|
||||||
|
...comment.properties,
|
||||||
|
author: author.properties,
|
||||||
|
}
|
||||||
|
session.close()
|
||||||
|
return commentReturnedWithAuthor
|
||||||
},
|
},
|
||||||
UpdateComment: async (object, params, context, resolveInfo) => {
|
UpdateComment: async (object, params, context, resolveInfo) => {
|
||||||
await neo4jgraphql(object, params, context, resolveInfo, false)
|
await neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
|
|||||||
72
backend/src/schema/resolvers/passwordReset.js
Normal file
72
backend/src/schema/resolvers/passwordReset.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import uuid from 'uuid/v4'
|
||||||
|
import bcrypt from 'bcryptjs'
|
||||||
|
import CONFIG from '../../config'
|
||||||
|
import nodemailer from 'nodemailer'
|
||||||
|
import { resetPasswordMail, wrongAccountMail } from './passwordReset/emailTemplates'
|
||||||
|
|
||||||
|
const transporter = () => {
|
||||||
|
const configs = {
|
||||||
|
host: CONFIG.SMTP_HOST,
|
||||||
|
port: CONFIG.SMTP_PORT,
|
||||||
|
ignoreTLS: CONFIG.SMTP_IGNORE_TLS,
|
||||||
|
secure: false, // true for 465, false for other ports
|
||||||
|
}
|
||||||
|
const { SMTP_USERNAME: user, SMTP_PASSWORD: pass } = CONFIG
|
||||||
|
if (user && pass) {
|
||||||
|
configs.auth = { user, pass }
|
||||||
|
}
|
||||||
|
return nodemailer.createTransport(configs)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createPasswordReset(options) {
|
||||||
|
const { driver, code, email, issuedAt = new Date() } = options
|
||||||
|
const session = driver.session()
|
||||||
|
const cypher = `
|
||||||
|
MATCH (u:User) WHERE u.email = $email
|
||||||
|
CREATE(pr:PasswordReset {code: $code, issuedAt: datetime($issuedAt), usedAt: NULL})
|
||||||
|
MERGE (u)-[:REQUESTED]->(pr)
|
||||||
|
RETURN u
|
||||||
|
`
|
||||||
|
const transactionRes = await session.run(cypher, {
|
||||||
|
issuedAt: issuedAt.toISOString(),
|
||||||
|
code,
|
||||||
|
email,
|
||||||
|
})
|
||||||
|
const users = transactionRes.records.map(record => record.get('u'))
|
||||||
|
session.close()
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Mutation: {
|
||||||
|
requestPasswordReset: async (_, { email }, { driver }) => {
|
||||||
|
const code = uuid().substring(0, 6)
|
||||||
|
const [user] = await createPasswordReset({ driver, code, email })
|
||||||
|
if (CONFIG.SMTP_HOST && CONFIG.SMTP_PORT) {
|
||||||
|
const name = (user && user.name) || ''
|
||||||
|
const mailTemplate = user ? resetPasswordMail : wrongAccountMail
|
||||||
|
await transporter().sendMail(mailTemplate({ email, code, name }))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
resetPassword: async (_, { email, code, newPassword }, { driver }) => {
|
||||||
|
const session = driver.session()
|
||||||
|
const stillValid = new Date()
|
||||||
|
stillValid.setDate(stillValid.getDate() - 1)
|
||||||
|
const newHashedPassword = await bcrypt.hashSync(newPassword, 10)
|
||||||
|
const cypher = `
|
||||||
|
MATCH (pr:PasswordReset {code: $code})
|
||||||
|
MATCH (u:User {email: $email})-[:REQUESTED]->(pr)
|
||||||
|
WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
|
||||||
|
SET pr.usedAt = datetime()
|
||||||
|
SET u.password = $newHashedPassword
|
||||||
|
RETURN pr
|
||||||
|
`
|
||||||
|
let transactionRes = await session.run(cypher, { stillValid, email, code, newHashedPassword })
|
||||||
|
const [reset] = transactionRes.records.map(record => record.get('pr'))
|
||||||
|
const result = !!(reset && reset.properties.usedAt)
|
||||||
|
session.close()
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
180
backend/src/schema/resolvers/passwordReset.spec.js
Normal file
180
backend/src/schema/resolvers/passwordReset.spec.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
import Factory from '../../seed/factories'
|
||||||
|
import { host } from '../../jest/helpers'
|
||||||
|
import { getDriver } from '../../bootstrap/neo4j'
|
||||||
|
import { createPasswordReset } from './passwordReset'
|
||||||
|
|
||||||
|
const factory = Factory()
|
||||||
|
let client
|
||||||
|
const driver = getDriver()
|
||||||
|
|
||||||
|
const getAllPasswordResets = async () => {
|
||||||
|
const session = driver.session()
|
||||||
|
let transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r')
|
||||||
|
const resets = transactionRes.records.map(record => record.get('r'))
|
||||||
|
session.close()
|
||||||
|
return resets
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('passwordReset', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
client = new GraphQLClient(host)
|
||||||
|
await factory.create('User', {
|
||||||
|
email: 'user@example.org',
|
||||||
|
role: 'user',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await factory.cleanDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('requestPasswordReset', () => {
|
||||||
|
const mutation = `mutation($email: String!) { requestPasswordReset(email: $email) }`
|
||||||
|
|
||||||
|
describe('with invalid email', () => {
|
||||||
|
const variables = { email: 'non-existent@example.org' }
|
||||||
|
|
||||||
|
it('resolves anyways', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
requestPasswordReset: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates no node', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const resets = await getAllPasswordResets()
|
||||||
|
expect(resets).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with a valid email', () => {
|
||||||
|
const variables = { email: 'user@example.org' }
|
||||||
|
|
||||||
|
it('resolves', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
requestPasswordReset: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates node with label `PasswordReset`', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const resets = await getAllPasswordResets()
|
||||||
|
expect(resets).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a reset code', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const resets = await getAllPasswordResets()
|
||||||
|
const [reset] = resets
|
||||||
|
const { code } = reset.properties
|
||||||
|
expect(code).toHaveLength(6)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resetPassword', () => {
|
||||||
|
const setup = async (options = {}) => {
|
||||||
|
const { email = 'user@example.org', issuedAt = new Date(), code = 'abcdef' } = options
|
||||||
|
|
||||||
|
const session = driver.session()
|
||||||
|
await createPasswordReset({ driver, email, issuedAt, code })
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutation = `mutation($code: String!, $email: String!, $newPassword: String!) { resetPassword(code: $code, email: $email, newPassword: $newPassword) }`
|
||||||
|
let email = 'user@example.org'
|
||||||
|
let code = 'abcdef'
|
||||||
|
let newPassword = 'supersecret'
|
||||||
|
let variables
|
||||||
|
|
||||||
|
describe('invalid email', () => {
|
||||||
|
it('resolves to false', async () => {
|
||||||
|
await setup()
|
||||||
|
variables = { newPassword, email: 'non-existent@example.org', code }
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({ resetPassword: false })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('valid email', () => {
|
||||||
|
describe('but invalid code', () => {
|
||||||
|
it('resolves to false', async () => {
|
||||||
|
await setup()
|
||||||
|
variables = { newPassword, email, code: 'slkdjf' }
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
resetPassword: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('and valid code', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
variables = {
|
||||||
|
newPassword,
|
||||||
|
email: 'user@example.org',
|
||||||
|
code: 'abcdef',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('and code not expired', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await setup()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resolves to true', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
resetPassword: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates PasswordReset `usedAt` property', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const requests = await getAllPasswordResets()
|
||||||
|
const [request] = requests
|
||||||
|
const { usedAt } = request.properties
|
||||||
|
expect(usedAt).not.toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates password of the user', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const checkLoginMutation = `
|
||||||
|
mutation($email: String!, $password: String!) {
|
||||||
|
login(email: $email, password: $password)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expected = expect.objectContaining({ login: expect.any(String) })
|
||||||
|
await expect(
|
||||||
|
client.request(checkLoginMutation, {
|
||||||
|
email: 'user@example.org',
|
||||||
|
password: 'supersecret',
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('but expired code', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const issuedAt = new Date()
|
||||||
|
issuedAt.setDate(issuedAt.getDate() - 1)
|
||||||
|
await setup({ issuedAt })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resolves to false', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
resetPassword: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not update PasswordReset `usedAt` property', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const requests = await getAllPasswordResets()
|
||||||
|
const [request] = requests
|
||||||
|
const { usedAt } = request.properties
|
||||||
|
expect(usedAt).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
85
backend/src/schema/resolvers/passwordReset/emailTemplates.js
Normal file
85
backend/src/schema/resolvers/passwordReset/emailTemplates.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import CONFIG from '../../../config'
|
||||||
|
|
||||||
|
export const from = '"Human Connection" <info@human-connection.org>'
|
||||||
|
|
||||||
|
export const resetPasswordMail = options => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
code,
|
||||||
|
subject = 'Use this link to reset your password. The link is only valid for 24 hours.',
|
||||||
|
supportUrl = 'https://human-connection.org/en/contact/',
|
||||||
|
} = options
|
||||||
|
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
|
||||||
|
actionUrl.searchParams.set('code', code)
|
||||||
|
actionUrl.searchParams.set('email', email)
|
||||||
|
|
||||||
|
return {
|
||||||
|
to: email,
|
||||||
|
subject,
|
||||||
|
text: `
|
||||||
|
Hi ${name}!
|
||||||
|
|
||||||
|
You recently requested to reset your password for your Human Connection account.
|
||||||
|
Use the link below to reset it. This password reset is only valid for the next
|
||||||
|
24 hours.
|
||||||
|
|
||||||
|
${actionUrl}
|
||||||
|
|
||||||
|
If you did not request a password reset, please ignore this email or contact
|
||||||
|
support if you have questions:
|
||||||
|
|
||||||
|
${supportUrl}
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
The Human Connection Team
|
||||||
|
|
||||||
|
If you're having trouble with the link above, you can manually copy and
|
||||||
|
paste the following code into your browser window:
|
||||||
|
|
||||||
|
${code}
|
||||||
|
|
||||||
|
Human Connection gemeinnützige GmbH
|
||||||
|
Bahnhofstr. 11
|
||||||
|
73235 Weilheim / Teck
|
||||||
|
Deutschland
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wrongAccountMail = options => {
|
||||||
|
const {
|
||||||
|
email,
|
||||||
|
subject = `We received a request to reset your password with this email address (${email})`,
|
||||||
|
supportUrl = 'https://human-connection.org/en/contact/',
|
||||||
|
} = options
|
||||||
|
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
|
||||||
|
return {
|
||||||
|
to: email,
|
||||||
|
subject,
|
||||||
|
text: `
|
||||||
|
We received a request to reset the password to access Human Connection with your
|
||||||
|
email address, but we were unable to find an account associated with this
|
||||||
|
address.
|
||||||
|
|
||||||
|
If you use Human Connection and were expecting this email, consider trying to
|
||||||
|
request a password reset using the email address associated with your account.
|
||||||
|
Try a different email:
|
||||||
|
|
||||||
|
${actionUrl}
|
||||||
|
|
||||||
|
If you do not use Human Connection or did not request a password reset, please
|
||||||
|
ignore this email. Feel free to contact support if you have further questions:
|
||||||
|
|
||||||
|
${supportUrl}
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
The Human Connection Team
|
||||||
|
|
||||||
|
Human Connection gemeinnützige GmbH
|
||||||
|
Bahnhofstr. 11
|
||||||
|
73235 Weilheim / Teck
|
||||||
|
Deutschland
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -74,6 +74,22 @@ describe('CreatePost', () => {
|
|||||||
await expect(client.request(mutation)).resolves.toMatchObject(expected)
|
await expect(client.request(mutation)).resolves.toMatchObject(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('language', () => {
|
||||||
|
it('allows a user to set the language of the post', async () => {
|
||||||
|
const createPostWithLanguageMutation = `
|
||||||
|
mutation {
|
||||||
|
CreatePost(title: "I am a title", content: "Some content", language: "en") {
|
||||||
|
language
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expected = { CreatePost: { language: 'en' } }
|
||||||
|
await expect(client.request(createPostWithLanguageMutation)).resolves.toEqual(
|
||||||
|
expect.objectContaining(expected),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -315,6 +315,8 @@ describe('change password', () => {
|
|||||||
describe('do not expose private RSA key', () => {
|
describe('do not expose private RSA key', () => {
|
||||||
let headers
|
let headers
|
||||||
let client
|
let client
|
||||||
|
let authenticatedClient
|
||||||
|
|
||||||
const queryUserPuplicKey = gql`
|
const queryUserPuplicKey = gql`
|
||||||
query($queriedUserSlug: String) {
|
query($queriedUserSlug: String) {
|
||||||
User(slug: $queriedUserSlug) {
|
User(slug: $queriedUserSlug) {
|
||||||
@ -332,7 +334,7 @@ describe('do not expose private RSA key', () => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const actionGenUserWithKeys = async () => {
|
const generateUserWithKeys = async authenticatedClient => {
|
||||||
// Generate user with "privateKey" via 'CreateUser' mutation instead of using the factories "factory.create('User', {...})", see above.
|
// Generate user with "privateKey" via 'CreateUser' mutation instead of using the factories "factory.create('User', {...})", see above.
|
||||||
const variables = {
|
const variables = {
|
||||||
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
|
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
|
||||||
@ -341,7 +343,7 @@ describe('do not expose private RSA key', () => {
|
|||||||
name: 'Apfel Strudel',
|
name: 'Apfel Strudel',
|
||||||
email: 'apfel-strudel@test.org',
|
email: 'apfel-strudel@test.org',
|
||||||
}
|
}
|
||||||
await client.request(
|
await authenticatedClient.request(
|
||||||
gql`
|
gql`
|
||||||
mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String!) {
|
mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String!) {
|
||||||
CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) {
|
CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) {
|
||||||
@ -353,14 +355,23 @@ describe('do not expose private RSA key', () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// not authenticate
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const adminParams = {
|
||||||
|
role: 'admin',
|
||||||
|
email: 'admin@example.org',
|
||||||
|
password: '1234',
|
||||||
|
}
|
||||||
|
// create an admin user who has enough permissions to create other users
|
||||||
|
await factory.create('User', adminParams)
|
||||||
|
const headers = await login(adminParams)
|
||||||
|
authenticatedClient = new GraphQLClient(host, { headers })
|
||||||
|
// but also create an unauthenticated client to issue the `User` query
|
||||||
client = new GraphQLClient(host)
|
client = new GraphQLClient(host)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unauthenticated query of "publicKey" (does the RSA key pair get generated at all?)', () => {
|
describe('unauthenticated query of "publicKey" (does the RSA key pair get generated at all?)', () => {
|
||||||
it('returns publicKey', async () => {
|
it('returns publicKey', async () => {
|
||||||
await actionGenUserWithKeys()
|
await generateUserWithKeys(authenticatedClient)
|
||||||
await expect(
|
await expect(
|
||||||
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
|
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
@ -378,7 +389,7 @@ describe('do not expose private RSA key', () => {
|
|||||||
|
|
||||||
describe('unauthenticated query of "privateKey"', () => {
|
describe('unauthenticated query of "privateKey"', () => {
|
||||||
it('throws "Not Authorised!"', async () => {
|
it('throws "Not Authorised!"', async () => {
|
||||||
await actionGenUserWithKeys()
|
await generateUserWithKeys(authenticatedClient)
|
||||||
await expect(
|
await expect(
|
||||||
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
|
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||||
).rejects.toThrow('Not Authorised')
|
).rejects.toThrow('Not Authorised')
|
||||||
@ -393,7 +404,7 @@ describe('do not expose private RSA key', () => {
|
|||||||
|
|
||||||
describe('authenticated query of "publicKey"', () => {
|
describe('authenticated query of "publicKey"', () => {
|
||||||
it('returns publicKey', async () => {
|
it('returns publicKey', async () => {
|
||||||
await actionGenUserWithKeys()
|
await generateUserWithKeys(authenticatedClient)
|
||||||
await expect(
|
await expect(
|
||||||
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
|
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
@ -411,7 +422,7 @@ describe('do not expose private RSA key', () => {
|
|||||||
|
|
||||||
describe('authenticated query of "privateKey"', () => {
|
describe('authenticated query of "privateKey"', () => {
|
||||||
it('throws "Not Authorised!"', async () => {
|
it('throws "Not Authorised!"', async () => {
|
||||||
await actionGenUserWithKeys()
|
await generateUserWithKeys(authenticatedClient)
|
||||||
await expect(
|
await expect(
|
||||||
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
|
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||||
).rejects.toThrow('Not Authorised')
|
).rejects.toThrow('Not Authorised')
|
||||||
|
|||||||
@ -11,5 +11,27 @@ export default {
|
|||||||
params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
|
params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
|
||||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
},
|
},
|
||||||
|
DeleteUser: async (object, params, context, resolveInfo) => {
|
||||||
|
const { resource } = params
|
||||||
|
const session = context.driver.session()
|
||||||
|
|
||||||
|
if (resource && resource.length) {
|
||||||
|
await Promise.all(
|
||||||
|
resource.map(async node => {
|
||||||
|
await session.run(
|
||||||
|
`
|
||||||
|
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
||||||
|
SET resource.deleted = true
|
||||||
|
RETURN author`,
|
||||||
|
{
|
||||||
|
userId: context.user.id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { GraphQLClient } from 'graphql-request'
|
import { GraphQLClient } from 'graphql-request'
|
||||||
import { host } from '../../jest/helpers'
|
import { login, host } from '../../jest/helpers'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
let client
|
let client
|
||||||
@ -18,14 +19,36 @@ describe('users', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
client = new GraphQLClient(host)
|
describe('given valid password and email', () => {
|
||||||
|
|
||||||
it('with password and email', async () => {
|
|
||||||
const variables = {
|
const variables = {
|
||||||
name: 'John Doe',
|
name: 'John Doe',
|
||||||
password: '123',
|
password: '123',
|
||||||
email: '123@123.de',
|
email: '123@123.de',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
client = new GraphQLClient(host)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is not allowed to create users', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated admin', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const adminParams = {
|
||||||
|
role: 'admin',
|
||||||
|
email: 'admin@example.org',
|
||||||
|
password: '1234',
|
||||||
|
}
|
||||||
|
await factory.create('User', adminParams)
|
||||||
|
const headers = await login(adminParams)
|
||||||
|
client = new GraphQLClient(host, { headers })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is allowed to create new users', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
CreateUser: {
|
CreateUser: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
@ -34,11 +57,20 @@ describe('users', () => {
|
|||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('UpdateUser', () => {
|
describe('UpdateUser', () => {
|
||||||
beforeEach(async () => {
|
const userParams = {
|
||||||
await factory.create('User', { id: 'u47', name: 'John Doe' })
|
email: 'user@example.org',
|
||||||
})
|
password: '1234',
|
||||||
|
id: 'u47',
|
||||||
|
name: 'John Doe',
|
||||||
|
}
|
||||||
|
const variables = {
|
||||||
|
id: 'u47',
|
||||||
|
name: 'John Doughnut',
|
||||||
|
}
|
||||||
|
|
||||||
const mutation = `
|
const mutation = `
|
||||||
mutation($id: ID!, $name: String) {
|
mutation($id: ID!, $name: String) {
|
||||||
@ -48,17 +80,40 @@ describe('users', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
client = new GraphQLClient(host)
|
|
||||||
|
|
||||||
it('name within specifications', async () => {
|
beforeEach(async () => {
|
||||||
const variables = {
|
await factory.create('User', userParams)
|
||||||
id: 'u47',
|
})
|
||||||
|
|
||||||
|
describe('as another user', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const someoneElseParams = {
|
||||||
|
email: 'someoneElse@example.org',
|
||||||
|
password: '1234',
|
||||||
name: 'James Doe',
|
name: 'James Doe',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await factory.create('User', someoneElseParams)
|
||||||
|
const headers = await login(someoneElseParams)
|
||||||
|
client = new GraphQLClient(host, { headers })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is not allowed to change other user accounts', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as the same user', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const headers = await login(userParams)
|
||||||
|
client = new GraphQLClient(host, { headers })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('name within specifications', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
UpdateUser: {
|
UpdateUser: {
|
||||||
id: 'u47',
|
id: 'u47',
|
||||||
name: 'James Doe',
|
name: 'John Doughnut',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||||
@ -82,4 +137,142 @@ describe('users', () => {
|
|||||||
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
|
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('DeleteUser', () => {
|
||||||
|
let deleteUserVariables
|
||||||
|
let asAuthor
|
||||||
|
const deleteUserMutation = gql`
|
||||||
|
mutation($id: ID!, $resource: [String]) {
|
||||||
|
DeleteUser(id: $id, resource: $resource) {
|
||||||
|
id
|
||||||
|
contributions {
|
||||||
|
id
|
||||||
|
deleted
|
||||||
|
}
|
||||||
|
comments {
|
||||||
|
id
|
||||||
|
deleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
beforeEach(async () => {
|
||||||
|
asAuthor = await factory.create('User', {
|
||||||
|
email: 'test@example.org',
|
||||||
|
password: '1234',
|
||||||
|
id: 'u343',
|
||||||
|
})
|
||||||
|
await factory.create('User', {
|
||||||
|
email: 'friendsAccount@example.org',
|
||||||
|
password: '1234',
|
||||||
|
id: 'u565',
|
||||||
|
})
|
||||||
|
deleteUserVariables = { id: 'u343', resource: [] }
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
client = new GraphQLClient(host)
|
||||||
|
await expect(client.request(deleteUserMutation, deleteUserVariables)).rejects.toThrow(
|
||||||
|
'Not Authorised',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
let headers
|
||||||
|
beforeEach(async () => {
|
||||||
|
headers = await login({
|
||||||
|
email: 'test@example.org',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
client = new GraphQLClient(host, { headers })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("attempting to delete another user's account", () => {
|
||||||
|
it('throws an authorization error', async () => {
|
||||||
|
deleteUserVariables = { id: 'u565' }
|
||||||
|
await expect(client.request(deleteUserMutation, deleteUserVariables)).rejects.toThrow(
|
||||||
|
'Not Authorised',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('attempting to delete my own account', () => {
|
||||||
|
let expectedResponse
|
||||||
|
beforeEach(async () => {
|
||||||
|
await asAuthor.authenticateAs({
|
||||||
|
email: 'test@example.org',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
await asAuthor.create('Post', {
|
||||||
|
id: 'p139',
|
||||||
|
content: 'Post by user u343',
|
||||||
|
})
|
||||||
|
await asAuthor.create('Comment', {
|
||||||
|
id: 'c155',
|
||||||
|
postId: 'p139',
|
||||||
|
content: 'Comment by user u343',
|
||||||
|
})
|
||||||
|
expectedResponse = {
|
||||||
|
DeleteUser: {
|
||||||
|
id: 'u343',
|
||||||
|
contributions: [{ id: 'p139', deleted: false }],
|
||||||
|
comments: [{ id: 'c155', deleted: false }],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it("deletes my account, but doesn't delete posts or comments by default", async () => {
|
||||||
|
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||||
|
expectedResponse,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("deletes a user's", () => {
|
||||||
|
it('posts on request', async () => {
|
||||||
|
deleteUserVariables = { id: 'u343', resource: ['Post'] }
|
||||||
|
expectedResponse = {
|
||||||
|
DeleteUser: {
|
||||||
|
id: 'u343',
|
||||||
|
contributions: [{ id: 'p139', deleted: true }],
|
||||||
|
comments: [{ id: 'c155', deleted: false }],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||||
|
expectedResponse,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('comments on request', async () => {
|
||||||
|
deleteUserVariables = { id: 'u343', resource: ['Comment'] }
|
||||||
|
expectedResponse = {
|
||||||
|
DeleteUser: {
|
||||||
|
id: 'u343',
|
||||||
|
contributions: [{ id: 'p139', deleted: false }],
|
||||||
|
comments: [{ id: 'c155', deleted: true }],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||||
|
expectedResponse,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('posts and comments on request', async () => {
|
||||||
|
deleteUserVariables = { id: 'u343', resource: ['Post', 'Comment'] }
|
||||||
|
expectedResponse = {
|
||||||
|
DeleteUser: {
|
||||||
|
id: 'u343',
|
||||||
|
contributions: [{ id: 'p139', deleted: true }],
|
||||||
|
comments: [{ id: 'c155', deleted: true }],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||||
|
expectedResponse,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,7 +4,8 @@ type Query {
|
|||||||
currentUser: User
|
currentUser: User
|
||||||
# Get the latest Network Statistics
|
# Get the latest Network Statistics
|
||||||
statistics: Statistics!
|
statistics: Statistics!
|
||||||
findPosts(filter: String!, limit: Int = 10): [Post]! @cypher(
|
findPosts(filter: String!, limit: Int = 10): [Post]!
|
||||||
|
@cypher(
|
||||||
statement: """
|
statement: """
|
||||||
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
|
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
|
||||||
YIELD node as post, score
|
YIELD node as post, score
|
||||||
@ -23,7 +24,9 @@ type Mutation {
|
|||||||
# Get a JWT Token for the given Email and password
|
# Get a JWT Token for the given Email and password
|
||||||
login(email: String!, password: String!): String!
|
login(email: String!, password: String!): String!
|
||||||
signup(email: String!, password: String!): Boolean!
|
signup(email: String!, password: String!): Boolean!
|
||||||
changePassword(oldPassword:String!, newPassword: String!): String!
|
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||||
|
requestPasswordReset(email: String!): Boolean!
|
||||||
|
resetPassword(email: String!, code: String!, newPassword: String!): Boolean!
|
||||||
report(id: ID!, description: String): Report
|
report(id: ID!, description: String): Report
|
||||||
disable(id: ID!): ID
|
disable(id: ID!): ID
|
||||||
enable(id: ID!): ID
|
enable(id: ID!): ID
|
||||||
@ -37,6 +40,7 @@ type Mutation {
|
|||||||
follow(id: ID!, type: FollowTypeEnum): Boolean!
|
follow(id: ID!, type: FollowTypeEnum): Boolean!
|
||||||
# Unfollow the given Type and ID
|
# Unfollow the given Type and ID
|
||||||
unfollow(id: ID!, type: FollowTypeEnum): Boolean!
|
unfollow(id: ID!, type: FollowTypeEnum): Boolean!
|
||||||
|
DeleteUser(id: ID!, resource: [String]): User
|
||||||
}
|
}
|
||||||
|
|
||||||
type Statistics {
|
type Statistics {
|
||||||
@ -53,7 +57,7 @@ type Statistics {
|
|||||||
|
|
||||||
type Notification {
|
type Notification {
|
||||||
id: ID!
|
id: ID!
|
||||||
read: Boolean,
|
read: Boolean
|
||||||
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
||||||
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
||||||
createdAt: String
|
createdAt: String
|
||||||
@ -80,7 +84,8 @@ type Report {
|
|||||||
id: ID!
|
id: ID!
|
||||||
submitter: User @relation(name: "REPORTED", direction: "IN")
|
submitter: User @relation(name: "REPORTED", direction: "IN")
|
||||||
description: String
|
description: String
|
||||||
type: String! @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
|
type: String!
|
||||||
|
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
|
||||||
createdAt: String
|
createdAt: String
|
||||||
comment: Comment @relation(name: "REPORTED", direction: "OUT")
|
comment: Comment @relation(name: "REPORTED", direction: "OUT")
|
||||||
post: Post @relation(name: "REPORTED", direction: "OUT")
|
post: Post @relation(name: "REPORTED", direction: "OUT")
|
||||||
@ -131,4 +136,3 @@ type SocialMedia {
|
|||||||
url: String
|
url: String
|
||||||
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
|
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,9 @@ type Post {
|
|||||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||||
createdAt: String
|
createdAt: String
|
||||||
updatedAt: String
|
updatedAt: String
|
||||||
|
language: String
|
||||||
relatedContributions: [Post]! @cypher(
|
relatedContributions: [Post]!
|
||||||
|
@cypher(
|
||||||
statement: """
|
statement: """
|
||||||
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||||
RETURN DISTINCT post
|
RETURN DISTINCT post
|
||||||
@ -28,13 +29,20 @@ type Post {
|
|||||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||||
|
|
||||||
comments: [Comment]! @relation(name: "COMMENTS", direction: "IN")
|
comments: [Comment]! @relation(name: "COMMENTS", direction: "IN")
|
||||||
commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
commentsCount: Int!
|
||||||
|
@cypher(
|
||||||
|
statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)"
|
||||||
|
)
|
||||||
|
|
||||||
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
|
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
|
||||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
shoutedCount: Int!
|
||||||
|
@cypher(
|
||||||
|
statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)"
|
||||||
|
)
|
||||||
|
|
||||||
# Has the currently logged in user shouted that post?
|
# Has the currently logged in user shouted that post?
|
||||||
shoutedByCurrentUser: Boolean! @cypher(
|
shoutedByCurrentUser: Boolean!
|
||||||
|
@cypher(
|
||||||
statement: """
|
statement: """
|
||||||
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
|
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
|
||||||
RETURN COUNT(u) >= 1
|
RETURN COUNT(u) >= 1
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export default function(params) {
|
|||||||
faker.lorem.sentence(),
|
faker.lorem.sentence(),
|
||||||
faker.lorem.sentence(),
|
faker.lorem.sentence(),
|
||||||
].join('. '),
|
].join('. '),
|
||||||
image = faker.image.image(),
|
image = faker.image.unsplash.imageUrl(),
|
||||||
visibility = 'public',
|
visibility = 'public',
|
||||||
deleted = false,
|
deleted = false,
|
||||||
} = params
|
} = params
|
||||||
|
|||||||
@ -214,21 +214,21 @@ import Factory from './factories'
|
|||||||
'Hey <a class="mention" href="/profile/u3">@jenny-rostock</a>, here is another notification for you!'
|
'Hey <a class="mention" href="/profile/u3">@jenny-rostock</a>, here is another notification for you!'
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
asAdmin.create('Post', { id: 'p0' }),
|
asAdmin.create('Post', { id: 'p0', image: faker.image.unsplash.food() }),
|
||||||
asModerator.create('Post', { id: 'p1' }),
|
asModerator.create('Post', { id: 'p1', image: faker.image.unsplash.technology() }),
|
||||||
asUser.create('Post', { id: 'p2' }),
|
asUser.create('Post', { id: 'p2' }),
|
||||||
asTick.create('Post', { id: 'p3' }),
|
asTick.create('Post', { id: 'p3' }),
|
||||||
asTrick.create('Post', { id: 'p4' }),
|
asTrick.create('Post', { id: 'p4' }),
|
||||||
asTrack.create('Post', { id: 'p5' }),
|
asTrack.create('Post', { id: 'p5' }),
|
||||||
asAdmin.create('Post', { id: 'p6' }),
|
asAdmin.create('Post', { id: 'p6', image: faker.image.unsplash.buildings() }),
|
||||||
asModerator.create('Post', { id: 'p7', content: `${mention1} ${faker.lorem.paragraph()}` }),
|
asModerator.create('Post', { id: 'p7', content: `${mention1} ${faker.lorem.paragraph()}` }),
|
||||||
asUser.create('Post', { id: 'p8' }),
|
asUser.create('Post', { id: 'p8', image: faker.image.unsplash.nature() }),
|
||||||
asTick.create('Post', { id: 'p9' }),
|
asTick.create('Post', { id: 'p9' }),
|
||||||
asTrick.create('Post', { id: 'p10' }),
|
asTrick.create('Post', { id: 'p10' }),
|
||||||
asTrack.create('Post', { id: 'p11' }),
|
asTrack.create('Post', { id: 'p11', image: faker.image.unsplash.people() }),
|
||||||
asAdmin.create('Post', { id: 'p12', content: `${mention2} ${faker.lorem.paragraph()}` }),
|
asAdmin.create('Post', { id: 'p12', content: `${mention2} ${faker.lorem.paragraph()}` }),
|
||||||
asModerator.create('Post', { id: 'p13' }),
|
asModerator.create('Post', { id: 'p13' }),
|
||||||
asUser.create('Post', { id: 'p14' }),
|
asUser.create('Post', { id: 'p14', image: faker.image.unsplash.objects() }),
|
||||||
asTick.create('Post', { id: 'p15' }),
|
asTick.create('Post', { id: 'p15' }),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ Feature: Follow a user
|
|||||||
| stuart-little |
|
| stuart-little |
|
||||||
| tero-vota |
|
| tero-vota |
|
||||||
|
|
||||||
|
@wip
|
||||||
Scenario: Send a follow to a user inbox and make sure it's added to the right followers collection
|
Scenario: Send a follow to a user inbox and make sure it's added to the right followers collection
|
||||||
When I send a POST request with the following activity to "/activitypub/users/tero-vota/inbox":
|
When I send a POST request with the following activity to "/activitypub/users/tero-vota/inbox":
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -27,6 +27,7 @@ Feature: Like an object like an article or note
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@wip
|
||||||
Scenario: Send a like of a person to an users inbox and make sure it's added to the likes collection
|
Scenario: Send a like of a person to an users inbox and make sure it's added to the likes collection
|
||||||
When I send a POST request with the following activity to "/activitypub/users/karl-heinz/inbox":
|
When I send a POST request with the following activity to "/activitypub/users/karl-heinz/inbox":
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1020,19 +1020,10 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/range-parser" "*"
|
"@types/range-parser" "*"
|
||||||
|
|
||||||
"@types/express@*", "@types/express@^4.11.1":
|
"@types/express@*", "@types/express@4.17.0", "@types/express@^4.11.1":
|
||||||
version "4.16.0"
|
version "4.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.0.tgz#6d8bc42ccaa6f35cf29a2b7c3333cb47b5a32a19"
|
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287"
|
||||||
integrity sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==
|
integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==
|
||||||
dependencies:
|
|
||||||
"@types/body-parser" "*"
|
|
||||||
"@types/express-serve-static-core" "*"
|
|
||||||
"@types/serve-static" "*"
|
|
||||||
|
|
||||||
"@types/express@4.16.1":
|
|
||||||
version "4.16.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.1.tgz#d756bd1a85c34d87eaf44c888bad27ba8a4b7cf0"
|
|
||||||
integrity sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/body-parser" "*"
|
"@types/body-parser" "*"
|
||||||
"@types/express-serve-static-core" "*"
|
"@types/express-serve-static-core" "*"
|
||||||
@ -1119,10 +1110,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
|
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
|
||||||
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
|
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
|
||||||
|
|
||||||
"@types/yup@0.26.16":
|
"@types/yup@0.26.17":
|
||||||
version "0.26.16"
|
version "0.26.17"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.16.tgz#75c428236207c48d9f8062dd1495cda8c5485a15"
|
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.17.tgz#5cb7cfc211d8e985b21d88289542591c92cad9dc"
|
||||||
integrity sha512-E2RNc7DSeQ+2EIJ1H3+yFjYu6YiyQBUJ7yNpIxomrYJ3oFizLZ5yDS3T1JTUNBC2OCRkgnhLS0smob5UuCHfNA==
|
integrity sha512-MN7VHlPsZQ2MTBxLE2Gl+Qfg2WyKsoz+vIr8xN0OSZ4AvJDrrKBlxc8b59UXCCIG9tPn9XhxTXh3j/htHbzC2Q==
|
||||||
|
|
||||||
"@types/zen-observable@^0.5.3":
|
"@types/zen-observable@^0.5.3":
|
||||||
version "0.5.4"
|
version "0.5.4"
|
||||||
@ -1226,10 +1217,10 @@ activitystreams-context@>=3.0.0, activitystreams-context@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/activitystreams-context/-/activitystreams-context-3.1.0.tgz#28334e129f17cfb937e8c702c52c1bcb1d2830c7"
|
resolved "https://registry.yarnpkg.com/activitystreams-context/-/activitystreams-context-3.1.0.tgz#28334e129f17cfb937e8c702c52c1bcb1d2830c7"
|
||||||
integrity sha512-KBQ+igwf1tezMXGVw5MvRSEm0gp97JI1hTZ45I6MEkWv25lEgNoA9L6wqfaOiCX8wnMRWw9pwRsPZKypdtxAtg==
|
integrity sha512-KBQ+igwf1tezMXGVw5MvRSEm0gp97JI1hTZ45I6MEkWv25lEgNoA9L6wqfaOiCX8wnMRWw9pwRsPZKypdtxAtg==
|
||||||
|
|
||||||
ajv@^6.5.5, ajv@^6.9.1:
|
ajv@^6.10.0, ajv@^6.5.5, ajv@^6.9.1:
|
||||||
version "6.9.2"
|
version "6.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.2.tgz#4927adb83e7f48e5a32b45729744c71ec39c9c7b"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
|
||||||
integrity sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==
|
integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal "^2.0.1"
|
fast-deep-equal "^2.0.1"
|
||||||
fast-json-stable-stringify "^2.0.0"
|
fast-json-stable-stringify "^2.0.0"
|
||||||
@ -1288,13 +1279,13 @@ anymatch@^2.0.0:
|
|||||||
micromatch "^3.1.4"
|
micromatch "^3.1.4"
|
||||||
normalize-path "^2.1.1"
|
normalize-path "^2.1.1"
|
||||||
|
|
||||||
apollo-cache-control@0.7.2:
|
apollo-cache-control@0.7.4:
|
||||||
version "0.7.2"
|
version "0.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.7.2.tgz#b8852422d973c582493e85c776abc9c660090162"
|
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.7.4.tgz#0cb5c7be0e0dd0c44b1257144cd7f9f2a3c374e6"
|
||||||
integrity sha512-7prjFN8H9lRE0npqGG8kM3XICvNCcgQt6eCy8kkcPOIZwM+F8m8ShjEfNF9UWW32i+poOk3G67HegPRyjCc6/Q==
|
integrity sha512-TVACHwcEF4wfHo5H9FLnoNjo0SLDo2jPW+bXs9aw0Y4Z2UisskSAPnIYOqUPnU8SoeNvs7zWgbLizq11SRTJtg==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-server-env "2.4.0"
|
apollo-server-env "2.4.0"
|
||||||
graphql-extensions "0.7.2"
|
graphql-extensions "0.7.4"
|
||||||
|
|
||||||
apollo-cache-control@^0.1.0:
|
apollo-cache-control@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
@ -1322,10 +1313,10 @@ apollo-cache@1.3.2, apollo-cache@^1.3.2:
|
|||||||
apollo-utilities "^1.3.2"
|
apollo-utilities "^1.3.2"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
apollo-client@~2.6.2:
|
apollo-client@~2.6.3:
|
||||||
version "2.6.2"
|
version "2.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.2.tgz#03b6af651e09b6e413e486ddc87464c85bd6e514"
|
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.3.tgz#9bb2d42fb59f1572e51417f341c5f743798d22db"
|
||||||
integrity sha512-oks1MaT5x7gHcPeC8vPC1UzzsKaEIC0tye+jg72eMDt5OKc7BobStTeS/o2Ib3e0ii40nKxGBnMdl/Xa/p56Yg==
|
integrity sha512-DS8pmF5CGiiJ658dG+mDn8pmCMMQIljKJSTeMNHnFuDLV0uAPZoeaAwVFiAmB408Ujqt92oIZ/8yJJAwSIhd4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/zen-observable" "^0.8.0"
|
"@types/zen-observable" "^0.8.0"
|
||||||
apollo-cache "1.3.2"
|
apollo-cache "1.3.2"
|
||||||
@ -1351,17 +1342,17 @@ apollo-engine-reporting-protobuf@0.3.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
protobufjs "^6.8.6"
|
protobufjs "^6.8.6"
|
||||||
|
|
||||||
apollo-engine-reporting@1.3.0:
|
apollo-engine-reporting@1.3.4:
|
||||||
version "1.3.0"
|
version "1.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.0.tgz#50151811a0f5e70f4a73e7092a61fec422d8e722"
|
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.4.tgz#65e12f94221d80b3b1740c26e82ce9bb6bdfb7ee"
|
||||||
integrity sha512-xP+Z+wdQH4ee7xfuP3WsJcIe30AH68gpp2lQm2+rnW5JfjIqD5YehSoO2Svi2jK3CSv8Y561i3QMW9i34P7hEQ==
|
integrity sha512-DJdYghyUBzT0/LcPLwuQNXDCw06r1RfxkVfNTGKoTv6a+leVvjhDJmXvc+jSuBPwaNsc+RYRnfyQ2qUn9fmfyA==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-engine-reporting-protobuf "0.3.1"
|
apollo-engine-reporting-protobuf "0.3.1"
|
||||||
apollo-graphql "^0.3.0"
|
apollo-graphql "^0.3.2"
|
||||||
apollo-server-core "2.6.2"
|
apollo-server-core "2.6.6"
|
||||||
apollo-server-env "2.4.0"
|
apollo-server-env "2.4.0"
|
||||||
async-retry "^1.2.1"
|
async-retry "^1.2.1"
|
||||||
graphql-extensions "0.7.2"
|
graphql-extensions "0.7.5"
|
||||||
|
|
||||||
apollo-env@0.5.1:
|
apollo-env@0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
@ -1380,49 +1371,49 @@ apollo-errors@^1.9.0:
|
|||||||
assert "^1.4.1"
|
assert "^1.4.1"
|
||||||
extendable-error "^0.1.5"
|
extendable-error "^0.1.5"
|
||||||
|
|
||||||
apollo-graphql@^0.3.0:
|
apollo-graphql@^0.3.2:
|
||||||
version "0.3.1"
|
version "0.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.1.tgz#d13b80cc0cae3fe7066b81b80914c6f983fac8d7"
|
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.2.tgz#8881a87f1d5fcf80837b34dba90737e664eabe9a"
|
||||||
integrity sha512-tbhtzNAAhNI34v4XY9OlZGnH7U0sX4BP1cJrUfSiNzQnZRg1UbQYZ06riHSOHpi5RSndFcA9LDM5C1ZKKOUeBg==
|
integrity sha512-YbzYGR14GV0023m//EU66vOzZ3i7c04V/SF8Qk+60vf1sOWyKgO6mxZJ4BKhw10qWUayirhSDxq3frYE+qSG0A==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-env "0.5.1"
|
apollo-env "0.5.1"
|
||||||
lodash.sortby "^4.7.0"
|
lodash.sortby "^4.7.0"
|
||||||
|
|
||||||
apollo-link-context@~1.0.14:
|
apollo-link-context@~1.0.18:
|
||||||
version "1.0.17"
|
version "1.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.17.tgz#439272cfb43ec1891506dd175ed907845b7de36c"
|
resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.18.tgz#9e700e3314da8ded50057fee0a18af2bfcedbfc3"
|
||||||
integrity sha512-W5UUfHcrrlP5uqJs5X1zbf84AMXhPZGAqX/7AQDgR6wY/7//sMGfJvm36KDkpIeSOElztGtM9z6zdPN1NbT41Q==
|
integrity sha512-aG5cbUp1zqOHHQjAJXG7n/izeMQ6LApd/whEF5z6qZp5ATvcyfSNkCfy3KRJMMZZ3iNfVTs6jF+IUA8Zvf+zeg==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-link "^1.2.11"
|
apollo-link "^1.2.12"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
apollo-link-http-common@^0.2.13:
|
apollo-link-http-common@^0.2.14:
|
||||||
version "0.2.13"
|
version "0.2.14"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
|
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.14.tgz#d3a195c12e00f4e311c417f121181dcc31f7d0c8"
|
||||||
integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
|
integrity sha512-v6mRU1oN6XuX8beVIRB6OpF4q1ULhSnmy7ScnHnuo1qV6GaFmDcbdvXqxIkAV1Q8SQCo2lsv4HeqJOWhFfApOg==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-link "^1.2.11"
|
apollo-link "^1.2.12"
|
||||||
ts-invariant "^0.3.2"
|
ts-invariant "^0.4.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
apollo-link-http@~1.5.14:
|
apollo-link-http@~1.5.15:
|
||||||
version "1.5.14"
|
version "1.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.14.tgz#ed6292248d1819ccd16523e346d35203a1b31109"
|
resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.15.tgz#106ab23bb8997bd55965d05855736d33119652cf"
|
||||||
integrity sha512-XEoPXmGpxFG3wioovgAlPXIarWaW4oWzt8YzjTYZ87R4R7d1A3wKR/KcvkdMV1m5G7YSAHcNkDLe/8hF2nH6cg==
|
integrity sha512-epZFhCKDjD7+oNTVK3P39pqWGn4LEhShAoA1Q9e2tDrBjItNfviiE33RmcLcCURDYyW5JA6SMgdODNI4Is8tvQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-link "^1.2.11"
|
apollo-link "^1.2.12"
|
||||||
apollo-link-http-common "^0.2.13"
|
apollo-link-http-common "^0.2.14"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
apollo-link@^1.0.0, apollo-link@^1.2.11, apollo-link@^1.2.3:
|
apollo-link@^1.0.0, apollo-link@^1.2.12, apollo-link@^1.2.3:
|
||||||
version "1.2.11"
|
version "1.2.12"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d"
|
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.12.tgz#014b514fba95f1945c38ad4c216f31bcfee68429"
|
||||||
integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==
|
integrity sha512-fsgIAXPKThyMVEMWQsUN22AoQI+J/pVXcjRGAShtk97h7D8O+SPskFinCGEkxPeQpE83uKaqafB2IyWdjN+J3Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-utilities "^1.2.1"
|
apollo-utilities "^1.3.0"
|
||||||
ts-invariant "^0.3.2"
|
ts-invariant "^0.4.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
zen-observable-ts "^0.8.18"
|
zen-observable-ts "^0.8.19"
|
||||||
|
|
||||||
apollo-server-caching@0.4.0:
|
apollo-server-caching@0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
@ -1431,24 +1422,24 @@ apollo-server-caching@0.4.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^5.0.0"
|
lru-cache "^5.0.0"
|
||||||
|
|
||||||
apollo-server-core@2.6.2:
|
apollo-server-core@2.6.6:
|
||||||
version "2.6.2"
|
version "2.6.6"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.2.tgz#a792b50d4df9e26ec03759a31fbcbce38361b218"
|
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.6.tgz#55fea7980a943948c49dea20d81b9bbfc0e04f87"
|
||||||
integrity sha512-AbAnfoQ26NPsNIyBa/BVKBtA/wRsNL/E6eEem1VIhzitfgO25bVXFbEZDLxbgz6wvJ+veyRFpse7Qi1bvRpxOw==
|
integrity sha512-PFSjJbqkV1eetfFJxu11gzklQYC8BrF0RZfvC1d1mhvtxAOKl25uhPHxltN0Omyjp7LW4YeoC6zwl9rLWuhZFQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@apollographql/apollo-tools" "^0.3.6"
|
"@apollographql/apollo-tools" "^0.3.6"
|
||||||
"@apollographql/graphql-playground-html" "1.6.20"
|
"@apollographql/graphql-playground-html" "1.6.20"
|
||||||
"@types/ws" "^6.0.0"
|
"@types/ws" "^6.0.0"
|
||||||
apollo-cache-control "0.7.2"
|
apollo-cache-control "0.7.4"
|
||||||
apollo-datasource "0.5.0"
|
apollo-datasource "0.5.0"
|
||||||
apollo-engine-reporting "1.3.0"
|
apollo-engine-reporting "1.3.4"
|
||||||
apollo-server-caching "0.4.0"
|
apollo-server-caching "0.4.0"
|
||||||
apollo-server-env "2.4.0"
|
apollo-server-env "2.4.0"
|
||||||
apollo-server-errors "2.3.0"
|
apollo-server-errors "2.3.0"
|
||||||
apollo-server-plugin-base "0.5.2"
|
apollo-server-plugin-base "0.5.5"
|
||||||
apollo-tracing "0.7.2"
|
apollo-tracing "0.7.3"
|
||||||
fast-json-stable-stringify "^2.0.0"
|
fast-json-stable-stringify "^2.0.0"
|
||||||
graphql-extensions "0.7.2"
|
graphql-extensions "0.7.5"
|
||||||
graphql-subscriptions "^1.0.0"
|
graphql-subscriptions "^1.0.0"
|
||||||
graphql-tag "^2.9.2"
|
graphql-tag "^2.9.2"
|
||||||
graphql-tools "^4.0.0"
|
graphql-tools "^4.0.0"
|
||||||
@ -1479,18 +1470,18 @@ apollo-server-errors@2.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.0.tgz#700622b66a16dffcad3b017e4796749814edc061"
|
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.0.tgz#700622b66a16dffcad3b017e4796749814edc061"
|
||||||
integrity sha512-rUvzwMo2ZQgzzPh2kcJyfbRSfVKRMhfIlhY7BzUfM4x6ZT0aijlgsf714Ll3Mbf5Fxii32kD0A/DmKsTecpccw==
|
integrity sha512-rUvzwMo2ZQgzzPh2kcJyfbRSfVKRMhfIlhY7BzUfM4x6ZT0aijlgsf714Ll3Mbf5Fxii32kD0A/DmKsTecpccw==
|
||||||
|
|
||||||
apollo-server-express@2.6.2:
|
apollo-server-express@2.6.6:
|
||||||
version "2.6.2"
|
version "2.6.6"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.2.tgz#526297c01a7a32fe9215566f9fd7ff92e82f1fa0"
|
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.6.tgz#ec2b955354d7dd4d12fe01ea7e983d302071d5b9"
|
||||||
integrity sha512-nbL3noJ5KxKGg+hT8UsAA7++oHWq/KNSevfdCluWTfUNqH1vYRTvAnARx/6JM06S9zcPTfOLcqwHnDnY9zYFxA==
|
integrity sha512-bY/xrr9lZH+hsjchiQuSXpW3ivXfL1h81M5VE9Ppus1PVwwEIar/irBN+PFp97WxERZPDjVZzrRKa+lRHjtJsA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@apollographql/graphql-playground-html" "1.6.20"
|
"@apollographql/graphql-playground-html" "1.6.20"
|
||||||
"@types/accepts" "^1.3.5"
|
"@types/accepts" "^1.3.5"
|
||||||
"@types/body-parser" "1.17.0"
|
"@types/body-parser" "1.17.0"
|
||||||
"@types/cors" "^2.8.4"
|
"@types/cors" "^2.8.4"
|
||||||
"@types/express" "4.16.1"
|
"@types/express" "4.17.0"
|
||||||
accepts "^1.3.5"
|
accepts "^1.3.5"
|
||||||
apollo-server-core "2.6.2"
|
apollo-server-core "2.6.6"
|
||||||
body-parser "^1.18.3"
|
body-parser "^1.18.3"
|
||||||
cors "^2.8.4"
|
cors "^2.8.4"
|
||||||
graphql-subscriptions "^1.0.0"
|
graphql-subscriptions "^1.0.0"
|
||||||
@ -1518,36 +1509,36 @@ apollo-server-module-graphiql@^1.3.4, apollo-server-module-graphiql@^1.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.4.0.tgz#c559efa285578820709f1769bb85d3b3eed3d8ec"
|
resolved "https://registry.yarnpkg.com/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.4.0.tgz#c559efa285578820709f1769bb85d3b3eed3d8ec"
|
||||||
integrity sha512-GmkOcb5he2x5gat+TuiTvabnBf1m4jzdecal3XbXBh/Jg+kx4hcvO3TTDFQ9CuTprtzdcVyA11iqG7iOMOt7vA==
|
integrity sha512-GmkOcb5he2x5gat+TuiTvabnBf1m4jzdecal3XbXBh/Jg+kx4hcvO3TTDFQ9CuTprtzdcVyA11iqG7iOMOt7vA==
|
||||||
|
|
||||||
apollo-server-plugin-base@0.5.2:
|
apollo-server-plugin-base@0.5.5:
|
||||||
version "0.5.2"
|
version "0.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.2.tgz#f97ba983f1e825fec49cba8ff6a23d00e1901819"
|
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.5.tgz#364e4a2fca4d95ddeb9fd3e78940ed1da58865c2"
|
||||||
integrity sha512-j81CpadRLhxikBYHMh91X4aTxfzFnmmebEiIR9rruS6dywWCxV2aLW87l9ocD1MiueNam0ysdwZkX4F3D4csNw==
|
integrity sha512-agiuhknyu3lnnEsqUh99tzxwPCGp+TuDK+TSRTkXU1RUG6lY4C3uJp0JGJw03cP+M6ze73TbRjMA4E68g/ks5A==
|
||||||
|
|
||||||
apollo-server-testing@~2.6.2:
|
apollo-server-testing@~2.6.6:
|
||||||
version "2.6.2"
|
version "2.6.6"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.2.tgz#e0ecddd565fce1c38a346f9fbe6118f543ccf6a6"
|
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.6.tgz#3d96486ebdb151219183fcb715973a8385c66e0a"
|
||||||
integrity sha512-I9QLFk4I/z9oOIXfnLc8RPBYAKih6Olrg3RDeRvWhDjLQ8gfALXVhCO+7WuvM35wNZcZVn7aXBeZ8Y3mlgkj8w==
|
integrity sha512-GfzEAqXGzhWp1YgNJONAijC3mC34E6cI5XiOdLX9FGAnBmZvDURlZwloZbdNgvqvXnwuxuNgo4xvCnTe7kndqg==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-server-core "2.6.2"
|
apollo-server-core "2.6.6"
|
||||||
|
|
||||||
apollo-server@~2.6.2:
|
apollo-server@~2.6.6:
|
||||||
version "2.6.2"
|
version "2.6.6"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.2.tgz#33fe894b740588f059a7679346516ffce50377d5"
|
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.6.tgz#0570fce4a682eb1de8bc1b86dbe2543de440cd4e"
|
||||||
integrity sha512-fMXaAKIb0dX0lzcZ4zlu7ay1L596d9HTNkdn8cKuM7zmTpugZSAL966COguJUDSjUS9CaB1Kh5hl1yRuRqHXSA==
|
integrity sha512-7Bulb3RnOO4/SGA66LXu3ZHCXIK8MYMrsxy4yti1/adDIUmcniolDqJwOYUGoTmv1AQjRxgJb4TVZ0Dk9nrrYg==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-server-core "2.6.2"
|
apollo-server-core "2.6.6"
|
||||||
apollo-server-express "2.6.2"
|
apollo-server-express "2.6.6"
|
||||||
express "^4.0.0"
|
express "^4.0.0"
|
||||||
graphql-subscriptions "^1.0.0"
|
graphql-subscriptions "^1.0.0"
|
||||||
graphql-tools "^4.0.0"
|
graphql-tools "^4.0.0"
|
||||||
|
|
||||||
apollo-tracing@0.7.2:
|
apollo-tracing@0.7.3:
|
||||||
version "0.7.2"
|
version "0.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.7.2.tgz#7730159a4670bca465ac1bfa01f9902610a7aba4"
|
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.7.3.tgz#8533e3e2dca2d5a25e8439ce498ea33ff4d159ee"
|
||||||
integrity sha512-bT4/n8Vy9DweC3+XWJelJD41FBlKMXR0OVxjLMiCe9clb4yTgKhYxRGTyh9KjmhWsng9gG/DphO0ixWsOgdXmA==
|
integrity sha512-H6fSC+awQGnfDyYdGIB0UQUhcUC3n5Vy+ujacJ0bY6R+vwWeZOQvu7wRHNjk/rbOSTLCo9A0OcVX7huRyu9SZg==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-server-env "2.4.0"
|
apollo-server-env "2.4.0"
|
||||||
graphql-extensions "0.7.2"
|
graphql-extensions "0.7.4"
|
||||||
|
|
||||||
apollo-tracing@^0.1.0:
|
apollo-tracing@^0.1.0:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
@ -1566,7 +1557,7 @@ apollo-upload-server@^7.0.0:
|
|||||||
http-errors "^1.7.0"
|
http-errors "^1.7.0"
|
||||||
object-path "^0.11.4"
|
object-path "^0.11.4"
|
||||||
|
|
||||||
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.2.1, apollo-utilities@^1.3.2:
|
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
|
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
|
||||||
integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
|
integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
|
||||||
@ -1790,10 +1781,10 @@ babel-core@~7.0.0-0:
|
|||||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
||||||
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
|
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
|
||||||
|
|
||||||
babel-eslint@~10.0.1:
|
babel-eslint@~10.0.2:
|
||||||
version "10.0.1"
|
version "10.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
|
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
|
||||||
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
|
integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.0.0"
|
"@babel/code-frame" "^7.0.0"
|
||||||
"@babel/parser" "^7.0.0"
|
"@babel/parser" "^7.0.0"
|
||||||
@ -2070,6 +2061,13 @@ busboy@^0.2.14:
|
|||||||
dicer "0.2.5"
|
dicer "0.2.5"
|
||||||
readable-stream "1.1.x"
|
readable-stream "1.1.x"
|
||||||
|
|
||||||
|
busboy@^0.3.1:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
|
||||||
|
integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==
|
||||||
|
dependencies:
|
||||||
|
dicer "0.3.0"
|
||||||
|
|
||||||
bytes@3.1.0:
|
bytes@3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||||
@ -2586,10 +2584,10 @@ data-urls@^1.0.0:
|
|||||||
whatwg-mimetype "^2.2.0"
|
whatwg-mimetype "^2.2.0"
|
||||||
whatwg-url "^7.0.0"
|
whatwg-url "^7.0.0"
|
||||||
|
|
||||||
date-fns@2.0.0-alpha.31:
|
date-fns@2.0.0-alpha.37:
|
||||||
version "2.0.0-alpha.31"
|
version "2.0.0-alpha.37"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.31.tgz#51bcfdca25dfc9bea334a556ab33dfc0bb00421c"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.37.tgz#c58b3e827da4f860ec8dc123e54019efb4a610e0"
|
||||||
integrity sha512-S19PwMqnbYsqcbCg02Yj9gv4veVNZ0OX7v2+zcd+Mq0RI7LoDKJipJjnMrTZ3Cc6blDuTce5G/pHXcVIGRwJWQ==
|
integrity sha512-fyIv/h6fkFd1u2NHXni5LPRPoa9FFh3hY67JSjNfa+k/u4EKvfrpGtoTM16Y/BJOqTb4W05UjcmwBna1ElyxDA==
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
@ -2721,6 +2719,13 @@ dicer@0.2.5:
|
|||||||
readable-stream "1.1.x"
|
readable-stream "1.1.x"
|
||||||
streamsearch "0.1.2"
|
streamsearch "0.1.2"
|
||||||
|
|
||||||
|
dicer@0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
|
||||||
|
integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==
|
||||||
|
dependencies:
|
||||||
|
streamsearch "0.1.2"
|
||||||
|
|
||||||
diff-sequences@^24.3.0:
|
diff-sequences@^24.3.0:
|
||||||
version "24.3.0"
|
version "24.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
|
||||||
@ -2995,10 +3000,10 @@ escodegen@^1.9.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
eslint-config-prettier@~4.3.0:
|
eslint-config-prettier@~5.0.0:
|
||||||
version "4.3.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.3.0.tgz#c55c1fcac8ce4518aeb77906984e134d9eb5a4f0"
|
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz#f7a94b2b8ae7cbf25842c36fa96c6d32cd0a697c"
|
||||||
integrity sha512-sZwhSTHVVz78+kYD3t5pCWSYEdVSBR0PXnwjDRsUs8ytIrK8PLXw+6FKp8r3Z7rx4ZszdetWlXYKOHoUrrwPlA==
|
integrity sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w==
|
||||||
dependencies:
|
dependencies:
|
||||||
get-stdin "^6.0.0"
|
get-stdin "^6.0.0"
|
||||||
|
|
||||||
@ -3031,10 +3036,10 @@ eslint-plugin-es@^1.4.0:
|
|||||||
eslint-utils "^1.3.0"
|
eslint-utils "^1.3.0"
|
||||||
regexpp "^2.0.1"
|
regexpp "^2.0.1"
|
||||||
|
|
||||||
eslint-plugin-import@~2.17.3:
|
eslint-plugin-import@~2.18.0:
|
||||||
version "2.17.3"
|
version "2.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.3.tgz#00548b4434c18faebaba04b24ae6198f280de189"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.0.tgz#7a5ba8d32622fb35eb9c8db195c2090bd18a3678"
|
||||||
integrity sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==
|
integrity sha512-PZpAEC4gj/6DEMMoU2Df01C5c50r7zdGIN52Yfi7CvvWaYssG7Jt5R9nFG5gmqodxNOz9vQS87xk6Izdtpdrig==
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes "^3.0.3"
|
array-includes "^3.0.3"
|
||||||
contains-path "^0.1.0"
|
contains-path "^0.1.0"
|
||||||
@ -3048,10 +3053,10 @@ eslint-plugin-import@~2.17.3:
|
|||||||
read-pkg-up "^2.0.0"
|
read-pkg-up "^2.0.0"
|
||||||
resolve "^1.11.0"
|
resolve "^1.11.0"
|
||||||
|
|
||||||
eslint-plugin-jest@~22.6.4:
|
eslint-plugin-jest@~22.7.1:
|
||||||
version "22.6.4"
|
version "22.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.6.4.tgz#2895b047dd82f90f43a58a25cf136220a21c9104"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.7.1.tgz#5dcdf8f7a285f98040378220d6beca581f0ab2a1"
|
||||||
integrity sha512-36OqnZR/uMCDxXGmTsqU4RwllR0IiB/XF8GW3ODmhsjiITKuI0GpgultWFt193ipN3HARkaIcKowpE6HBvRHNg==
|
integrity sha512-CrT3AzA738neimv8G8iK2HCkrCwHnAJeeo7k5TEHK86VMItKl6zdJT/tHBDImfnVVAYsVs4Y6BUdBZQCCgfiyw==
|
||||||
|
|
||||||
eslint-plugin-node@~9.1.0:
|
eslint-plugin-node@~9.1.0:
|
||||||
version "9.1.0"
|
version "9.1.0"
|
||||||
@ -3072,10 +3077,10 @@ eslint-plugin-prettier@~3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prettier-linter-helpers "^1.0.0"
|
prettier-linter-helpers "^1.0.0"
|
||||||
|
|
||||||
eslint-plugin-promise@~4.1.1:
|
eslint-plugin-promise@~4.2.1:
|
||||||
version "4.1.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
||||||
integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==
|
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
||||||
|
|
||||||
eslint-plugin-standard@~4.0.0:
|
eslint-plugin-standard@~4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
@ -3108,13 +3113,13 @@ eslint-visitor-keys@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||||
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
|
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
|
||||||
|
|
||||||
eslint@~5.16.0:
|
eslint@~6.0.1:
|
||||||
version "5.16.0"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.0.1.tgz#4a32181d72cb999d6f54151df7d337131f81cda7"
|
||||||
integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==
|
integrity sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.0.0"
|
"@babel/code-frame" "^7.0.0"
|
||||||
ajv "^6.9.1"
|
ajv "^6.10.0"
|
||||||
chalk "^2.1.0"
|
chalk "^2.1.0"
|
||||||
cross-spawn "^6.0.5"
|
cross-spawn "^6.0.5"
|
||||||
debug "^4.0.1"
|
debug "^4.0.1"
|
||||||
@ -3122,18 +3127,19 @@ eslint@~5.16.0:
|
|||||||
eslint-scope "^4.0.3"
|
eslint-scope "^4.0.3"
|
||||||
eslint-utils "^1.3.1"
|
eslint-utils "^1.3.1"
|
||||||
eslint-visitor-keys "^1.0.0"
|
eslint-visitor-keys "^1.0.0"
|
||||||
espree "^5.0.1"
|
espree "^6.0.0"
|
||||||
esquery "^1.0.1"
|
esquery "^1.0.1"
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
file-entry-cache "^5.0.1"
|
file-entry-cache "^5.0.1"
|
||||||
functional-red-black-tree "^1.0.1"
|
functional-red-black-tree "^1.0.1"
|
||||||
glob "^7.1.2"
|
glob-parent "^3.1.0"
|
||||||
globals "^11.7.0"
|
globals "^11.7.0"
|
||||||
ignore "^4.0.6"
|
ignore "^4.0.6"
|
||||||
import-fresh "^3.0.0"
|
import-fresh "^3.0.0"
|
||||||
imurmurhash "^0.1.4"
|
imurmurhash "^0.1.4"
|
||||||
inquirer "^6.2.2"
|
inquirer "^6.2.2"
|
||||||
js-yaml "^3.13.0"
|
is-glob "^4.0.0"
|
||||||
|
js-yaml "^3.13.1"
|
||||||
json-stable-stringify-without-jsonify "^1.0.1"
|
json-stable-stringify-without-jsonify "^1.0.1"
|
||||||
levn "^0.3.0"
|
levn "^0.3.0"
|
||||||
lodash "^4.17.11"
|
lodash "^4.17.11"
|
||||||
@ -3141,7 +3147,6 @@ eslint@~5.16.0:
|
|||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
natural-compare "^1.4.0"
|
natural-compare "^1.4.0"
|
||||||
optionator "^0.8.2"
|
optionator "^0.8.2"
|
||||||
path-is-inside "^1.0.2"
|
|
||||||
progress "^2.0.0"
|
progress "^2.0.0"
|
||||||
regexpp "^2.0.1"
|
regexpp "^2.0.1"
|
||||||
semver "^5.5.1"
|
semver "^5.5.1"
|
||||||
@ -3150,10 +3155,10 @@ eslint@~5.16.0:
|
|||||||
table "^5.2.3"
|
table "^5.2.3"
|
||||||
text-table "^0.2.0"
|
text-table "^0.2.0"
|
||||||
|
|
||||||
espree@^5.0.1:
|
espree@^6.0.0:
|
||||||
version "5.0.1"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
|
resolved "https://registry.yarnpkg.com/espree/-/espree-6.0.0.tgz#716fc1f5a245ef5b9a7fdb1d7b0d3f02322e75f6"
|
||||||
integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==
|
integrity sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^6.0.7"
|
acorn "^6.0.7"
|
||||||
acorn-jsx "^5.0.0"
|
acorn-jsx "^5.0.0"
|
||||||
@ -3368,10 +3373,9 @@ extsprintf@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||||
|
|
||||||
faker@~4.1.0:
|
faker@Marak/faker.js#master:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f"
|
resolved "https://codeload.github.com/Marak/faker.js/tar.gz/10bfb9f467b0ac2b8912ffc15690b50ef3244f09"
|
||||||
integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=
|
|
||||||
|
|
||||||
fast-deep-equal@^2.0.1:
|
fast-deep-equal@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
@ -3535,6 +3539,11 @@ fs-capacitor@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-1.0.1.tgz#ff9dbfa14dfaf4472537720f19c3088ed9278df0"
|
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-1.0.1.tgz#ff9dbfa14dfaf4472537720f19c3088ed9278df0"
|
||||||
integrity sha512-XdZK0Q78WP29Vm3FGgJRhRhrBm51PagovzWtW2kJ3Q6cYJbGtZqWSGTSPwvtEkyjIirFd7b8Yes/dpOYjt4RRQ==
|
integrity sha512-XdZK0Q78WP29Vm3FGgJRhRhrBm51PagovzWtW2kJ3Q6cYJbGtZqWSGTSPwvtEkyjIirFd7b8Yes/dpOYjt4RRQ==
|
||||||
|
|
||||||
|
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-minipass@^1.2.5:
|
fs-minipass@^1.2.5:
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
|
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
|
||||||
@ -3711,10 +3720,17 @@ graphql-deduplicator@^2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/graphql-deduplicator/-/graphql-deduplicator-2.0.2.tgz#d8608161cf6be97725e178df0c41f6a1f9f778f3"
|
resolved "https://registry.yarnpkg.com/graphql-deduplicator/-/graphql-deduplicator-2.0.2.tgz#d8608161cf6be97725e178df0c41f6a1f9f778f3"
|
||||||
integrity sha512-0CGmTmQh4UvJfsaTPppJAcHwHln8Ayat7yXXxdnuWT+Mb1dBzkbErabCWzjXyKh/RefqlGTTA7EQOZHofMaKJA==
|
integrity sha512-0CGmTmQh4UvJfsaTPppJAcHwHln8Ayat7yXXxdnuWT+Mb1dBzkbErabCWzjXyKh/RefqlGTTA7EQOZHofMaKJA==
|
||||||
|
|
||||||
graphql-extensions@0.7.2:
|
graphql-extensions@0.7.4:
|
||||||
version "0.7.2"
|
version "0.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.7.2.tgz#8711543f835661eaf24b48d6ac2aad44dbbd5506"
|
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.7.4.tgz#78327712822281d5778b9210a55dc59c93a9c184"
|
||||||
integrity sha512-TuVINuAOrEtzQAkAlCZMi9aP5rcZ+pVaqoBI5fD2k5O9fmb8OuXUQOW028MUhC66tg4E7h4YSF1uYUIimbu4SQ==
|
integrity sha512-Ly+DiTDU+UtlfPGQkqmBX2SWMr9OT3JxMRwpB9K86rDNDBTJtG6AE2kliQKKE+hg1+945KAimO7Ep+YAvS7ywg==
|
||||||
|
dependencies:
|
||||||
|
"@apollographql/apollo-tools" "^0.3.6"
|
||||||
|
|
||||||
|
graphql-extensions@0.7.5:
|
||||||
|
version "0.7.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.7.5.tgz#fab2b9e53cf6014952e6547456d50680ff0ea579"
|
||||||
|
integrity sha512-B1m+/WEJa3IYKWqBPS9W/7OasfPmlHOSz5hpEAq2Jbn6T0FQ/d2YWFf2HBETHR3RR2qfT+55VMiYovl2ga3qcg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@apollographql/apollo-tools" "^0.3.6"
|
"@apollographql/apollo-tools" "^0.3.6"
|
||||||
|
|
||||||
@ -3772,12 +3788,12 @@ graphql-request@~1.8.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cross-fetch "2.2.2"
|
cross-fetch "2.2.2"
|
||||||
|
|
||||||
graphql-shield@~5.3.8:
|
graphql-shield@~5.7.1:
|
||||||
version "5.3.8"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.8.tgz#f9e7ad2285f6cfbe20a8a49154ce6c1b184e3893"
|
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.7.1.tgz#04095fb8148a463997f7c509d4aeb2a6abf79f98"
|
||||||
integrity sha512-33rQ8U5jMurHIapctHk7hBcUg3nxC7fmMIMtyWiomJXhBmztFq/SG7jNaapnL5M7Q/0BmoaSQd3FLSpelP9KPw==
|
integrity sha512-UZ0K1uAqRAoGA1U2DsUu4vIZX2Vents4Xim99GFEUBTgvSDkejiE+k/Dywqfu76lJFEE8qu3vG5fhJN3SmnKbA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/yup" "0.26.16"
|
"@types/yup" "0.26.17"
|
||||||
lightercollective "^0.3.0"
|
lightercollective "^0.3.0"
|
||||||
object-hash "^1.3.1"
|
object-hash "^1.3.1"
|
||||||
yup "^0.27.0"
|
yup "^0.27.0"
|
||||||
@ -3812,20 +3828,20 @@ graphql-tools@^4.0.0, graphql-tools@^4.0.4:
|
|||||||
iterall "^1.1.3"
|
iterall "^1.1.3"
|
||||||
uuid "^3.1.0"
|
uuid "^3.1.0"
|
||||||
|
|
||||||
graphql-upload@^8.0.2:
|
graphql-upload@^8.0.0, graphql-upload@^8.0.2:
|
||||||
version "8.0.2"
|
version "8.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-8.0.2.tgz#1c1f116f15b7f8485cf40ff593a21368f0f58856"
|
resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-8.0.7.tgz#8644264e241529552ea4b3797e7ee15809cf01a3"
|
||||||
integrity sha512-u8a5tKPfJ0rU4MY+B3skabL8pEjMkm3tUzq25KBx6nT0yEWmqUO7Z5tdwvwYLFpkLwew94Gue0ARbZtar3gLTw==
|
integrity sha512-gi2yygbDPXbHPC7H0PNPqP++VKSoNoJO4UrXWq4T0Bi4IhyUd3Ycop/FSxhx2svWIK3jdXR/i0vi91yR1aAF0g==
|
||||||
dependencies:
|
dependencies:
|
||||||
busboy "^0.2.14"
|
busboy "^0.3.1"
|
||||||
fs-capacitor "^1.0.0"
|
fs-capacitor "^2.0.4"
|
||||||
http-errors "^1.7.1"
|
http-errors "^1.7.2"
|
||||||
object-path "^0.11.4"
|
object-path "^0.11.4"
|
||||||
|
|
||||||
graphql-yoga@~1.17.4:
|
graphql-yoga@~1.18.0:
|
||||||
version "1.17.4"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-1.17.4.tgz#6d325a6270399edf0776fb5f60a2e9e19512e63c"
|
resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-1.18.0.tgz#2668278e94a0bd1b2ff8c60f928c4e18d62e381a"
|
||||||
integrity sha512-zOXFtmS43xDLoECKiuA3xVWH/wLDvLH1D/5fBKcaMFdF43ifDfnA9N6dlGggqAoOhqBnrqHwDpoKlJ6sI1LuxA==
|
integrity sha512-WEibitQA2oFTmD7XBO8/ps8DWeVpkzOzgbB3EvtM2oIpyGhPCzRZYrC7OS9MmijvRwLRXsgHImHWUm82ZrIOWA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/cors" "^2.8.4"
|
"@types/cors" "^2.8.4"
|
||||||
"@types/express" "^4.11.1"
|
"@types/express" "^4.11.1"
|
||||||
@ -3847,6 +3863,7 @@ graphql-yoga@~1.17.4:
|
|||||||
graphql-playground-middleware-lambda "1.7.12"
|
graphql-playground-middleware-lambda "1.7.12"
|
||||||
graphql-subscriptions "^0.5.8"
|
graphql-subscriptions "^0.5.8"
|
||||||
graphql-tools "^4.0.0"
|
graphql-tools "^4.0.0"
|
||||||
|
graphql-upload "^8.0.0"
|
||||||
subscriptions-transport-ws "^0.9.8"
|
subscriptions-transport-ws "^0.9.8"
|
||||||
|
|
||||||
"graphql@^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0", graphql@^14.2.1, graphql@~14.3.1:
|
"graphql@^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0", graphql@^14.2.1, graphql@~14.3.1:
|
||||||
@ -4045,7 +4062,7 @@ htmlparser2@^3.10.0, htmlparser2@^3.9.1:
|
|||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
readable-stream "^3.0.6"
|
readable-stream "^3.0.6"
|
||||||
|
|
||||||
http-errors@1.7.2, http-errors@~1.7.2:
|
http-errors@1.7.2, http-errors@^1.7.2, http-errors@~1.7.2:
|
||||||
version "1.7.2"
|
version "1.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||||
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||||
@ -4056,7 +4073,7 @@ http-errors@1.7.2, http-errors@~1.7.2:
|
|||||||
statuses ">= 1.5.0 < 2"
|
statuses ">= 1.5.0 < 2"
|
||||||
toidentifier "1.0.0"
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
http-errors@^1.7.0, http-errors@^1.7.1:
|
http-errors@^1.7.0:
|
||||||
version "1.7.1"
|
version "1.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.1.tgz#6a4ffe5d35188e1c39f872534690585852e1f027"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.1.tgz#6a4ffe5d35188e1c39f872534690585852e1f027"
|
||||||
integrity sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==
|
integrity sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==
|
||||||
@ -4923,10 +4940,10 @@ js-levenshtein@^1.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||||
|
|
||||||
js-yaml@^3.13.0:
|
js-yaml@^3.13.1:
|
||||||
version "3.13.0"
|
version "3.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e"
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
||||||
integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==
|
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
@ -5596,9 +5613,10 @@ neo4j-driver@^1.7.3, neo4j-driver@~1.7.4:
|
|||||||
text-encoding "^0.6.4"
|
text-encoding "^0.6.4"
|
||||||
uri-js "^4.2.1"
|
uri-js "^4.2.1"
|
||||||
|
|
||||||
"neo4j-graphql-js@git+https://github.com/Human-Connection/neo4j-graphql-js.git#temporary_fixes":
|
neo4j-graphql-js@^2.6.3:
|
||||||
version "2.6.1"
|
version "2.6.3"
|
||||||
resolved "git+https://github.com/Human-Connection/neo4j-graphql-js.git#84d529b9ecbc5c284cce4f86238c6d19b192cf0f"
|
resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.6.3.tgz#8f28c2479adda08c90abcc32a784587ef49b8b95"
|
||||||
|
integrity sha512-WZdEqQ8EL9GOIB1ZccbLk1BZz5Dqdbk9i8BDXqxhp1SOI07P9y2cZ244f2Uz4zyES9AVXGmv+861N5xLhrSL2A==
|
||||||
dependencies:
|
dependencies:
|
||||||
graphql "^14.2.1"
|
graphql "^14.2.1"
|
||||||
graphql-auth-directives "^2.1.0"
|
graphql-auth-directives "^2.1.0"
|
||||||
@ -5693,6 +5711,11 @@ node-releases@^1.1.19:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
semver "^5.3.0"
|
||||||
|
|
||||||
|
nodemailer@^6.2.1:
|
||||||
|
version "6.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.2.1.tgz#20d773925eb8f7a06166a0b62c751dc8290429f3"
|
||||||
|
integrity sha512-TagB7iuIi9uyNgHExo8lUDq3VK5/B0BpbkcjIgNvxbtVrjNqq0DwAOTuzALPVkK76kMhTSzIgHqg8X1uklVs6g==
|
||||||
|
|
||||||
nodemon@~1.19.1:
|
nodemon@~1.19.1:
|
||||||
version "1.19.1"
|
version "1.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.1.tgz#576f0aad0f863aabf8c48517f6192ff987cd5071"
|
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.1.tgz#576f0aad0f863aabf8c48517f6192ff987cd5071"
|
||||||
@ -6099,7 +6122,7 @@ path-is-absolute@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||||
|
|
||||||
path-is-inside@^1.0.1, path-is-inside@^1.0.2:
|
path-is-inside@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||||
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
||||||
@ -7543,13 +7566,6 @@ trunc-text@1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.1.tgz#58f876d8ac59b224b79834bb478b8656e69622b5"
|
resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.1.tgz#58f876d8ac59b224b79834bb478b8656e69622b5"
|
||||||
integrity sha1-WPh22KxZsiS3mDS7R4uGVuaWIrU=
|
integrity sha1-WPh22KxZsiS3mDS7R4uGVuaWIrU=
|
||||||
|
|
||||||
ts-invariant@^0.3.2:
|
|
||||||
version "0.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0"
|
|
||||||
integrity sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==
|
|
||||||
dependencies:
|
|
||||||
tslib "^1.9.3"
|
|
||||||
|
|
||||||
ts-invariant@^0.4.0:
|
ts-invariant@^0.4.0:
|
||||||
version "0.4.2"
|
version "0.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.2.tgz#8685131b8083e67c66d602540e78763408be9113"
|
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.2.tgz#8685131b8083e67c66d602540e78763408be9113"
|
||||||
@ -8112,10 +8128,10 @@ yup@^0.27.0:
|
|||||||
synchronous-promise "^2.0.6"
|
synchronous-promise "^2.0.6"
|
||||||
toposort "^2.0.2"
|
toposort "^2.0.2"
|
||||||
|
|
||||||
zen-observable-ts@^0.8.18:
|
zen-observable-ts@^0.8.19:
|
||||||
version "0.8.18"
|
version "0.8.19"
|
||||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz#ade44b1060cc4a800627856ec10b9c67f5f639c8"
|
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.19.tgz#c094cd20e83ddb02a11144a6e2a89706946b5694"
|
||||||
integrity sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A==
|
integrity sha512-u1a2rpE13G+jSzrg3aiCqXU5tN2kw41b+cBZGmnc+30YimdkKiDj9bTowcB41eL77/17RF/h+393AuVgShyheQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
zen-observable "^0.8.0"
|
zen-observable "^0.8.0"
|
||||||
|
|||||||
@ -1,347 +1,361 @@
|
|||||||
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
|
import { Given, When, Then } from "cypress-cucumber-preprocessor/steps";
|
||||||
import { getLangByName } from '../../support/helpers'
|
import { getLangByName } from "../../support/helpers";
|
||||||
|
|
||||||
/* global cy */
|
/* global cy */
|
||||||
|
|
||||||
let lastPost = {}
|
let lastPost = {};
|
||||||
|
|
||||||
let loginCredentials = {
|
let loginCredentials = {
|
||||||
email: 'peterpan@example.org',
|
email: "peterpan@example.org",
|
||||||
password: '1234'
|
password: "1234"
|
||||||
}
|
};
|
||||||
const narratorParams = {
|
const narratorParams = {
|
||||||
name: 'Peter Pan',
|
name: "Peter Pan",
|
||||||
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg',
|
avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg",
|
||||||
...loginCredentials
|
...loginCredentials
|
||||||
}
|
};
|
||||||
|
|
||||||
Given('I am logged in', () => {
|
Given("I am logged in", () => {
|
||||||
cy.login(loginCredentials)
|
cy.login(loginCredentials);
|
||||||
})
|
});
|
||||||
|
|
||||||
Given('we have a selection of tags and categories as well as posts', () => {
|
Given("we have a selection of tags and categories as well as posts", () => {
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.authenticateAs(loginCredentials)
|
.authenticateAs(loginCredentials)
|
||||||
.create('Category', {
|
.create("Category", {
|
||||||
id: 'cat1',
|
id: "cat1",
|
||||||
name: 'Just For Fun',
|
name: "Just For Fun",
|
||||||
slug: 'justforfun',
|
slug: "justforfun",
|
||||||
icon: 'smile'
|
icon: "smile"
|
||||||
})
|
})
|
||||||
.create('Category', {
|
.create("Category", {
|
||||||
id: 'cat2',
|
id: "cat2",
|
||||||
name: 'Happyness & Values',
|
name: "Happyness & Values",
|
||||||
slug: 'happyness-values',
|
slug: "happyness-values",
|
||||||
icon: 'heart-o'
|
icon: "heart-o"
|
||||||
})
|
})
|
||||||
.create('Category', {
|
.create("Category", {
|
||||||
id: 'cat3',
|
id: "cat3",
|
||||||
name: 'Health & Wellbeing',
|
name: "Health & Wellbeing",
|
||||||
slug: 'health-wellbeing',
|
slug: "health-wellbeing",
|
||||||
icon: 'medkit'
|
icon: "medkit"
|
||||||
})
|
})
|
||||||
.create('Tag', { id: 't1', name: 'Ecology' })
|
.create("Tag", { id: "t1", name: "Ecology" })
|
||||||
.create('Tag', { id: 't2', name: 'Nature' })
|
.create("Tag", { id: "t2", name: "Nature" })
|
||||||
.create('Tag', { id: 't3', name: 'Democracy' })
|
.create("Tag", { id: "t3", name: "Democracy" });
|
||||||
|
|
||||||
const someAuthor = {
|
const someAuthor = {
|
||||||
id: 'authorId',
|
id: "authorId",
|
||||||
email: 'author@example.org',
|
email: "author@example.org",
|
||||||
password: '1234'
|
password: "1234"
|
||||||
}
|
};
|
||||||
const yetAnotherAuthor = {
|
const yetAnotherAuthor = {
|
||||||
id: 'yetAnotherAuthor',
|
id: "yetAnotherAuthor",
|
||||||
email: 'yet-another-author@example.org',
|
email: "yet-another-author@example.org",
|
||||||
password: '1234'
|
password: "1234"
|
||||||
}
|
};
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.create('User', someAuthor)
|
.create("User", someAuthor)
|
||||||
.authenticateAs(someAuthor)
|
.authenticateAs(someAuthor)
|
||||||
.create('Post', { id: 'p0' })
|
.create("Post", { id: "p0" })
|
||||||
.create('Post', { id: 'p1' })
|
.create("Post", { id: "p1" });
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.create('User', yetAnotherAuthor)
|
.create("User", yetAnotherAuthor)
|
||||||
.authenticateAs(yetAnotherAuthor)
|
.authenticateAs(yetAnotherAuthor)
|
||||||
.create('Post', { id: 'p2' })
|
.create("Post", { id: "p2" });
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.authenticateAs(loginCredentials)
|
.authenticateAs(loginCredentials)
|
||||||
.create('Post', { id: 'p3' })
|
.create("Post", { id: "p3" })
|
||||||
.relate('Post', 'Categories', { from: 'p0', to: 'cat1' })
|
.relate("Post", "Categories", { from: "p0", to: "cat1" })
|
||||||
.relate('Post', 'Categories', { from: 'p1', to: 'cat2' })
|
.relate("Post", "Categories", { from: "p1", to: "cat2" })
|
||||||
.relate('Post', 'Categories', { from: 'p2', to: 'cat1' })
|
.relate("Post", "Categories", { from: "p2", to: "cat1" })
|
||||||
.relate('Post', 'Tags', { from: 'p0', to: 't1' })
|
.relate("Post", "Tags", { from: "p0", to: "t1" })
|
||||||
.relate('Post', 'Tags', { from: 'p0', to: 't2' })
|
.relate("Post", "Tags", { from: "p0", to: "t2" })
|
||||||
.relate('Post', 'Tags', { from: 'p0', to: 't3' })
|
.relate("Post", "Tags", { from: "p0", to: "t3" })
|
||||||
.relate('Post', 'Tags', { from: 'p1', to: 't2' })
|
.relate("Post", "Tags", { from: "p1", to: "t2" })
|
||||||
.relate('Post', 'Tags', { from: 'p1', to: 't3' })
|
.relate("Post", "Tags", { from: "p1", to: "t3" })
|
||||||
.relate('Post', 'Tags', { from: 'p2', to: 't2' })
|
.relate("Post", "Tags", { from: "p2", to: "t2" })
|
||||||
.relate('Post', 'Tags', { from: 'p2', to: 't3' })
|
.relate("Post", "Tags", { from: "p2", to: "t3" })
|
||||||
.relate('Post', 'Tags', { from: 'p3', to: 't3' })
|
.relate("Post", "Tags", { from: "p3", to: "t3" });
|
||||||
})
|
});
|
||||||
|
|
||||||
Given('we have the following user accounts:', table => {
|
Given("we have the following user accounts:", table => {
|
||||||
table.hashes().forEach(params => {
|
table.hashes().forEach(params => {
|
||||||
cy.factory().create('User', params)
|
cy.factory().create("User", params);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
Given('I have a user account', () => {
|
Given("I have a user account", () => {
|
||||||
cy.factory().create('User', narratorParams)
|
cy.factory().create("User", narratorParams);
|
||||||
})
|
});
|
||||||
|
|
||||||
Given('my user account has the role {string}', role => {
|
Given("my user account has the role {string}", role => {
|
||||||
cy.factory().create('User', {
|
cy.factory().create("User", {
|
||||||
role,
|
role,
|
||||||
...loginCredentials
|
...loginCredentials
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I log out', cy.logout)
|
When("I log out", cy.logout);
|
||||||
|
|
||||||
When('I visit {string}', page => {
|
When("I visit {string}", page => {
|
||||||
cy.openPage(page)
|
cy.openPage(page);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I visit the {string} page', page => {
|
When("I visit the {string} page", page => {
|
||||||
cy.openPage(page)
|
cy.openPage(page);
|
||||||
})
|
});
|
||||||
|
|
||||||
Given('I am on the {string} page', page => {
|
Given("I am on the {string} page", page => {
|
||||||
cy.openPage(page)
|
cy.openPage(page);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I fill in my email and password combination and click submit', () => {
|
When("I fill in my email and password combination and click submit", () => {
|
||||||
cy.login(loginCredentials)
|
cy.login(loginCredentials);
|
||||||
})
|
});
|
||||||
|
|
||||||
When(/(?:when )?I refresh the page/, () => {
|
When(/(?:when )?I refresh the page/, () => {
|
||||||
cy.reload()
|
cy.reload();
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I log out through the menu in the top right corner', () => {
|
When("I log out through the menu in the top right corner", () => {
|
||||||
cy.get('.avatar-menu').click()
|
cy.get(".avatar-menu").click();
|
||||||
cy.get('.avatar-menu-popover')
|
cy.get(".avatar-menu-popover")
|
||||||
.find('a[href="/logout"]')
|
.find('a[href="/logout"]')
|
||||||
.click()
|
.click();
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I can see my name {string} in the dropdown menu', () => {
|
Then("I can see my name {string} in the dropdown menu", () => {
|
||||||
cy.get('.avatar-menu-popover').should('contain', narratorParams.name)
|
cy.get(".avatar-menu-popover").should("contain", narratorParams.name);
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I see the login screen again', () => {
|
Then("I see the login screen again", () => {
|
||||||
cy.location('pathname').should('contain', '/login')
|
cy.location("pathname").should("contain", "/login");
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I can click on my profile picture in the top right corner', () => {
|
Then("I can click on my profile picture in the top right corner", () => {
|
||||||
cy.get('.avatar-menu').click()
|
cy.get(".avatar-menu").click();
|
||||||
cy.get('.avatar-menu-popover')
|
cy.get(".avatar-menu-popover");
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I am still logged in', () => {
|
Then("I am still logged in", () => {
|
||||||
cy.get('.avatar-menu').click()
|
cy.get(".avatar-menu").click();
|
||||||
cy.get('.avatar-menu-popover').contains(narratorParams.name)
|
cy.get(".avatar-menu-popover").contains(narratorParams.name);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I select {string} in the language menu', name => {
|
When("I select {string} in the language menu", name => {
|
||||||
cy.switchLanguage(name, true)
|
cy.switchLanguage(name, true);
|
||||||
})
|
});
|
||||||
Given('I previously switched the language to {string}', name => {
|
Given("I previously switched the language to {string}", name => {
|
||||||
cy.switchLanguage(name, true)
|
cy.switchLanguage(name, true);
|
||||||
})
|
});
|
||||||
Then('the whole user interface appears in {string}', name => {
|
Then("the whole user interface appears in {string}", name => {
|
||||||
const lang = getLangByName(name)
|
const lang = getLangByName(name);
|
||||||
cy.get(`html[lang=${lang.code}]`)
|
cy.get(`html[lang=${lang.code}]`);
|
||||||
cy.getCookie('locale').should('have.property', 'value', lang.code)
|
cy.getCookie("locale").should("have.property", "value", lang.code);
|
||||||
})
|
});
|
||||||
Then('I see a button with the label {string}', label => {
|
Then("I see a button with the label {string}", label => {
|
||||||
cy.contains('button', label)
|
cy.contains("button", label);
|
||||||
})
|
});
|
||||||
|
|
||||||
When(`I click on {string}`, linkOrButton => {
|
When(`I click on {string}`, linkOrButton => {
|
||||||
cy.contains(linkOrButton).click()
|
cy.contains(linkOrButton).click();
|
||||||
})
|
});
|
||||||
|
|
||||||
When(`I click on the menu item {string}`, linkOrButton => {
|
When(`I click on the menu item {string}`, linkOrButton => {
|
||||||
cy.contains('.ds-menu-item', linkOrButton).click()
|
cy.contains(".ds-menu-item", linkOrButton).click();
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I press {string}', label => {
|
When("I press {string}", label => {
|
||||||
cy.contains(label).click()
|
cy.contains(label).click();
|
||||||
})
|
});
|
||||||
|
|
||||||
Given('we have the following posts in our database:', table => {
|
Given("we have the following posts in our database:", table => {
|
||||||
table.hashes().forEach(({ Author, ...postAttributes }) => {
|
table.hashes().forEach(({ Author, ...postAttributes }) => {
|
||||||
const userAttributes = {
|
const userAttributes = {
|
||||||
name: Author,
|
name: Author,
|
||||||
email: `${Author}@example.org`,
|
email: `${Author}@example.org`,
|
||||||
password: '1234'
|
password: "1234"
|
||||||
}
|
};
|
||||||
postAttributes.deleted = Boolean(postAttributes.deleted)
|
postAttributes.deleted = Boolean(postAttributes.deleted);
|
||||||
const disabled = Boolean(postAttributes.disabled)
|
const disabled = Boolean(postAttributes.disabled);
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.create('User', userAttributes)
|
.create("User", userAttributes)
|
||||||
.authenticateAs(userAttributes)
|
.authenticateAs(userAttributes)
|
||||||
.create('Post', postAttributes)
|
.create("Post", postAttributes);
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
const moderatorParams = {
|
const moderatorParams = {
|
||||||
email: 'moderator@example.org',
|
email: "moderator@example.org",
|
||||||
role: 'moderator',
|
role: "moderator",
|
||||||
password: '1234'
|
password: "1234"
|
||||||
}
|
};
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.create('User', moderatorParams)
|
.create("User", moderatorParams)
|
||||||
.authenticateAs(moderatorParams)
|
.authenticateAs(moderatorParams)
|
||||||
.mutate('mutation($id: ID!) { disable(id: $id) }', postAttributes)
|
.mutate("mutation($id: ID!) { disable(id: $id) }", postAttributes);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I see a success message:', message => {
|
Then("I see a success message:", message => {
|
||||||
cy.contains(message)
|
cy.contains(message);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I click on the avatar menu in the top right corner', () => {
|
When("I click on the avatar menu in the top right corner", () => {
|
||||||
cy.get('.avatar-menu').click()
|
cy.get(".avatar-menu").click();
|
||||||
})
|
});
|
||||||
|
|
||||||
When(
|
When(
|
||||||
'I click on the big plus icon in the bottom right corner to create post',
|
"I click on the big plus icon in the bottom right corner to create post",
|
||||||
() => {
|
() => {
|
||||||
cy.get('.post-add-button').click()
|
cy.get(".post-add-button").click();
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
Given('I previously created a post', () => {
|
Given("I previously created a post", () => {
|
||||||
|
lastPost.title = "previously created post";
|
||||||
|
lastPost.content = "with some content";
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.authenticateAs(loginCredentials)
|
.authenticateAs(loginCredentials)
|
||||||
.create('Post', lastPost)
|
.create("Post", lastPost);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I choose {string} as the title of the post', title => {
|
When("I choose {string} as the title of the post", title => {
|
||||||
lastPost.title = title.replace('\n', ' ')
|
lastPost.title = title.replace("\n", " ");
|
||||||
cy.get('input[name="title"]').type(lastPost.title)
|
cy.get('input[name="title"]').type(lastPost.title);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I type in the following text:', text => {
|
When("I type in the following text:", text => {
|
||||||
lastPost.content = text.replace('\n', ' ')
|
lastPost.content = text.replace("\n", " ");
|
||||||
cy.get('.ProseMirror').type(lastPost.content)
|
cy.get(".ProseMirror").type(lastPost.content);
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('the post shows up on the landing page at position {int}', index => {
|
Then("the post shows up on the landing page at position {int}", index => {
|
||||||
cy.openPage('landing')
|
cy.openPage("landing");
|
||||||
const selector = `.post-card:nth-child(${index}) > .ds-card-content`
|
const selector = `.post-card:nth-child(${index}) > .ds-card-content`;
|
||||||
cy.get(selector).should('contain', lastPost.title)
|
cy.get(selector).should("contain", lastPost.title);
|
||||||
cy.get(selector).should('contain', lastPost.content)
|
cy.get(selector).should("contain", lastPost.content);
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I get redirected to {string}', route => {
|
Then("I get redirected to {string}", route => {
|
||||||
cy.location('pathname').should('contain', route.replace('...', ''))
|
cy.location("pathname").should("contain", route.replace("...", ""));
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('the post was saved successfully', () => {
|
Then("the post was saved successfully", () => {
|
||||||
cy.get('.ds-card-content > .ds-heading').should('contain', lastPost.title)
|
cy.get(".ds-card-content > .ds-heading").should("contain", lastPost.title);
|
||||||
cy.get('.content').should('contain', lastPost.content)
|
cy.get(".content").should("contain", lastPost.content);
|
||||||
})
|
});
|
||||||
|
|
||||||
Then(/^I should see only ([0-9]+) posts? on the landing page/, postCount => {
|
Then(/^I should see only ([0-9]+) posts? on the landing page/, postCount => {
|
||||||
cy.get('.post-card').should('have.length', postCount)
|
cy.get(".post-card").should("have.length", postCount);
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('the first post on the landing page has the title:', title => {
|
Then("the first post on the landing page has the title:", title => {
|
||||||
cy.get('.post-card:first').should('contain', title)
|
cy.get(".post-card:first").should("contain", title);
|
||||||
})
|
});
|
||||||
|
|
||||||
Then(
|
Then(
|
||||||
'the page {string} returns a 404 error with a message:',
|
"the page {string} returns a 404 error with a message:",
|
||||||
(route, message) => {
|
(route, message) => {
|
||||||
// TODO: how can we check HTTP codes with cypress?
|
// TODO: how can we check HTTP codes with cypress?
|
||||||
cy.visit(route, { failOnStatusCode: false })
|
cy.visit(route, { failOnStatusCode: false });
|
||||||
cy.get('.error').should('contain', message)
|
cy.get(".error").should("contain", message);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
Given('my user account has the following login credentials:', table => {
|
Given("my user account has the following login credentials:", table => {
|
||||||
loginCredentials = table.hashes()[0]
|
loginCredentials = table.hashes()[0];
|
||||||
cy.debug()
|
cy.debug();
|
||||||
cy.factory().create('User', loginCredentials)
|
cy.factory().create("User", loginCredentials);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I fill the password form with:', table => {
|
When("I fill the password form with:", table => {
|
||||||
table = table.rowsHash()
|
table = table.rowsHash();
|
||||||
cy.get('input[id=oldPassword]')
|
cy.get("input[id=oldPassword]")
|
||||||
.type(table['Your old password'])
|
.type(table["Your old password"])
|
||||||
.get('input[id=newPassword]')
|
.get("input[id=newPassword]")
|
||||||
.type(table['Your new passsword'])
|
.type(table["Your new passsword"])
|
||||||
.get('input[id=confirmPassword]')
|
.get("input[id=confirmPassword]")
|
||||||
.type(table['Confirm new password'])
|
.type(table["Confirm new password"]);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('submit the form', () => {
|
When("submit the form", () => {
|
||||||
cy.get('form').submit()
|
cy.get("form").submit();
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I cannot login anymore with password {string}', password => {
|
Then("I cannot login anymore with password {string}", password => {
|
||||||
cy.reload()
|
cy.reload();
|
||||||
const { email } = loginCredentials
|
const { email } = loginCredentials;
|
||||||
cy.visit(`/login`)
|
cy.visit(`/login`);
|
||||||
cy.get('input[name=email]')
|
cy.get("input[name=email]")
|
||||||
.trigger('focus')
|
.trigger("focus")
|
||||||
.type(email)
|
.type(email);
|
||||||
cy.get('input[name=password]')
|
cy.get("input[name=password]")
|
||||||
.trigger('focus')
|
.trigger("focus")
|
||||||
.type(password)
|
.type(password);
|
||||||
cy.get('button[name=submit]')
|
cy.get("button[name=submit]")
|
||||||
.as('submitButton')
|
.as("submitButton")
|
||||||
.click()
|
.click();
|
||||||
cy.get('.iziToast-wrapper').should('contain', 'Incorrect email address or password.')
|
cy.get(".iziToast-wrapper").should(
|
||||||
})
|
"contain",
|
||||||
|
"Incorrect email address or password."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
Then('I can login successfully with password {string}', password => {
|
Then("I can login successfully with password {string}", password => {
|
||||||
cy.reload()
|
cy.reload();
|
||||||
cy.login({
|
cy.login({
|
||||||
...loginCredentials,
|
...loginCredentials,
|
||||||
...{password}
|
...{ password }
|
||||||
})
|
});
|
||||||
cy.get('.iziToast-wrapper').should('contain', "You are logged in!")
|
cy.get(".iziToast-wrapper").should("contain", "You are logged in!");
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I log in with the following credentials:', table => {
|
When("I log in with the following credentials:", table => {
|
||||||
const { email, password } = table.hashes()[0]
|
const { email, password } = table.hashes()[0];
|
||||||
cy.login({ email, password })
|
cy.login({ email, password });
|
||||||
})
|
});
|
||||||
|
|
||||||
When('open the notification menu and click on the first item', () => {
|
When("open the notification menu and click on the first item", () => {
|
||||||
cy.get('.notifications-menu').click()
|
cy.get(".notifications-menu").click();
|
||||||
cy.get('.notification-mention-post').first().click()
|
cy.get(".notification-mention-post")
|
||||||
})
|
.first()
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
Then('see {int} unread notifications in the top menu', count => {
|
Then("see {int} unread notifications in the top menu", count => {
|
||||||
cy.get('.notifications-menu').should('contain', count)
|
cy.get(".notifications-menu").should("contain", count);
|
||||||
})
|
});
|
||||||
|
|
||||||
Then('I get to the post page of {string}', path => {
|
Then("I get to the post page of {string}", path => {
|
||||||
path = path.replace('...', '')
|
path = path.replace("...", "");
|
||||||
cy.url().should('contain', '/post/')
|
cy.url().should("contain", "/post/");
|
||||||
cy.url().should('contain', path)
|
cy.url().should("contain", path);
|
||||||
})
|
});
|
||||||
|
|
||||||
When('I start to write a new post with the title {string} beginning with:', (title, intro) => {
|
When(
|
||||||
cy.get('.post-add-button').click()
|
"I start to write a new post with the title {string} beginning with:",
|
||||||
cy.get('input[name="title"]').type(title)
|
(title, intro) => {
|
||||||
cy.get('.ProseMirror').type(intro)
|
cy.get(".post-add-button").click();
|
||||||
})
|
cy.get('input[name="title"]').type(title);
|
||||||
|
cy.get(".ProseMirror").type(intro);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
When('mention {string} in the text', (mention) => {
|
When("mention {string} in the text", mention => {
|
||||||
cy.get('.ProseMirror').type(' @')
|
cy.get(".ProseMirror").type(" @");
|
||||||
cy.get('.suggestion-list__item').contains(mention).click()
|
cy.get(".suggestion-list__item")
|
||||||
cy.debug()
|
.contains(mention)
|
||||||
})
|
.click();
|
||||||
|
cy.debug();
|
||||||
|
});
|
||||||
|
|
||||||
Then('the notification gets marked as read', () => {
|
Then("the notification gets marked as read", () => {
|
||||||
cy.get('.notification').first().should('have.class', 'read')
|
cy.get(".notification")
|
||||||
})
|
.first()
|
||||||
|
.should("have.class", "read");
|
||||||
|
});
|
||||||
|
|
||||||
Then('there are no notifications in the top menu', () => {
|
Then("there are no notifications in the top menu", () => {
|
||||||
cy.get('.notifications-menu').should('contain', '0')
|
cy.get(".notifications-menu").should("contain", "0");
|
||||||
})
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
|
# - nitro-mailserver.human-connection.org
|
||||||
- nitro-staging.human-connection.org
|
- nitro-staging.human-connection.org
|
||||||
secretName: tls
|
secretName: tls
|
||||||
rules:
|
rules:
|
||||||
@ -20,3 +21,10 @@ spec:
|
|||||||
backend:
|
backend:
|
||||||
serviceName: nitro-web
|
serviceName: nitro-web
|
||||||
servicePort: 3000
|
servicePort: 3000
|
||||||
|
# - host: nitro-mailserver.human-connection.org
|
||||||
|
# http:
|
||||||
|
# paths:
|
||||||
|
# - path: /
|
||||||
|
# backend:
|
||||||
|
# serviceName: mailserver
|
||||||
|
# servicePort: 80
|
||||||
|
|||||||
18
deployment/human-connection/mailserver/README.md
Normal file
18
deployment/human-connection/mailserver/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Development Mail Server
|
||||||
|
|
||||||
|
You can deploy a fake smtp server which captures all send mails and displays
|
||||||
|
them in a web interface. The [sample configuration](../templates/configmap.template.yml)
|
||||||
|
is assuming such a dummy server in the `SMTP_HOST` configuration and points to
|
||||||
|
a cluster-internal SMTP server.
|
||||||
|
|
||||||
|
To deploy the SMTP server just uncomment the relevant code in the
|
||||||
|
[ingress server configuration](../../https/templates/ingress.template.yaml) and
|
||||||
|
run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# in folder deployment/human-connection
|
||||||
|
kubectl apply -f mailserver/
|
||||||
|
```
|
||||||
|
|
||||||
|
You might need to refresh the TLS secret to enable HTTPS on the publicly
|
||||||
|
available web interface.
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mailserver
|
||||||
|
namespace: human-connection
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
minReadySeconds: 15
|
||||||
|
progressDeadlineSeconds: 60
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
human-connection.org/selector: deployment-human-connection-mailserver
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
human-connection.org/selector: deployment-human-connection-mailserver
|
||||||
|
name: "mailserver"
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mailserver
|
||||||
|
image: djfarrelly/maildev
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
- containerPort: 25
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: configmap
|
||||||
|
- secretRef:
|
||||||
|
name: human-connection
|
||||||
|
restartPolicy: Always
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
status: {}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: mailserver
|
||||||
|
namespace: human-connection
|
||||||
|
labels:
|
||||||
|
human-connection.org/selector: deployment-human-connection-mailserver
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
- name: smtp
|
||||||
|
port: 25
|
||||||
|
targetPort: 25
|
||||||
|
selector:
|
||||||
|
human-connection.org/selector: deployment-human-connection-mailserver
|
||||||
@ -2,6 +2,10 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
data:
|
data:
|
||||||
|
SMTP_HOST: "mailserver.human-connection"
|
||||||
|
SMTP_PORT: "25"
|
||||||
|
SMTP_USERNAME: ""
|
||||||
|
SMTP_PASSWORD: ""
|
||||||
GRAPHQL_PORT: "4000"
|
GRAPHQL_PORT: "4000"
|
||||||
GRAPHQL_URI: "http://nitro-backend.human-connection:4000"
|
GRAPHQL_URI: "http://nitro-backend.human-connection:4000"
|
||||||
MOCKS: "false"
|
MOCKS: "false"
|
||||||
|
|||||||
@ -5,6 +5,11 @@ data:
|
|||||||
MONGODB_PASSWORD: "TU9OR09EQl9QQVNTV09SRA=="
|
MONGODB_PASSWORD: "TU9OR09EQl9QQVNTV09SRA=="
|
||||||
PRIVATE_KEY_PASSPHRASE: "YTdkc2Y3OHNhZGc4N2FkODdzZmFnc2FkZzc4"
|
PRIVATE_KEY_PASSPHRASE: "YTdkc2Y3OHNhZGc4N2FkODdzZmFnc2FkZzc4"
|
||||||
MAPBOX_TOKEN: "cGsuZXlKMUlqb2lhSFZ0WVc0dFkyOXVibVZqZEdsdmJpSXNJbUVpT2lKamFqbDBjbkJ1Ykdvd2VUVmxNM1Z3WjJsek5UTnVkM1p0SW4wLktaOEtLOWw3MG9talhiRWtrYkhHc1EK"
|
MAPBOX_TOKEN: "cGsuZXlKMUlqb2lhSFZ0WVc0dFkyOXVibVZqZEdsdmJpSXNJbUVpT2lKamFqbDBjbkJ1Ykdvd2VUVmxNM1Z3WjJsek5UTnVkM1p0SW4wLktaOEtLOWw3MG9talhiRWtrYkhHc1EK"
|
||||||
|
SMTP_HOST:
|
||||||
|
SMTP_PORT: 587
|
||||||
|
SMTP_USERNAME:
|
||||||
|
SMTP_PASSWORD:
|
||||||
|
SMTP_IGNORE_TLS:
|
||||||
metadata:
|
metadata:
|
||||||
name: human-connection
|
name: human-connection
|
||||||
namespace: human-connection
|
namespace: human-connection
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
# SSH Access
|
||||||
|
# SSH_USERNAME='username'
|
||||||
|
# SSH_HOST='example.org'
|
||||||
|
|
||||||
|
# UPLOADS_DIRECTORY=/var/www/api/uploads
|
||||||
|
OUTPUT_DIRECTORY='/uploads/'
|
||||||
@ -1,6 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# import .env config
|
||||||
|
set -o allexport
|
||||||
|
source $(dirname "$0")/.env
|
||||||
|
set +o allexport
|
||||||
|
|
||||||
for var in "SSH_USERNAME" "SSH_HOST" "UPLOADS_DIRECTORY"
|
for var in "SSH_USERNAME" "SSH_HOST" "UPLOADS_DIRECTORY"
|
||||||
do
|
do
|
||||||
if [[ -z "${!var}" ]]; then
|
if [[ -z "${!var}" ]]; then
|
||||||
@ -9,4 +14,4 @@ do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/ /uploads/
|
rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/ ${OUTPUT_DIRECTORY}
|
||||||
|
|||||||
@ -8,11 +8,18 @@ set +o allexport
|
|||||||
|
|
||||||
# Export collection function defintion
|
# Export collection function defintion
|
||||||
function export_collection () {
|
function export_collection () {
|
||||||
"${EXPORT_MONGOEXPORT_BIN}" --db ${MONGODB_DATABASE} --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --collection $1 --collection $1 --out "${EXPORT_PATH}$1.json"
|
"${EXPORT_MONGOEXPORT_BIN}" --db ${MONGODB_DATABASE} --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --collection $1 --out "${EXPORT_PATH}$1.json"
|
||||||
mkdir -p ${EXPORT_PATH}splits/$1/
|
mkdir -p ${EXPORT_PATH}splits/$1/
|
||||||
split -l ${MONGO_EXPORT_SPLIT_SIZE} -a 3 ${EXPORT_PATH}$1.json ${EXPORT_PATH}splits/$1/
|
split -l ${MONGO_EXPORT_SPLIT_SIZE} -a 3 ${EXPORT_PATH}$1.json ${EXPORT_PATH}splits/$1/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Export collection with query function defintion
|
||||||
|
function export_collection_query () {
|
||||||
|
"${EXPORT_MONGOEXPORT_BIN}" --db ${MONGODB_DATABASE} --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --collection $1 --out "${EXPORT_PATH}$1_$3.json" --query "$2"
|
||||||
|
mkdir -p ${EXPORT_PATH}splits/$1_$3/
|
||||||
|
split -l ${MONGO_EXPORT_SPLIT_SIZE} -a 3 ${EXPORT_PATH}$1_$3.json ${EXPORT_PATH}splits/$1_$3/
|
||||||
|
}
|
||||||
|
|
||||||
# Delete old export & ensure directory
|
# Delete old export & ensure directory
|
||||||
rm -rf ${EXPORT_PATH}*
|
rm -rf ${EXPORT_PATH}*
|
||||||
mkdir -p ${EXPORT_PATH}
|
mkdir -p ${EXPORT_PATH}
|
||||||
@ -24,9 +31,12 @@ ssh -4 -M -S my-ctrl-socket -fnNT -L 27018:localhost:27017 -l ${SSH_USERNAME} ${
|
|||||||
export_collection "badges"
|
export_collection "badges"
|
||||||
export_collection "categories"
|
export_collection "categories"
|
||||||
export_collection "comments"
|
export_collection "comments"
|
||||||
export_collection "contributions"
|
export_collection_query "contributions" "{'type': 'DELETED'}" "DELETED"
|
||||||
|
export_collection_query "contributions" "{'type': 'post'}" "post"
|
||||||
|
export_collection_query "contributions" "{'type': 'cando'}" "cando"
|
||||||
export_collection "emotions"
|
export_collection "emotions"
|
||||||
export_collection "follows"
|
export_collection_query "follows" "{'foreignService': 'organizations'}" "organizations"
|
||||||
|
export_collection_query "follows" "{'foreignService': 'users'}" "users"
|
||||||
export_collection "invites"
|
export_collection "invites"
|
||||||
export_collection "notifications"
|
export_collection "notifications"
|
||||||
export_collection "organizations"
|
export_collection "organizations"
|
||||||
|
|||||||
@ -45,7 +45,7 @@ MERGE(b:Badge {id: badge._id["$oid"]})
|
|||||||
ON CREATE SET
|
ON CREATE SET
|
||||||
b.key = badge.key,
|
b.key = badge.key,
|
||||||
b.type = badge.type,
|
b.type = badge.type,
|
||||||
b.icon = badge.image.path,
|
b.icon = replace(badge.image.path, 'https://api-alpha.human-connection.org', ''),
|
||||||
b.status = badge.status,
|
b.status = badge.status,
|
||||||
b.createdAt = badge.createdAt.`$date`,
|
b.createdAt = badge.createdAt.`$date`,
|
||||||
b.updatedAt = badge.updatedAt.`$date`
|
b.updatedAt = badge.updatedAt.`$date`
|
||||||
@ -28,7 +28,7 @@
|
|||||||
[?] unique: true, // Unique value is not enforced in Nitro?
|
[?] unique: true, // Unique value is not enforced in Nitro?
|
||||||
[-] index: true
|
[-] index: true
|
||||||
},
|
},
|
||||||
[ ] type: {
|
[ ] type: { // db.getCollection('contributions').distinct('type') -> 'DELETED', 'cando', 'post'
|
||||||
[ ] type: String,
|
[ ] type: String,
|
||||||
[ ] required: true,
|
[ ] required: true,
|
||||||
[-] index: true
|
[-] index: true
|
||||||
@ -50,7 +50,7 @@
|
|||||||
[?] required: true // Not required in Nitro
|
[?] required: true // Not required in Nitro
|
||||||
},
|
},
|
||||||
[ ] hasMore: { type: Boolean },
|
[ ] hasMore: { type: Boolean },
|
||||||
[?] teaserImg: { type: String }, // Path is incorrect in Nitro
|
[X] teaserImg: { type: String },
|
||||||
[ ] language: {
|
[ ] language: {
|
||||||
[ ] type: String,
|
[ ] type: String,
|
||||||
[ ] required: true,
|
[ ] required: true,
|
||||||
@ -109,8 +109,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[?] deleted: {
|
[?] deleted: { // THis field is not always present in the alpha-data
|
||||||
[X] type: Boolean,
|
[?] type: Boolean,
|
||||||
[ ] default: false, // Default value is missing in Nitro
|
[ ] default: false, // Default value is missing in Nitro
|
||||||
[-] index: true
|
[-] index: true
|
||||||
},
|
},
|
||||||
@ -131,13 +131,13 @@ MERGE (p:Post {id: post._id["$oid"]})
|
|||||||
ON CREATE SET
|
ON CREATE SET
|
||||||
p.title = post.title,
|
p.title = post.title,
|
||||||
p.slug = post.slug,
|
p.slug = post.slug,
|
||||||
p.image = post.teaserImg,
|
p.image = replace(post.teaserImg, 'https://api-alpha.human-connection.org', ''),
|
||||||
p.content = post.content,
|
p.content = post.content,
|
||||||
p.contentExcerpt = post.contentExcerpt,
|
p.contentExcerpt = post.contentExcerpt,
|
||||||
p.visibility = toLower(post.visibility),
|
p.visibility = toLower(post.visibility),
|
||||||
p.createdAt = post.createdAt.`$date`,
|
p.createdAt = post.createdAt.`$date`,
|
||||||
p.updatedAt = post.updatedAt.`$date`,
|
p.updatedAt = post.updatedAt.`$date`,
|
||||||
p.deleted = post.deleted,
|
p.deleted = COALESCE(post.deleted,false),
|
||||||
p.disabled = NOT post.isEnabled
|
p.disabled = NOT post.isEnabled
|
||||||
WITH p, post
|
WITH p, post
|
||||||
MATCH (u:User {id: post.userId})
|
MATCH (u:User {id: post.userId})
|
||||||
@ -9,10 +9,10 @@ set +o allexport
|
|||||||
# Delete collection function defintion
|
# Delete collection function defintion
|
||||||
function delete_collection () {
|
function delete_collection () {
|
||||||
# Delete from Database
|
# Delete from Database
|
||||||
echo "Delete $1"
|
echo "Delete $2"
|
||||||
"${IMPORT_CYPHERSHELL_BIN}" < $(dirname "$0")/$1_delete.cql > /dev/null
|
"${IMPORT_CYPHERSHELL_BIN}" < $(dirname "$0")/$1/delete.cql > /dev/null
|
||||||
# Delete index file
|
# Delete index file
|
||||||
rm -f "${IMPORT_PATH}splits/$1.index"
|
rm -f "${IMPORT_PATH}splits/$2.index"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Import collection function defintion
|
# Import collection function defintion
|
||||||
@ -34,7 +34,7 @@ function import_collection () {
|
|||||||
# calculate the path of the chunk
|
# calculate the path of the chunk
|
||||||
export IMPORT_CHUNK_PATH_CQL_FILE="${IMPORT_CHUNK_PATH_CQL}$1/${CHUNK_FILE_NAME}"
|
export IMPORT_CHUNK_PATH_CQL_FILE="${IMPORT_CHUNK_PATH_CQL}$1/${CHUNK_FILE_NAME}"
|
||||||
# load the neo4j command and replace file variable with actual path
|
# load the neo4j command and replace file variable with actual path
|
||||||
NEO4J_COMMAND="$(envsubst '${IMPORT_CHUNK_PATH_CQL_FILE}' < $(dirname "$0")/$1.cql)"
|
NEO4J_COMMAND="$(envsubst '${IMPORT_CHUNK_PATH_CQL_FILE}' < $(dirname "$0")/$2)"
|
||||||
# run the import of the chunk
|
# run the import of the chunk
|
||||||
echo "Import $1 ${CHUNK_FILE_NAME} (${chunk})"
|
echo "Import $1 ${CHUNK_FILE_NAME} (${chunk})"
|
||||||
echo "${NEO4J_COMMAND}" | "${IMPORT_CYPHERSHELL_BIN}" > /dev/null
|
echo "${NEO4J_COMMAND}" | "${IMPORT_CYPHERSHELL_BIN}" > /dev/null
|
||||||
@ -52,13 +52,14 @@ SECONDS=0
|
|||||||
|
|
||||||
# Delete all Neo4J Database content
|
# Delete all Neo4J Database content
|
||||||
echo "Deleting Database Contents"
|
echo "Deleting Database Contents"
|
||||||
delete_collection "badges"
|
delete_collection "badges" "badges"
|
||||||
delete_collection "categories"
|
delete_collection "categories" "categories"
|
||||||
delete_collection "users"
|
delete_collection "users" "users"
|
||||||
delete_collection "follows"
|
delete_collection "follows" "follows_users"
|
||||||
delete_collection "contributions"
|
delete_collection "contributions" "contributions_post"
|
||||||
delete_collection "shouts"
|
delete_collection "contributions" "contributions_cando"
|
||||||
delete_collection "comments"
|
delete_collection "shouts" "shouts"
|
||||||
|
delete_collection "comments" "comments"
|
||||||
|
|
||||||
#delete_collection "emotions"
|
#delete_collection "emotions"
|
||||||
#delete_collection "invites"
|
#delete_collection "invites"
|
||||||
@ -75,26 +76,33 @@ echo "DONE"
|
|||||||
|
|
||||||
# Import Data
|
# Import Data
|
||||||
echo "Start Importing Data"
|
echo "Start Importing Data"
|
||||||
import_collection "badges"
|
import_collection "badges" "badges/badges.cql"
|
||||||
import_collection "categories"
|
import_collection "categories" "categories/categories.cql"
|
||||||
import_collection "users"
|
import_collection "users" "users/users.cql"
|
||||||
import_collection "follows"
|
import_collection "follows_users" "follows/follows.cql"
|
||||||
import_collection "contributions"
|
#import_collection "follows_organizations" "follows/follows.cql"
|
||||||
import_collection "shouts"
|
import_collection "contributions_post" "contributions/contributions.cql"
|
||||||
import_collection "comments"
|
import_collection "contributions_cando" "contributions/contributions.cql"
|
||||||
|
#import_collection "contributions_DELETED" "contributions/contributions.cql"
|
||||||
|
import_collection "shouts" "shouts/shouts.cql"
|
||||||
|
import_collection "comments" "comments/comments.cql"
|
||||||
|
|
||||||
# import_collection "emotions"
|
# import_collection "emotions"
|
||||||
# import_collection "invites"
|
# import_collection "invites"
|
||||||
# import_collection "notifications"
|
# import_collection "notifications"
|
||||||
# import_collection "organizations"
|
# import_collection "organizations"
|
||||||
# import_collection "pages"
|
# import_collection "pages"
|
||||||
# import_collection "projects"
|
|
||||||
# import_collection "settings"
|
|
||||||
# import_collection "status"
|
|
||||||
# import_collection "systemnotifications"
|
# import_collection "systemnotifications"
|
||||||
# import_collection "userscandos"
|
# import_collection "userscandos"
|
||||||
# import_collection "usersettings"
|
# import_collection "usersettings"
|
||||||
|
|
||||||
|
# does only contain dummy data
|
||||||
|
# import_collection "projects"
|
||||||
|
|
||||||
|
# does only contain alpha specifc data
|
||||||
|
# import_collection "status
|
||||||
|
# import_collection "settings""
|
||||||
|
|
||||||
echo "DONE"
|
echo "DONE"
|
||||||
|
|
||||||
echo "Time elapsed: $SECONDS seconds"
|
echo "Time elapsed: $SECONDS seconds"
|
||||||
|
|||||||
@ -49,8 +49,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ ] timezone: { type: String },
|
[ ] timezone: { type: String },
|
||||||
[?] avatar: { type: String }, // Path is incorrect in Nitro
|
[X] avatar: { type: String },
|
||||||
[?] coverImg: { type: String }, // Path is incorrect in Nitro, was not modeled in latest Nitro - do we want this?
|
[X] coverImg: { type: String },
|
||||||
[ ] doiToken: { type: String },
|
[ ] doiToken: { type: String },
|
||||||
[ ] confirmedAt: { type: Date },
|
[ ] confirmedAt: { type: Date },
|
||||||
[?] badgeIds: [], // Verify this is working properly
|
[?] badgeIds: [], // Verify this is working properly
|
||||||
@ -102,8 +102,8 @@ u.name = user.name,
|
|||||||
u.slug = user.slug,
|
u.slug = user.slug,
|
||||||
u.email = user.email,
|
u.email = user.email,
|
||||||
u.password = user.password,
|
u.password = user.password,
|
||||||
u.avatar = user.avatar,
|
u.avatar = replace(user.avatar, 'https://api-alpha.human-connection.org', ''),
|
||||||
u.coverImg = user.coverImg,
|
u.coverImg = replace(user.coverImg, 'https://api-alpha.human-connection.org', ''),
|
||||||
u.wasInvited = user.wasInvited,
|
u.wasInvited = user.wasInvited,
|
||||||
u.wasSeeded = user.wasSeeded,
|
u.wasSeeded = user.wasSeeded,
|
||||||
u.role = toLower(user.role),
|
u.role = toLower(user.role),
|
||||||
@ -1,6 +1,12 @@
|
|||||||
version: "3.4"
|
version: "3.4"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
mailserver:
|
||||||
|
image: djfarrelly/maildev
|
||||||
|
ports:
|
||||||
|
- 1080:80
|
||||||
|
networks:
|
||||||
|
- hc-network
|
||||||
webapp:
|
webapp:
|
||||||
build:
|
build:
|
||||||
context: webapp
|
context: webapp
|
||||||
@ -20,6 +26,10 @@ services:
|
|||||||
- backend_node_modules:/nitro-backend/node_modules
|
- backend_node_modules:/nitro-backend/node_modules
|
||||||
- uploads:/nitro-backend/public/uploads
|
- uploads:/nitro-backend/public/uploads
|
||||||
command: yarn run dev
|
command: yarn run dev
|
||||||
|
environment:
|
||||||
|
- SMTP_HOST=mailserver
|
||||||
|
- SMTP_PORT=25
|
||||||
|
- SMTP_IGNORE_TLS=true
|
||||||
neo4j:
|
neo4j:
|
||||||
environment:
|
environment:
|
||||||
- NEO4J_AUTH=none
|
- NEO4J_AUTH=none
|
||||||
|
|||||||
@ -22,11 +22,11 @@
|
|||||||
"codecov": "^3.5.0",
|
"codecov": "^3.5.0",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"cypress": "^3.3.1",
|
"cypress": "^3.3.1",
|
||||||
"cypress-cucumber-preprocessor": "^1.11.2",
|
"cypress-cucumber-preprocessor": "^1.12.0",
|
||||||
"cypress-file-upload": "^3.1.2",
|
"cypress-file-upload": "^3.1.4",
|
||||||
"cypress-plugin-retries": "^1.2.2",
|
"cypress-plugin-retries": "^1.2.2",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"faker": "^4.1.0",
|
"faker": "Marak/faker.js#master",
|
||||||
"graphql-request": "^1.8.2",
|
"graphql-request": "^1.8.2",
|
||||||
"neo4j-driver": "^1.7.5",
|
"neo4j-driver": "^1.7.5",
|
||||||
"npm-run-all": "^4.1.5"
|
"npm-run-all": "^4.1.5"
|
||||||
|
|||||||
@ -10,7 +10,7 @@ $easeOut: cubic-bezier(0.19, 1, 0.22, 1);
|
|||||||
&::before {
|
&::before {
|
||||||
@include border-radius($border-radius-x-large);
|
@include border-radius($border-radius-x-large);
|
||||||
box-shadow: inset 0 0 0 5px $color-danger;
|
box-shadow: inset 0 0 0 5px $color-danger;
|
||||||
content: "";
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -102,10 +102,10 @@ hr {
|
|||||||
height: 1px !important;
|
height: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class$=menu-trigger] {
|
[class$='menu-trigger'] {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
[class$=menu-popover] {
|
[class$='menu-popover'] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
@ -145,10 +145,11 @@ hr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[class$="menu-popover"] {
|
[class$='menu-popover'] {
|
||||||
min-width: 130px;
|
min-width: 130px;
|
||||||
|
|
||||||
a, button {
|
a,
|
||||||
|
button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { mount, createLocalVue } from '@vue/test-utils'
|
import { mount, createLocalVue } from '@vue/test-utils'
|
||||||
import Styleguide from '@human-connection/styleguide'
|
import Styleguide from '@human-connection/styleguide'
|
||||||
import Avatar from './Avatar.vue'
|
import Avatar from './Avatar.vue'
|
||||||
|
import Filters from '~/plugins/vue-filters'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const localVue = createLocalVue()
|
||||||
localVue.use(Styleguide)
|
localVue.use(Styleguide)
|
||||||
|
localVue.use(Filters)
|
||||||
|
|
||||||
describe('Avatar.vue', () => {
|
describe('Avatar.vue', () => {
|
||||||
let propsData = {}
|
let propsData = {}
|
||||||
@ -51,7 +53,7 @@ describe('Avatar.vue', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
propsData = {
|
propsData = {
|
||||||
user: {
|
user: {
|
||||||
avatar: 'http://lorempixel.com/640/480/animals',
|
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -62,7 +64,7 @@ describe('Avatar.vue', () => {
|
|||||||
Wrapper()
|
Wrapper()
|
||||||
.find('img')
|
.find('img')
|
||||||
.attributes('src'),
|
.attributes('src'),
|
||||||
).toBe('http://lorempixel.com/640/480/animals')
|
).toBe('https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-avatar :image="avatarUrl" :name="userName" class="avatar" :size="size" />
|
<ds-avatar
|
||||||
|
:image="user && user.avatar | proxyApiUrl"
|
||||||
|
:name="userName"
|
||||||
|
class="avatar"
|
||||||
|
:size="size"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -10,11 +15,6 @@ export default {
|
|||||||
size: { type: String, default: 'small' },
|
size: { type: String, default: 'small' },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
avatarUrl() {
|
|
||||||
const { avatar: imageSrc } = this.user || {}
|
|
||||||
if (!imageSrc) return imageSrc
|
|
||||||
return imageSrc.startsWith('/') ? imageSrc.replace('/', '/api/') : imageSrc
|
|
||||||
},
|
|
||||||
userName() {
|
userName() {
|
||||||
const { name } = this.user || {}
|
const { name } = this.user || {}
|
||||||
// The name is used to display the initials in case
|
// The name is used to display the initials in case
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="[badges.length === 2 && 'hc-badges-dual']" class="hc-badges">
|
<div :class="[badges.length === 2 && 'hc-badges-dual']" class="hc-badges">
|
||||||
<div v-for="badge in badges" :key="badge.key" class="hc-badge-container">
|
<div v-for="badge in badges" :key="badge.key" class="hc-badge-container">
|
||||||
<hc-image :title="badge.key" :image-props="{ src: badge.icon }" class="hc-badge" />
|
<img :title="badge.key" :src="badge.icon | proxyApiUrl" class="hc-badge" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import HcImage from './Image'
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
HcImage,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
badges: {
|
badges: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|||||||
@ -25,6 +25,9 @@ describe('Comment.vue', () => {
|
|||||||
success: jest.fn(),
|
success: jest.fn(),
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
},
|
},
|
||||||
|
$filters: {
|
||||||
|
truncate: a => a,
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: jest.fn().mockResolvedValue(),
|
mutate: jest.fn().mockResolvedValue(),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,12 +9,13 @@
|
|||||||
<ds-space margin-bottom="x-small">
|
<ds-space margin-bottom="x-small">
|
||||||
<hc-user :user="author" :date-time="comment.createdAt"/>
|
<hc-user :user="author" :date-time="comment.createdAt"/>
|
||||||
</ds-space>
|
</ds-space>
|
||||||
|
<!-- Content Menu (can open Modals) -->
|
||||||
<no-ssr>
|
<no-ssr>
|
||||||
<content-menu
|
<content-menu
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
resource-type="comment"
|
resource-type="comment"
|
||||||
:resource="comment"
|
:resource="comment"
|
||||||
:callbacks="{ confirm: deleteCommentCallback, cancel: null }"
|
:modalsData="menuModalsData"
|
||||||
style="float-right"
|
style="float-right"
|
||||||
:is-owner="isAuthor(author.id)"
|
:is-owner="isAuthor(author.id)"
|
||||||
v-on:showEditCommentMenu="editCommentMenu"
|
v-on:showEditCommentMenu="editCommentMenu"
|
||||||
@ -75,6 +76,30 @@ export default {
|
|||||||
if (this.deleted) return {}
|
if (this.deleted) return {}
|
||||||
return this.comment.author || {}
|
return this.comment.author || {}
|
||||||
},
|
},
|
||||||
|
menuModalsData() {
|
||||||
|
return {
|
||||||
|
delete: {
|
||||||
|
titleIdent: 'delete.comment.title',
|
||||||
|
messageIdent: 'delete.comment.message',
|
||||||
|
messageParams: {
|
||||||
|
name: this.$filters.truncate(this.comment.contentExcerpt, 30),
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
danger: true,
|
||||||
|
icon: 'trash',
|
||||||
|
textIdent: 'delete.submit',
|
||||||
|
callback: this.deleteCommentCallback,
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
icon: 'close',
|
||||||
|
textIdent: 'delete.cancel',
|
||||||
|
callback: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isAuthor(id) {
|
isAuthor(id) {
|
||||||
|
|||||||
@ -43,7 +43,13 @@ export default {
|
|||||||
return value.match(/(contribution|comment|organization|user)/)
|
return value.match(/(contribution|comment|organization|user)/)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
callbacks: { type: Object, required: true },
|
modalsData: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
// default: () => {
|
||||||
|
// return {}
|
||||||
|
// },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
routes() {
|
routes() {
|
||||||
@ -146,7 +152,7 @@ export default {
|
|||||||
data: {
|
data: {
|
||||||
type: this.resourceType,
|
type: this.resourceType,
|
||||||
resource: this.resource,
|
resource: this.resource,
|
||||||
callbacks: this.callbacks,
|
modalsData: this.modalsData,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
213
webapp/components/ContributionForm/ContributionForm.spec.js
Normal file
213
webapp/components/ContributionForm/ContributionForm.spec.js
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||||
|
import ContributionForm from './index.vue'
|
||||||
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import PostMutations from '~/graphql/PostMutations.js'
|
||||||
|
|
||||||
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
|
localVue.use(Vuex)
|
||||||
|
localVue.use(Styleguide)
|
||||||
|
|
||||||
|
config.stubs['no-ssr'] = '<span><slot /></span>'
|
||||||
|
|
||||||
|
describe('ContributionForm.vue', () => {
|
||||||
|
let wrapper
|
||||||
|
let postTitleInput
|
||||||
|
let expectedParams
|
||||||
|
let deutschOption
|
||||||
|
let cancelBtn
|
||||||
|
let mocks
|
||||||
|
let propsData
|
||||||
|
const postTitle = 'this is a title for a post'
|
||||||
|
const postContent = 'this is a post'
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks = {
|
||||||
|
$t: jest.fn(),
|
||||||
|
$apollo: {
|
||||||
|
mutate: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
CreatePost: {
|
||||||
|
title: postTitle,
|
||||||
|
slug: 'this-is-a-title-for-a-post',
|
||||||
|
content: postContent,
|
||||||
|
contentExcerpt: postContent,
|
||||||
|
language: 'en',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.mockRejectedValue({ message: 'Not Authorised!' }),
|
||||||
|
},
|
||||||
|
$toast: {
|
||||||
|
error: jest.fn(),
|
||||||
|
success: jest.fn(),
|
||||||
|
},
|
||||||
|
$i18n: {
|
||||||
|
locale: () => 'en',
|
||||||
|
},
|
||||||
|
$router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
push: jest.fn(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
propsData = {}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
const getters = {
|
||||||
|
'editor/placeholder': () => {
|
||||||
|
return 'some cool placeholder'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
getters,
|
||||||
|
})
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(ContributionForm, { mocks, localVue, store, propsData })
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
wrapper.setData({ form: { languageOptions: [{ label: 'Deutsch', value: 'de' }] } })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('CreatePost', () => {
|
||||||
|
describe('language placeholder', () => {
|
||||||
|
it("displays the name that corresponds with the user's location code", () => {
|
||||||
|
expect(wrapper.find('.ds-select-placeholder').text()).toEqual('English')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('invalid form submission', () => {
|
||||||
|
it('title required for form submission', async () => {
|
||||||
|
postTitleInput = wrapper.find('.ds-input')
|
||||||
|
postTitleInput.setValue(postTitle)
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('content required for form submission', async () => {
|
||||||
|
wrapper.vm.updateEditorContent(postContent)
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('valid form submission', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
expectedParams = {
|
||||||
|
mutation: PostMutations().CreatePost,
|
||||||
|
variables: { title: postTitle, content: postContent, language: 'en', id: null },
|
||||||
|
}
|
||||||
|
postTitleInput = wrapper.find('.ds-input')
|
||||||
|
postTitleInput.setValue(postTitle)
|
||||||
|
wrapper.vm.updateEditorContent(postContent)
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('with title and content', () => {
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("sends a fallback language based on a user's locale", () => {
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports changing the language', async () => {
|
||||||
|
expectedParams.variables.language = 'de'
|
||||||
|
deutschOption = wrapper.findAll('li').at(0)
|
||||||
|
deutschOption.trigger('click')
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("pushes the user to the post's page", async () => {
|
||||||
|
expect(mocks.$router.push).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows a success toaster', () => {
|
||||||
|
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('cancel', () => {
|
||||||
|
it('calls $router.back() when cancel button clicked', () => {
|
||||||
|
cancelBtn = wrapper.find('.cancel-button')
|
||||||
|
cancelBtn.trigger('click')
|
||||||
|
expect(mocks.$router.back).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('handles errors', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
postTitleInput = wrapper.find('.ds-input')
|
||||||
|
postTitleInput.setValue(postTitle)
|
||||||
|
wrapper.vm.updateEditorContent(postContent)
|
||||||
|
// second submission causes mutation to reject
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
})
|
||||||
|
it('shows an error toaster when apollo mutation rejects', async () => {
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
await mocks.$apollo.mutate
|
||||||
|
expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UpdatePost', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
propsData = {
|
||||||
|
contribution: {
|
||||||
|
id: 'p1456',
|
||||||
|
slug: 'dies-ist-ein-post',
|
||||||
|
title: 'dies ist ein Post',
|
||||||
|
content: 'auf Deutsch geschrieben',
|
||||||
|
language: 'de',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets id equal to contribution id', () => {
|
||||||
|
expect(wrapper.vm.id).toEqual(propsData.contribution.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets slug equal to contribution slug', () => {
|
||||||
|
expect(wrapper.vm.slug).toEqual(propsData.contribution.slug)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets title equal to contribution title', () => {
|
||||||
|
expect(wrapper.vm.form.title).toEqual(propsData.contribution.title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets content equal to contribution content', () => {
|
||||||
|
expect(wrapper.vm.form.content).toEqual(propsData.contribution.content)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets language equal to contribution language', () => {
|
||||||
|
expect(wrapper.vm.form.language).toEqual({ value: propsData.contribution.language })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the UpdatePost apollo mutation', async () => {
|
||||||
|
expectedParams = {
|
||||||
|
mutation: PostMutations().UpdatePost,
|
||||||
|
variables: {
|
||||||
|
title: postTitle,
|
||||||
|
content: postContent,
|
||||||
|
language: propsData.contribution.language,
|
||||||
|
id: propsData.contribution.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
postTitleInput = wrapper.find('.ds-input')
|
||||||
|
postTitleInput.setValue(postTitle)
|
||||||
|
wrapper.vm.updateEditorContent(postContent)
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -6,8 +6,27 @@
|
|||||||
<no-ssr>
|
<no-ssr>
|
||||||
<hc-editor :users="users" :value="form.content" @input="updateEditorContent" />
|
<hc-editor :users="users" :value="form.content" @input="updateEditorContent" />
|
||||||
</no-ssr>
|
</no-ssr>
|
||||||
|
<ds-space margin-bottom="xxx-large" />
|
||||||
|
<ds-flex class="contribution-form-footer">
|
||||||
|
<ds-flex-item :width="{ base: '10%', sm: '10%', md: '10%', lg: '15%' }" />
|
||||||
|
<ds-flex-item :width="{ base: '80%', sm: '30%', md: '30%', lg: '20%' }">
|
||||||
|
<ds-space margin-bottom="small" />
|
||||||
|
<ds-select
|
||||||
|
model="language"
|
||||||
|
:options="form.languageOptions"
|
||||||
|
icon="globe"
|
||||||
|
:placeholder="locale"
|
||||||
|
:label="$t('contribution.languageSelectLabel')"
|
||||||
|
/>
|
||||||
|
</ds-flex-item>
|
||||||
|
</ds-flex>
|
||||||
<div slot="footer" style="text-align: right">
|
<div slot="footer" style="text-align: right">
|
||||||
<ds-button :disabled="loading || disabled" ghost @click.prevent="$router.back()">
|
<ds-button
|
||||||
|
:disabled="loading || disabled"
|
||||||
|
ghost
|
||||||
|
class="cancel-button"
|
||||||
|
@click="$router.back()"
|
||||||
|
>
|
||||||
{{ $t('actions.cancel') }}
|
{{ $t('actions.cancel') }}
|
||||||
</ds-button>
|
</ds-button>
|
||||||
<ds-button
|
<ds-button
|
||||||
@ -28,6 +47,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import HcEditor from '~/components/Editor'
|
import HcEditor from '~/components/Editor'
|
||||||
|
import orderBy from 'lodash/orderBy'
|
||||||
|
import locales from '~/locales'
|
||||||
|
import PostMutations from '~/graphql/PostMutations.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -41,6 +63,8 @@ export default {
|
|||||||
form: {
|
form: {
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: '',
|
||||||
|
language: null,
|
||||||
|
languageOptions: [],
|
||||||
},
|
},
|
||||||
formSchema: {
|
formSchema: {
|
||||||
title: { required: true, min: 3, max: 64 },
|
title: { required: true, min: 3, max: 64 },
|
||||||
@ -64,26 +88,38 @@ export default {
|
|||||||
this.slug = contribution.slug
|
this.slug = contribution.slug
|
||||||
this.form.content = contribution.content
|
this.form.content = contribution.content
|
||||||
this.form.title = contribution.title
|
this.form.title = contribution.title
|
||||||
|
this.form.language = { value: contribution.language }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
locale() {
|
||||||
|
const locale =
|
||||||
|
this.contribution && this.contribution.language
|
||||||
|
? locales.find(loc => this.contribution.language === loc.code)
|
||||||
|
: locales.find(loc => this.$i18n.locale() === loc.code)
|
||||||
|
return locale.name
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.availableLocales()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit() {
|
submit() {
|
||||||
const postMutations = require('~/graphql/PostMutations.js').default(this)
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: this.id ? postMutations.UpdatePost : postMutations.CreatePost,
|
mutation: this.id ? PostMutations().UpdatePost : PostMutations().CreatePost,
|
||||||
variables: {
|
variables: {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
title: this.form.title,
|
title: this.form.title,
|
||||||
content: this.form.content,
|
content: this.form.content,
|
||||||
|
language: this.form.language ? this.form.language.value : this.$i18n.locale(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.$toast.success('Saved!')
|
this.$toast.success(this.$t('contribution.success'))
|
||||||
this.disabled = true
|
this.disabled = true
|
||||||
|
|
||||||
const result = res.data[this.id ? 'UpdatePost' : 'CreatePost']
|
const result = res.data[this.id ? 'UpdatePost' : 'CreatePost']
|
||||||
@ -103,6 +139,11 @@ export default {
|
|||||||
// this.form.content = value
|
// this.form.content = value
|
||||||
this.$refs.contributionForm.update('content', value)
|
this.$refs.contributionForm.update('content', value)
|
||||||
},
|
},
|
||||||
|
availableLocales() {
|
||||||
|
orderBy(locales, 'name').map(locale => {
|
||||||
|
this.form.languageOptions.push({ label: locale.name, value: locale.code })
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
User: {
|
User: {
|
||||||
@ -135,4 +176,8 @@ export default {
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contribution-form-footer {
|
||||||
|
border-top: $border-size-base solid $border-color-softest;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
183
webapp/components/DeleteData/DeleteData.spec.js
Normal file
183
webapp/components/DeleteData/DeleteData.spec.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import { mount, createLocalVue } from '@vue/test-utils'
|
||||||
|
import DeleteData from './DeleteData.vue'
|
||||||
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
|
localVue.use(Vuex)
|
||||||
|
localVue.use(Styleguide)
|
||||||
|
|
||||||
|
describe('DeleteData.vue', () => {
|
||||||
|
let mocks
|
||||||
|
let wrapper
|
||||||
|
let getters
|
||||||
|
let actions
|
||||||
|
let deleteAccountBtn
|
||||||
|
let enableDeletionInput
|
||||||
|
let enableContributionDeletionCheckbox
|
||||||
|
let enableCommentDeletionCheckbox
|
||||||
|
const deleteAccountName = 'Delete MyAccount'
|
||||||
|
const deleteContributionsMessage = 'Delete my 2 posts'
|
||||||
|
const deleteCommentsMessage = 'Delete my 3 comments'
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks = {
|
||||||
|
$t: jest.fn(),
|
||||||
|
$apollo: {
|
||||||
|
mutate: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
DeleteData: {
|
||||||
|
id: 'u343',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.mockRejectedValue({ message: 'Not authorised!' }),
|
||||||
|
},
|
||||||
|
$toast: {
|
||||||
|
error: jest.fn(),
|
||||||
|
success: jest.fn(),
|
||||||
|
},
|
||||||
|
$router: {
|
||||||
|
history: {
|
||||||
|
push: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
getters = {
|
||||||
|
'auth/user': () => {
|
||||||
|
return { id: 'u343', name: deleteAccountName, contributionsCount: 2, commentsCount: 3 }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actions = { 'auth/logout': jest.fn() }
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
const Wrapper = () => {
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
})
|
||||||
|
return mount(DeleteData, { mocks, localVue, store })
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('defaults to deleteContributions to false', () => {
|
||||||
|
expect(wrapper.vm.deleteContributions).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('defaults to deleteComments to false', () => {
|
||||||
|
expect(wrapper.vm.deleteComments).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('defaults to deleteEnabled to false', () => {
|
||||||
|
expect(wrapper.vm.deleteEnabled).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not call the delete user mutation if deleteEnabled is false', () => {
|
||||||
|
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||||
|
deleteAccountBtn.trigger('click')
|
||||||
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('calls the delete user mutation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||||
|
enableDeletionInput.setValue(deleteAccountName)
|
||||||
|
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('if deleteEnabled is true and only deletes user by default', () => {
|
||||||
|
deleteAccountBtn.trigger('click')
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
id: 'u343',
|
||||||
|
resource: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("deletes a user's posts if requested", () => {
|
||||||
|
mocks.$t.mockImplementation(() => deleteContributionsMessage)
|
||||||
|
enableContributionDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(0)
|
||||||
|
enableContributionDeletionCheckbox.trigger('click')
|
||||||
|
deleteAccountBtn.trigger('click')
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
id: 'u343',
|
||||||
|
resource: ['Post'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("deletes a user's comments if requested", () => {
|
||||||
|
mocks.$t.mockImplementation(() => deleteCommentsMessage)
|
||||||
|
enableCommentDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(1)
|
||||||
|
enableCommentDeletionCheckbox.trigger('click')
|
||||||
|
deleteAccountBtn.trigger('click')
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
id: 'u343',
|
||||||
|
resource: ['Comment'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("deletes a user's posts and comments if requested", () => {
|
||||||
|
mocks.$t.mockImplementation(() => deleteContributionsMessage)
|
||||||
|
enableContributionDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(0)
|
||||||
|
enableContributionDeletionCheckbox.trigger('click')
|
||||||
|
mocks.$t.mockImplementation(() => deleteCommentsMessage)
|
||||||
|
enableCommentDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(1)
|
||||||
|
enableCommentDeletionCheckbox.trigger('click')
|
||||||
|
deleteAccountBtn.trigger('click')
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
id: 'u343',
|
||||||
|
resource: ['Post', 'Comment'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows a success toaster after successful mutation', async () => {
|
||||||
|
await deleteAccountBtn.trigger('click')
|
||||||
|
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('redirect the user to the homepage', async () => {
|
||||||
|
await deleteAccountBtn.trigger('click')
|
||||||
|
expect(mocks.$router.history.push).toHaveBeenCalledWith('/')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('error handling', () => {
|
||||||
|
it('shows an error toaster when the mutation rejects', async () => {
|
||||||
|
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||||
|
enableDeletionInput.setValue(deleteAccountName)
|
||||||
|
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||||
|
await deleteAccountBtn.trigger('click')
|
||||||
|
// second submission causes mutation to reject
|
||||||
|
await deleteAccountBtn.trigger('click')
|
||||||
|
await mocks.$apollo.mutate
|
||||||
|
expect(mocks.$toast.error).toHaveBeenCalledWith('Not authorised!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
225
webapp/components/DeleteData/DeleteData.vue
Normal file
225
webapp/components/DeleteData/DeleteData.vue
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ds-card hover>
|
||||||
|
<ds-space />
|
||||||
|
<ds-container>
|
||||||
|
<ds-flex>
|
||||||
|
<ds-flex-item :width="{ base: '22%', sm: '12%', md: '12%', lg: '8%' }">
|
||||||
|
<ds-icon name="warning" size="xxx-large" class="delete-warning-icon" />
|
||||||
|
</ds-flex-item>
|
||||||
|
<ds-flex-item :width="{ base: '78%', sm: '88%', md: '88%', lg: '92%' }">
|
||||||
|
<ds-heading>{{ $t('settings.deleteUserAccount.name') }}</ds-heading>
|
||||||
|
</ds-flex-item>
|
||||||
|
<ds-space />
|
||||||
|
<ds-heading tag="h4">
|
||||||
|
{{ $t('settings.deleteUserAccount.accountDescription') }}
|
||||||
|
</ds-heading>
|
||||||
|
</ds-flex>
|
||||||
|
</ds-container>
|
||||||
|
<ds-space />
|
||||||
|
<ds-container>
|
||||||
|
<transition name="slide-up">
|
||||||
|
<div v-if="deleteEnabled">
|
||||||
|
<label v-if="currentUser.contributionsCount" class="checkbox-container">
|
||||||
|
<input type="checkbox" v-model="deleteContributions" />
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
{{
|
||||||
|
$t('settings.deleteUserAccount.contributionsCount', {
|
||||||
|
count: currentUser.contributionsCount,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
<ds-space margin-bottom="small" />
|
||||||
|
<label v-if="currentUser.commentsCount" class="checkbox-container">
|
||||||
|
<input type="checkbox" v-model="deleteComments" />
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
{{
|
||||||
|
$t('settings.deleteUserAccount.commentsCount', {
|
||||||
|
count: currentUser.commentsCount,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
<ds-space margin-bottom="small" />
|
||||||
|
<ds-section id="delete-user-account-warning">
|
||||||
|
<div v-html="$t('settings.deleteUserAccount.accountWarning')"></div>
|
||||||
|
</ds-section>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</ds-container>
|
||||||
|
<template slot="footer" class="delete-data-footer">
|
||||||
|
<ds-container>
|
||||||
|
<div
|
||||||
|
class="delete-input-label"
|
||||||
|
v-html="$t('settings.deleteUserAccount.pleaseConfirm', { confirm: currentUser.name })"
|
||||||
|
></div>
|
||||||
|
<ds-space margin-bottom="xx-small" />
|
||||||
|
<ds-flex :gutter="{ base: 'xx-small', md: 'small', lg: 'large' }">
|
||||||
|
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1.75 }">
|
||||||
|
<ds-input
|
||||||
|
v-model="enableDeletionValue"
|
||||||
|
@input="enableDeletion"
|
||||||
|
class="enable-deletion-input"
|
||||||
|
/>
|
||||||
|
</ds-flex-item>
|
||||||
|
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1 }">
|
||||||
|
<ds-button icon="trash" danger :disabled="!deleteEnabled" @click="handleSubmit">
|
||||||
|
{{ $t('settings.deleteUserAccount.name') }}
|
||||||
|
</ds-button>
|
||||||
|
</ds-flex-item>
|
||||||
|
</ds-flex>
|
||||||
|
</ds-container>
|
||||||
|
</template>
|
||||||
|
</ds-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters, mapActions } from 'vuex'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DeleteData',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
deleteContributions: false,
|
||||||
|
deleteComments: false,
|
||||||
|
deleteEnabled: false,
|
||||||
|
enableDeletionValue: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
currentUser: 'auth/user',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
logout: 'auth/logout',
|
||||||
|
}),
|
||||||
|
enableDeletion() {
|
||||||
|
if (this.enableDeletionValue === this.currentUser.name) {
|
||||||
|
this.deleteEnabled = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
let resourceArgs = []
|
||||||
|
if (this.deleteContributions) {
|
||||||
|
resourceArgs.push('Post')
|
||||||
|
}
|
||||||
|
if (this.deleteComments) {
|
||||||
|
resourceArgs.push('Comment')
|
||||||
|
}
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation($id: ID!, $resource: [String]) {
|
||||||
|
DeleteUser(id: $id, resource: $resource) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: { id: this.currentUser.id, resource: resourceArgs },
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$t('settings.deleteUserAccount.success'))
|
||||||
|
this.logout()
|
||||||
|
this.$router.history.push('/')
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.$toast.error(error.message)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.delete-warning-icon {
|
||||||
|
color: $color-danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 35px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: $font-size-large;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border: 2px solid $background-color-inverse-softer;
|
||||||
|
background-color: $background-color-base;
|
||||||
|
border-radius: $border-radius-x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container:hover input ~ .checkmark {
|
||||||
|
background-color: $background-color-softest;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input:checked ~ .checkmark {
|
||||||
|
background-color: $background-color-danger-active;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input:checked ~ .checkmark:after {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container .checkmark:after {
|
||||||
|
left: 6px;
|
||||||
|
top: 3px;
|
||||||
|
width: 5px;
|
||||||
|
height: 10px;
|
||||||
|
border: solid $background-color-base;
|
||||||
|
border-width: 0 $border-size-large $border-size-large 0;
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.enable-deletion-input input:focus {
|
||||||
|
border-color: $border-color-danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-input-label {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
b.is-danger {
|
||||||
|
color: $text-color-danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-data-footer {
|
||||||
|
border-top: $border-size-base solid $border-color-softest;
|
||||||
|
background-color: $background-color-danger-inverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#delete-user-account-warning {
|
||||||
|
background-color: $background-color-danger-inverse;
|
||||||
|
border-left: $border-size-x-large solid $background-color-danger-active;
|
||||||
|
color: $text-color-danger;
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
border-radius: $border-radius-x-large;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -12,9 +12,7 @@
|
|||||||
@{{ user.slug }}
|
@{{ user.slug }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-else class="suggestion-list__item is-empty">
|
<div v-else class="suggestion-list__item is-empty">No users found</div>
|
||||||
No users found
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<editor-menu-bubble :editor="editor">
|
<editor-menu-bubble :editor="editor">
|
||||||
@ -175,6 +173,7 @@ import {
|
|||||||
History,
|
History,
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
import Mention from './nodes/Mention.js'
|
import Mention from './nodes/Mention.js'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
let throttleInputEvent
|
let throttleInputEvent
|
||||||
|
|
||||||
@ -212,7 +211,7 @@ export default {
|
|||||||
new ListItem(),
|
new ListItem(),
|
||||||
new Placeholder({
|
new Placeholder({
|
||||||
emptyNodeClass: 'is-empty',
|
emptyNodeClass: 'is-empty',
|
||||||
emptyNodeText: this.$t('editor.placeholder'),
|
emptyNodeText: this.placeholder || this.$t('editor.placeholder'),
|
||||||
}),
|
}),
|
||||||
new History(),
|
new History(),
|
||||||
new Mention({
|
new Mention({
|
||||||
@ -297,6 +296,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters({ placeholder: 'editor/placeholder' }),
|
||||||
hasResults() {
|
hasResults() {
|
||||||
return this.filteredUsers.length
|
return this.filteredUsers.length
|
||||||
},
|
},
|
||||||
@ -316,19 +316,20 @@ export default {
|
|||||||
this.editor.setContent(content)
|
this.editor.setContent(content)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
placeholder: {
|
||||||
|
immediate: true,
|
||||||
|
handler: function(val) {
|
||||||
|
if (!val) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.editor.extensions.options.placeholder.emptyNodeText = val
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.$root.$on('changeLanguage', () => {
|
|
||||||
this.changePlaceHolderText()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.editor.destroy()
|
this.editor.destroy()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
changePlaceHolderText() {
|
|
||||||
this.editor.extensions.options.placeholder.emptyNodeText = this.$t('editor.placeholder')
|
|
||||||
},
|
|
||||||
// navigate to the previous item
|
// navigate to the previous item
|
||||||
// if it's the first item, navigate to the last one
|
// if it's the first item, navigate to the last one
|
||||||
upHandler() {
|
upHandler() {
|
||||||
|
|||||||
@ -1,31 +1,43 @@
|
|||||||
import { mount, createLocalVue } from '@vue/test-utils'
|
import { mount, createLocalVue } from '@vue/test-utils'
|
||||||
import Editor from './'
|
import Editor from './'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
import Styleguide from '@human-connection/styleguide'
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const localVue = createLocalVue()
|
||||||
|
localVue.use(Vuex)
|
||||||
localVue.use(Styleguide)
|
localVue.use(Styleguide)
|
||||||
|
|
||||||
describe('Editor.vue', () => {
|
describe('Editor.vue', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
let propsData
|
let propsData
|
||||||
let mocks
|
let mocks
|
||||||
|
let getters
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
propsData = {}
|
propsData = {}
|
||||||
mocks = {
|
mocks = {
|
||||||
$t: () => {},
|
$t: () => {},
|
||||||
}
|
}
|
||||||
|
getters = {
|
||||||
|
'editor/placeholder': () => {
|
||||||
|
return 'some cool placeholder'
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
let Wrapper = () => {
|
let Wrapper = () => {
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
getters,
|
||||||
|
})
|
||||||
return (wrapper = mount(Editor, {
|
return (wrapper = mount(Editor, {
|
||||||
mocks,
|
mocks,
|
||||||
propsData,
|
propsData,
|
||||||
localVue,
|
localVue,
|
||||||
sync: false,
|
sync: false,
|
||||||
stubs: { transition: false },
|
stubs: { transition: false },
|
||||||
|
store,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,5 +55,13 @@ describe('Editor.vue', () => {
|
|||||||
expect(wrapper.find('.ProseMirror').text()).toContain('I am a piece of text')
|
expect(wrapper.find('.ProseMirror').text()).toContain('I am a piece of text')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('uses the placeholder', () => {
|
||||||
|
it('from the store', () => {
|
||||||
|
expect(wrapper.vm.editor.extensions.options.placeholder.emptyNodeText).toEqual(
|
||||||
|
'some cool placeholder',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { mount, createLocalVue } from '@vue/test-utils'
|
import { mount, createLocalVue } from '@vue/test-utils'
|
||||||
import FilterMenu from './FilterMenu.vue'
|
import FilterMenu from './FilterMenu.vue'
|
||||||
import Styleguide from '@human-connection/styleguide'
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
import VTooltip from 'v-tooltip'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
localVue.use(Styleguide)
|
localVue.use(Styleguide)
|
||||||
|
localVue.use(VTooltip)
|
||||||
|
|
||||||
describe('FilterMenu.vue', () => {
|
describe('FilterMenu.vue', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
|
|||||||
@ -2,13 +2,16 @@
|
|||||||
<ds-card>
|
<ds-card>
|
||||||
<ds-flex>
|
<ds-flex>
|
||||||
<ds-flex-item class="filter-menu-title">
|
<ds-flex-item class="filter-menu-title">
|
||||||
<ds-heading size="h3">
|
<ds-heading size="h3">{{ $t('filter-menu.title') }}</ds-heading>
|
||||||
{{ $t('filter-menu.title') }}
|
|
||||||
</ds-heading>
|
|
||||||
</ds-flex-item>
|
</ds-flex-item>
|
||||||
<ds-flex-item>
|
<ds-flex-item>
|
||||||
<div class="filter-menu-buttons">
|
<div class="filter-menu-buttons">
|
||||||
<ds-button
|
<ds-button
|
||||||
|
v-tooltip="{
|
||||||
|
content: this.$t('contribution.filterFollow'),
|
||||||
|
placement: 'left',
|
||||||
|
delay: { show: 500 },
|
||||||
|
}"
|
||||||
name="filter-by-followed-authors-only"
|
name="filter-by-followed-authors-only"
|
||||||
icon="user-plus"
|
icon="user-plus"
|
||||||
:primary="!!filterAuthorIsFollowedById"
|
:primary="!!filterAuthorIsFollowedById"
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
<template>
|
|
||||||
<img v-bind="imageProps" :src="imageSrc" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
imageProps: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
imageSrc() {
|
|
||||||
const src = this.imageProps.src
|
|
||||||
return src.startsWith('/') ? src.replace('/', '/api/') : src
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import { shallowMount } from '@vue/test-utils'
|
|
||||||
import Image from '.'
|
|
||||||
|
|
||||||
describe('Image', () => {
|
|
||||||
let propsData = { imageProps: { class: 'hc-badge', src: '' } }
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return shallowMount(Image, { propsData })
|
|
||||||
}
|
|
||||||
|
|
||||||
it('renders', () => {
|
|
||||||
expect(Wrapper().is('img')).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('passes properties down to `img`', () => {
|
|
||||||
expect(Wrapper().classes()).toEqual(['hc-badge'])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given a relative `src`', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
propsData.imageProps.src = '/img/badges/fundraisingbox_de_airship.svg'
|
|
||||||
})
|
|
||||||
|
|
||||||
it('adds a prefix to load the image from the backend', () => {
|
|
||||||
expect(Wrapper().attributes('src')).toBe('/api/img/badges/fundraisingbox_de_airship.svg')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given an absolute `src`', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
propsData.imageProps.src = 'http://lorempixel.com/640/480/animals'
|
|
||||||
})
|
|
||||||
|
|
||||||
it('keeps the URL as is', () => {
|
|
||||||
// e.g. our seeds have absolute image URLs
|
|
||||||
expect(Wrapper().attributes('src')).toBe('http://lorempixel.com/640/480/animals')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
68
webapp/components/LocaleSwitch/LocaleSwitch.spec.js
Normal file
68
webapp/components/LocaleSwitch/LocaleSwitch.spec.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { mount, createLocalVue } from '@vue/test-utils'
|
||||||
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import VTooltip from 'v-tooltip'
|
||||||
|
import LocaleSwitch from './LocaleSwitch.vue'
|
||||||
|
import { mutations } from '~/store/editor'
|
||||||
|
|
||||||
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
|
localVue.use(Vuex)
|
||||||
|
localVue.use(Styleguide)
|
||||||
|
localVue.use(VTooltip)
|
||||||
|
|
||||||
|
describe('LocaleSwitch.vue', () => {
|
||||||
|
let wrapper
|
||||||
|
let mocks
|
||||||
|
let computed
|
||||||
|
let deutschLanguageItem
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks = {
|
||||||
|
$i18n: {
|
||||||
|
locale: () => 'de',
|
||||||
|
set: jest.fn(),
|
||||||
|
},
|
||||||
|
$t: jest.fn(),
|
||||||
|
setPlaceholderText: jest.fn(),
|
||||||
|
}
|
||||||
|
computed = {
|
||||||
|
current: () => {
|
||||||
|
return { code: 'en' }
|
||||||
|
},
|
||||||
|
routes: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'English',
|
||||||
|
path: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Deutsch',
|
||||||
|
path: 'de',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
mutations: {
|
||||||
|
'editor/SET_PLACEHOLDER_TEXT': mutations.SET_PLACEHOLDER_TEXT,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(LocaleSwitch, { mocks, localVue, store, computed })
|
||||||
|
}
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
wrapper.find('.locale-menu').trigger('click')
|
||||||
|
deutschLanguageItem = wrapper.findAll('li').at(1)
|
||||||
|
deutschLanguageItem.trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("changes a user's locale", () => {
|
||||||
|
expect(mocks.$i18n.set).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -36,6 +36,7 @@
|
|||||||
import Dropdown from '~/components/Dropdown'
|
import Dropdown from '~/components/Dropdown'
|
||||||
import find from 'lodash/find'
|
import find from 'lodash/find'
|
||||||
import orderBy from 'lodash/orderBy'
|
import orderBy from 'lodash/orderBy'
|
||||||
|
import { mapMutations } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -65,10 +66,11 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapMutations({ setPlaceholderText: 'editor/SET_PLACEHOLDER_TEXT' }),
|
||||||
changeLanguage(locale, toggleMenu) {
|
changeLanguage(locale, toggleMenu) {
|
||||||
this.$i18n.set(locale)
|
this.$i18n.set(locale)
|
||||||
toggleMenu()
|
toggleMenu()
|
||||||
this.$root.$emit('changeLanguage')
|
this.setPlaceholderText(this.$t('editor.placeholder'))
|
||||||
},
|
},
|
||||||
matcher(locale) {
|
matcher(locale) {
|
||||||
return locale === this.$i18n.locale()
|
return locale === this.$i18n.locale()
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||||
import Modal from './Modal.vue'
|
import Modal from './Modal.vue'
|
||||||
import DeleteModal from './Modal/DeleteModal.vue'
|
import ConfirmModal from './Modal/ConfirmModal.vue'
|
||||||
import DisableModal from './Modal/DisableModal.vue'
|
import DisableModal from './Modal/DisableModal.vue'
|
||||||
import ReportModal from './Modal/ReportModal.vue'
|
import ReportModal from './Modal/ReportModal.vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
@ -60,7 +60,7 @@ describe('Modal.vue', () => {
|
|||||||
|
|
||||||
it('initially empty', () => {
|
it('initially empty', () => {
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
expect(wrapper.contains(DeleteModal)).toBe(false)
|
expect(wrapper.contains(ConfirmModal)).toBe(false)
|
||||||
expect(wrapper.contains(DisableModal)).toBe(false)
|
expect(wrapper.contains(DisableModal)).toBe(false)
|
||||||
expect(wrapper.contains(ReportModal)).toBe(false)
|
expect(wrapper.contains(ReportModal)).toBe(false)
|
||||||
})
|
})
|
||||||
@ -75,10 +75,6 @@ describe('Modal.vue', () => {
|
|||||||
id: 'c456',
|
id: 'c456',
|
||||||
title: 'some title',
|
title: 'some title',
|
||||||
},
|
},
|
||||||
callbacks: {
|
|
||||||
confirm: null,
|
|
||||||
cancel: null,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
@ -93,10 +89,6 @@ describe('Modal.vue', () => {
|
|||||||
type: 'contribution',
|
type: 'contribution',
|
||||||
name: 'some title',
|
name: 'some title',
|
||||||
id: 'c456',
|
id: 'c456',
|
||||||
callbacks: {
|
|
||||||
confirm: null,
|
|
||||||
cancel: null,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -117,20 +109,12 @@ describe('Modal.vue', () => {
|
|||||||
name: 'Author name',
|
name: 'Author name',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
callbacks: {
|
|
||||||
confirm: null,
|
|
||||||
cancel: null,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||||
type: 'comment',
|
type: 'comment',
|
||||||
name: 'Author name',
|
name: 'Author name',
|
||||||
id: 'c456',
|
id: 'c456',
|
||||||
callbacks: {
|
|
||||||
confirm: null,
|
|
||||||
cancel: null,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -140,20 +124,12 @@ describe('Modal.vue', () => {
|
|||||||
resource: {
|
resource: {
|
||||||
id: 'c456',
|
id: 'c456',
|
||||||
},
|
},
|
||||||
callbacks: {
|
|
||||||
confirm: null,
|
|
||||||
cancel: null,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||||
type: 'comment',
|
type: 'comment',
|
||||||
name: '',
|
name: '',
|
||||||
id: 'c456',
|
id: 'c456',
|
||||||
callbacks: {
|
|
||||||
confirm: null,
|
|
||||||
cancel: null,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
:id="data.resource.id"
|
:id="data.resource.id"
|
||||||
:type="data.type"
|
:type="data.type"
|
||||||
:name="name"
|
:name="name"
|
||||||
:callbacks="data.callbacks"
|
|
||||||
@close="close"
|
@close="close"
|
||||||
/>
|
/>
|
||||||
<release-modal
|
<release-modal
|
||||||
@ -21,22 +20,21 @@
|
|||||||
:id="data.resource.id"
|
:id="data.resource.id"
|
||||||
:type="data.type"
|
:type="data.type"
|
||||||
:name="name"
|
:name="name"
|
||||||
:callbacks="data.callbacks"
|
|
||||||
@close="close"
|
@close="close"
|
||||||
/>
|
/>
|
||||||
<delete-modal
|
<confirm-modal
|
||||||
v-if="open === 'delete'"
|
v-if="open === 'delete'"
|
||||||
:id="data.resource.id"
|
:id="data.resource.id"
|
||||||
:type="data.type"
|
:type="data.type"
|
||||||
:name="name"
|
:name="name"
|
||||||
:callbacks="data.callbacks"
|
:modalData="data.modalsData.delete"
|
||||||
@close="close"
|
@close="close"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import DeleteModal from '~/components/Modal/DeleteModal'
|
import ConfirmModal from '~/components/Modal/ConfirmModal'
|
||||||
import DisableModal from '~/components/Modal/DisableModal'
|
import DisableModal from '~/components/Modal/DisableModal'
|
||||||
import ReleaseModal from '~/components/ReleaseModal/ReleaseModal.vue'
|
import ReleaseModal from '~/components/ReleaseModal/ReleaseModal.vue'
|
||||||
import ReportModal from '~/components/Modal/ReportModal'
|
import ReportModal from '~/components/Modal/ReportModal'
|
||||||
@ -48,7 +46,7 @@ export default {
|
|||||||
DisableModal,
|
DisableModal,
|
||||||
ReleaseModal,
|
ReleaseModal,
|
||||||
ReportModal,
|
ReportModal,
|
||||||
DeleteModal,
|
ConfirmModal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
@ -63,7 +61,7 @@ export default {
|
|||||||
switch (this.data.type) {
|
switch (this.data.type) {
|
||||||
case 'user':
|
case 'user':
|
||||||
return name
|
return name
|
||||||
case 'contribution':
|
case 'contribution': // REFACTORING: In ConfirmModal – Already replaced "title" by "this.menuModalsData.delete.messageParams".
|
||||||
return title
|
return title
|
||||||
case 'comment':
|
case 'comment':
|
||||||
return author && author.name
|
return author && author.name
|
||||||
|
|||||||
@ -1,28 +1,29 @@
|
|||||||
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'
|
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'
|
||||||
import DeleteModal from './DeleteModal.vue'
|
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import Styleguide from '@human-connection/styleguide'
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
import ConfirmModal from './ConfirmModal.vue'
|
||||||
|
import { postMenuModalsData } from '~/components/utils/PostHelpers'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
localVue.use(Vuex)
|
localVue.use(Vuex)
|
||||||
localVue.use(Styleguide)
|
localVue.use(Styleguide)
|
||||||
|
|
||||||
describe('DeleteModal.vue', () => {
|
describe('ConfirmModal.vue', () => {
|
||||||
let Wrapper
|
let Wrapper
|
||||||
let wrapper
|
let wrapper
|
||||||
let propsData
|
let propsData
|
||||||
let mocks
|
let mocks
|
||||||
|
const postName = 'It is a post'
|
||||||
|
const confirmCallback = jest.fn()
|
||||||
|
const cancelCallback = jest.fn()
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
propsData = {
|
propsData = {
|
||||||
type: 'contribution',
|
type: 'contribution',
|
||||||
id: 'p23',
|
id: 'p23',
|
||||||
name: 'It is a post',
|
name: postName,
|
||||||
callbacks: {
|
modalData: postMenuModalsData(postName, confirmCallback, cancelCallback).delete,
|
||||||
confirm: jest.fn(),
|
|
||||||
cancel: jest.fn(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
mocks = {
|
mocks = {
|
||||||
$t: jest.fn(),
|
$t: jest.fn(),
|
||||||
@ -32,9 +33,13 @@ describe('DeleteModal.vue', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
describe('shallowMount', () => {
|
describe('shallowMount', () => {
|
||||||
Wrapper = () => {
|
Wrapper = () => {
|
||||||
return shallowMount(DeleteModal, {
|
return shallowMount(ConfirmModal, {
|
||||||
propsData,
|
propsData,
|
||||||
mocks,
|
mocks,
|
||||||
localVue,
|
localVue,
|
||||||
@ -61,7 +66,7 @@ describe('DeleteModal.vue', () => {
|
|||||||
...propsData,
|
...propsData,
|
||||||
type: 'contribution',
|
type: 'contribution',
|
||||||
id: 'p23',
|
id: 'p23',
|
||||||
name: 'It is a post',
|
name: postName,
|
||||||
}
|
}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
@ -72,32 +77,7 @@ describe('DeleteModal.vue', () => {
|
|||||||
[
|
[
|
||||||
'delete.contribution.message',
|
'delete.contribution.message',
|
||||||
{
|
{
|
||||||
name: 'It is a post',
|
name: postName,
|
||||||
},
|
|
||||||
],
|
|
||||||
]
|
|
||||||
expect(calls).toEqual(expect.arrayContaining(expected))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given a comment', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
propsData = {
|
|
||||||
...propsData,
|
|
||||||
type: 'comment',
|
|
||||||
id: 'c4',
|
|
||||||
name: 'It is the user of the comment',
|
|
||||||
}
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('mentions comments user name', () => {
|
|
||||||
const calls = mocks.$t.mock.calls
|
|
||||||
const expected = [
|
|
||||||
[
|
|
||||||
'delete.comment.message',
|
|
||||||
{
|
|
||||||
name: 'It is the user of the comment',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
@ -108,7 +88,7 @@ describe('DeleteModal.vue', () => {
|
|||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
Wrapper = () => {
|
Wrapper = () => {
|
||||||
return mount(DeleteModal, {
|
return mount(ConfirmModal, {
|
||||||
propsData,
|
propsData,
|
||||||
mocks,
|
mocks,
|
||||||
localVue,
|
localVue,
|
||||||
@ -135,7 +115,7 @@ describe('DeleteModal.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does call the cancel callback', () => {
|
it('does call the cancel callback', () => {
|
||||||
expect(propsData.callbacks.cancel).toHaveBeenCalledTimes(1)
|
expect(cancelCallback).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits "close"', () => {
|
it('emits "close"', () => {
|
||||||
@ -161,10 +141,11 @@ describe('DeleteModal.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does call the confirm callback', () => {
|
it('does call the confirm callback', () => {
|
||||||
expect(propsData.callbacks.confirm).toHaveBeenCalledTimes(1)
|
expect(confirmCallback).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
it('emits close', () => {
|
|
||||||
expect(wrapper.emitted().close).toBeTruthy()
|
it('emits "close"', () => {
|
||||||
|
expect(wrapper.emitted().close).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('resets success', () => {
|
it('resets success', () => {
|
||||||
@ -10,10 +10,18 @@
|
|||||||
<p v-html="message" />
|
<p v-html="message" />
|
||||||
|
|
||||||
<template slot="footer">
|
<template slot="footer">
|
||||||
<ds-button class="cancel" icon="close" @click="cancel">{{ $t('delete.cancel') }}</ds-button>
|
<ds-button class="cancel" :icon="modalData.buttons.cancel.icon" @click="cancel">
|
||||||
|
{{ $t(modalData.buttons.cancel.textIdent) }}
|
||||||
|
</ds-button>
|
||||||
|
|
||||||
<ds-button danger class="confirm" icon="trash" :loading="loading" @click="confirm">
|
<ds-button
|
||||||
{{ $t('delete.submit') }}
|
:danger="modalData.buttons.confirm.danger"
|
||||||
|
class="confirm"
|
||||||
|
:icon="modalData.buttons.confirm.icon"
|
||||||
|
:loading="loading"
|
||||||
|
@click="confirm"
|
||||||
|
>
|
||||||
|
{{ $t(modalData.buttons.confirm.textIdent) }}
|
||||||
</ds-button>
|
</ds-button>
|
||||||
</template>
|
</template>
|
||||||
</ds-modal>
|
</ds-modal>
|
||||||
@ -23,14 +31,14 @@
|
|||||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DeleteModal',
|
name: 'ConfirmModal',
|
||||||
components: {
|
components: {
|
||||||
SweetalertIcon,
|
SweetalertIcon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
name: { type: String, default: '' },
|
name: { type: String, default: '' },
|
||||||
type: { type: String, required: true },
|
type: { type: String, required: true },
|
||||||
callbacks: { type: Object, required: true },
|
modalData: { type: Object, required: true },
|
||||||
id: { type: String, required: true },
|
id: { type: String, required: true },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -42,18 +50,15 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
title() {
|
||||||
return this.$t(`delete.${this.type}.title`)
|
return this.$t(this.modalData.titleIdent)
|
||||||
},
|
},
|
||||||
message() {
|
message() {
|
||||||
const name = this.$filters.truncate(this.name, 30)
|
return this.$t(this.modalData.messageIdent, this.modalData.messageParams)
|
||||||
return this.$t(`delete.${this.type}.message`, { name })
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async cancel() {
|
async cancel() {
|
||||||
if (this.callbacks.cancel) {
|
await this.modalData.buttons.cancel.callback()
|
||||||
await this.callbacks.cancel()
|
|
||||||
}
|
|
||||||
this.isOpen = false
|
this.isOpen = false
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
@ -62,9 +67,7 @@ export default {
|
|||||||
async confirm() {
|
async confirm() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
if (this.callbacks.confirm) {
|
await this.modalData.buttons.confirm.callback()
|
||||||
await this.callbacks.confirm()
|
|
||||||
}
|
|
||||||
this.success = true
|
this.success = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.isOpen = false
|
this.isOpen = false
|
||||||
@ -16,10 +16,6 @@ describe('DisableModal.vue', () => {
|
|||||||
type: 'contribution',
|
type: 'contribution',
|
||||||
id: 'c42',
|
id: 'c42',
|
||||||
name: 'blah',
|
name: 'blah',
|
||||||
callbacks: {
|
|
||||||
confirm: jest.fn(),
|
|
||||||
cancel: jest.fn(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
mocks = {
|
mocks = {
|
||||||
$filters: {
|
$filters: {
|
||||||
@ -33,8 +29,12 @@ describe('DisableModal.vue', () => {
|
|||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: jest
|
mutate: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValueOnce({ enable: 'u4711' })
|
.mockResolvedValueOnce({
|
||||||
.mockRejectedValue({ message: 'Not Authorised!' }),
|
enable: 'u4711',
|
||||||
|
})
|
||||||
|
.mockRejectedValue({
|
||||||
|
message: 'Not Authorised!',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
location: {
|
location: {
|
||||||
reload: jest.fn(),
|
reload: jest.fn(),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user