Merge branch 'master' of https://github.com/Human-Connection/Human-Connection into 1000_find_users_by_email_address

# Conflicts:
#	webapp/layouts/default.vue
This commit is contained in:
Wolfgang Huß 2019-07-16 08:44:42 +02:00
commit b5bd77b539
69 changed files with 1379 additions and 1173 deletions

View File

@ -95,7 +95,7 @@ coverage:
# - master
#flags:
# - integration
paths:
paths:
- backend/ # only include coverage in "backend/" folder
webapp: # declare a new status context "frontend"
against: parent
@ -127,7 +127,7 @@ coverage:
# - integration
# paths:
# - folder
#changes:
# default:
# against: parent
@ -150,20 +150,8 @@ coverage:
#ignore: # files and folders for processing
# - tests/*
#fixes:
# - "old_path::new_path"
comment:
# layout options are quite limited in v4.x - there have been way more options in v1.0
layout: reach, diff, flags, files # mostly old options: header, diff, uncovered, reach, files, tree, changes, sunburst, flags
behavior: new # default = posts once then update, posts new if delete
# once = post once then updates
# new = delete old, post new
# spammy = post new
require_changes: false # if true: only post the comment if coverage changes
require_base: no # [yes :: must have a base report to post]
require_head: no # [yes :: must have a head report to post]
branches: null # branch names that can post comment
flags: null
paths: null
comment: off

View File

@ -11,7 +11,6 @@ addons:
before_install:
- yarn global add wait-on
# Install Codecov
- yarn global add codecov
- yarn install
- cp cypress.env.template.json cypress.env.json
@ -40,7 +39,7 @@ script:
# Fullstack
- yarn run cypress:run
# Coverage
- codecov
- yarn run codecov
after_success:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh

View File

@ -34,7 +34,6 @@
"!**/src/**/?(*.)+(spec|test).js?(x)"
],
"coverageReporters": [
"text",
"lcov"
],
"testMatch": [
@ -48,7 +47,7 @@
"apollo-client": "~2.6.3",
"apollo-link-context": "~1.0.18",
"apollo-link-http": "~1.5.15",
"apollo-server": "~2.6.8",
"apollo-server": "~2.6.9",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
@ -68,20 +67,20 @@
"helmet": "~3.18.0",
"jsonwebtoken": "~8.5.1",
"linkifyjs": "~2.1.8",
"lodash": "~4.17.13",
"lodash": "~4.17.14",
"merge-graphql-schemas": "^1.5.8",
"neo4j-driver": "~1.7.4",
"neo4j-graphql-js": "^2.6.3",
"neode": "^0.2.16",
"node-fetch": "~2.6.0",
"nodemailer": "^6.2.1",
"nodemailer": "^6.3.0",
"npm-run-all": "~4.1.5",
"request": "~2.88.0",
"sanitize-html": "~1.20.1",
"slug": "~1.1.0",
"trunc-html": "~1.1.2",
"uuid": "~3.3.2",
"wait-on": "~3.2.0"
"wait-on": "~3.3.0"
},
"devDependencies": {
"@babel/cli": "~7.5.0",
@ -90,7 +89,7 @@
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.5.4",
"@babel/register": "~7.4.4",
"apollo-server-testing": "~2.6.8",
"apollo-server-testing": "~2.6.9",
"babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.2",
"babel-jest": "~24.8.0",

View File

@ -11,6 +11,7 @@ export const signupTemplate = options => {
} = options
const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('nonce', nonce)
actionUrl.searchParams.set('email', email)
return {
to: email,

View File

@ -146,6 +146,7 @@ const permissions = shield(
Comment: allow,
User: or(noEmailFilter, isAdmin),
isLoggedIn: allow,
Badge: allow,
},
Mutation: {
'*': deny,
@ -160,9 +161,6 @@ const permissions = shield(
UpdatePost: isAuthor,
DeletePost: isAuthor,
report: isAuthenticated,
CreateBadge: isAdmin,
UpdateBadge: isAdmin,
DeleteBadge: isAdmin,
CreateSocialMedia: isAuthenticated,
DeleteSocialMedia: isAuthenticated,
// AddBadgeRewarded: isAdmin,

View File

@ -1,6 +1,9 @@
import { UserInputError } from 'apollo-server'
import Joi from '@hapi/joi'
const COMMENT_MIN_LENGTH = 1
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
const validate = schema => {
return async (resolve, root, args, context, info) => {
const validation = schema.validate(args)
@ -15,8 +18,36 @@ const socialMediaSchema = Joi.object().keys({
.required(),
})
const validateCommentCreation = async (resolve, root, args, context, info) => {
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
const { postId } = args
if (!args.content || content.length < COMMENT_MIN_LENGTH) {
throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`)
}
const session = context.driver.session()
const postQueryRes = await session.run(
`
MATCH (post:Post {id: $postId})
RETURN post`,
{
postId,
},
)
const [post] = postQueryRes.records.map(record => {
return record.get('post')
})
if (!post) {
throw new UserInputError(NO_POST_ERR_MESSAGE)
} else {
return resolve(root, args, context, info)
}
}
export default {
Mutation: {
CreateSocialMedia: validate(socialMediaSchema),
CreateComment: validateCommentCreation,
},
}

View File

@ -0,0 +1,7 @@
module.exports = {
id: { type: 'string', primary: true, lowercase: true },
status: { type: 'string', valid: ['permanent', 'temporary'] },
type: { type: 'string', valid: ['role', 'crowdfunding'] },
icon: { type: 'string', required: true },
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
}

View File

@ -43,6 +43,12 @@ module.exports = {
target: 'User',
direction: 'in',
},
rewarded: {
type: 'relationship',
relationship: 'REWARDED',
target: 'Badge',
direction: 'in',
},
invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' },
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
updatedAt: {

View File

@ -1,6 +1,7 @@
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
// module that is not browser-compatible. Node's `fs` module is server-side only
export default {
Badge: require('./Badge.js'),
User: require('./User.js'),
InvitationCode: require('./InvitationCode.js'),
EmailAddress: require('./EmailAddress.js'),

View File

@ -12,11 +12,25 @@ export default applyScalars(
resolvers,
config: {
query: {
exclude: ['InvitationCode', 'EmailAddress', 'Notfication', 'Statistics', 'LoggedInUser'],
exclude: [
'Badge',
'InvitationCode',
'EmailAddress',
'Notfication',
'Statistics',
'LoggedInUser',
],
// add 'User' here as soon as possible
},
mutation: {
exclude: ['InvitationCode', 'EmailAddress', 'Notfication', 'Statistics', 'LoggedInUser'],
exclude: [
'Badge',
'InvitationCode',
'EmailAddress',
'Notfication',
'Statistics',
'LoggedInUser',
],
// add 'User' here as soon as possible
},
debug: CONFIG.DEBUG,

View File

@ -0,0 +1,9 @@
import { neo4jgraphql } from 'neo4j-graphql-js'
export default {
Query: {
Badge: async (object, args, context, resolveInfo) => {
return neo4jgraphql(object, args, context, resolveInfo, false)
},
},
}

View File

@ -1,200 +0,0 @@
import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories'
import { host, login } from '../../jest/helpers'
const factory = Factory()
let client
describe('badges', () => {
beforeEach(async () => {
await factory.create('User', {
email: 'user@example.org',
role: 'user',
password: '1234',
})
await factory.create('User', {
id: 'u2',
role: 'moderator',
email: 'moderator@example.org',
})
await factory.create('User', {
id: 'u3',
role: 'admin',
email: 'admin@example.org',
})
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('CreateBadge', () => {
const variables = {
id: 'b1',
key: 'indiegogo_en_racoon',
type: 'crowdfunding',
status: 'permanent',
icon: '/img/badges/indiegogo_en_racoon.svg',
}
const mutation = `
mutation(
$id: ID
$key: String!
$type: BadgeType!
$status: BadgeStatus!
$icon: String!
) {
CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) {
id,
key,
type,
status,
icon
}
}
`
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated admin', () => {
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('creates a badge', async () => {
const expected = {
CreateBadge: {
icon: '/img/badges/indiegogo_en_racoon.svg',
id: 'b1',
key: 'indiegogo_en_racoon',
status: 'permanent',
type: 'crowdfunding',
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
describe('authenticated moderator', () => {
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorization error', async () => {
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
})
describe('UpdateBadge', () => {
beforeEach(async () => {
await factory.authenticateAs({ email: 'admin@example.org', password: '1234' })
await factory.create('Badge', { id: 'b1' })
})
const variables = {
id: 'b1',
key: 'whatever',
}
const mutation = `
mutation($id: ID!, $key: String!) {
UpdateBadge(id: $id, key: $key) {
id
key
}
}
`
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated moderator', () => {
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorization error', async () => {
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated admin', () => {
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('updates a badge', async () => {
const expected = {
UpdateBadge: {
id: 'b1',
key: 'whatever',
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
})
describe('DeleteBadge', () => {
beforeEach(async () => {
await factory.authenticateAs({ email: 'admin@example.org', password: '1234' })
await factory.create('Badge', { id: 'b1' })
})
const variables = {
id: 'b1',
}
const mutation = `
mutation($id: ID!) {
DeleteBadge(id: $id) {
id
}
}
`
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated moderator', () => {
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorization error', async () => {
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated admin', () => {
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('deletes a badge', async () => {
const expected = {
DeleteBadge: {
id: 'b1',
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
})
})

View File

@ -1,40 +1,15 @@
import { neo4jgraphql } from 'neo4j-graphql-js'
import { UserInputError } from 'apollo-server'
const COMMENT_MIN_LENGTH = 1
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
export default {
Mutation: {
CreateComment: async (object, params, context, resolveInfo) => {
const content = params.content.replace(/<(?:.|\n)*?>/gm, '').trim()
const { postId } = params
// Adding relationship from comment to post by passing in the postId,
// but we do not want to create the comment with postId as an attribute
// because we use relationships for this. So, we are deleting it from params
// before comment creation.
delete params.postId
if (!params.content || content.length < COMMENT_MIN_LENGTH) {
throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`)
}
const session = context.driver.session()
const postQueryRes = await session.run(
`
MATCH (post:Post {id: $postId})
RETURN post`,
{
postId,
},
)
const [post] = postQueryRes.records.map(record => {
return record.get('post')
})
if (!post) {
throw new UserInputError(NO_POST_ERR_MESSAGE)
}
const commentWithoutRelationships = await neo4jgraphql(
object,
params,

View File

@ -18,10 +18,11 @@ const createPostWithCategoriesMutation = `
mutation($title: String!, $content: String!, $categoryIds: [ID]) {
CreatePost(title: $title, content: $content, categoryIds: $categoryIds) {
id
title
}
}
`
const creatPostWithCategoriesVariables = {
const createPostWithCategoriesVariables = {
title: postTitle,
content: postContent,
categoryIds: ['cat9', 'cat4', 'cat15'],
@ -35,6 +36,26 @@ const postQueryWithCategories = `
}
}
`
const createPostWithoutCategoriesVariables = {
title: 'This is a post without categories',
content: 'I should be able to filter it out',
categoryIds: null,
}
const postQueryFilteredByCategory = `
query Post($filter: _PostFilter) {
Post(filter: $filter) {
title
id
categories {
id
}
}
}
`
const postCategoriesFilterParam = { categories_some: { id_in: ['cat4'] } }
const postQueryFilteredByCategoryVariables = {
filter: postCategoriesFilterParam,
}
beforeEach(async () => {
userParams = {
name: 'TestUser',
@ -133,7 +154,8 @@ describe('CreatePost', () => {
})
describe('categories', () => {
it('allows a user to set the categories of the post', async () => {
let postWithCategories
beforeEach(async () => {
await Promise.all([
factory.create('Category', {
id: 'cat9',
@ -151,18 +173,39 @@ describe('CreatePost', () => {
icon: 'shopping-cart',
}),
])
const expected = [{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]
const postWithCategories = await client.request(
postWithCategories = await client.request(
createPostWithCategoriesMutation,
creatPostWithCategoriesVariables,
createPostWithCategoriesVariables,
)
})
it('allows a user to set the categories of the post', async () => {
const expected = [{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]
const postQueryWithCategoriesVariables = {
id: postWithCategories.CreatePost.id,
}
await expect(
client.request(postQueryWithCategories, postQueryWithCategoriesVariables),
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] })
})
it('allows a user to filter for posts by category', async () => {
await client.request(createPostWithCategoriesMutation, createPostWithoutCategoriesVariables)
const categoryIds = [{ id: 'cat4' }, { id: 'cat15' }, { id: 'cat9' }]
const expected = {
Post: [
{
title: postTitle,
id: postWithCategories.CreatePost.id,
categories: expect.arrayContaining(categoryIds),
},
],
}
await expect(
client.request(postQueryFilteredByCategory, postQueryFilteredByCategoryVariables),
).resolves.toEqual(expected)
})
})
})
})
@ -260,7 +303,7 @@ describe('UpdatePost', () => {
])
postWithCategories = await client.request(
createPostWithCategoriesMutation,
creatPostWithCategoriesVariables,
createPostWithCategoriesVariables,
)
updatePostVariables = {
id: postWithCategories.CreatePost.id,

View File

@ -1,47 +1,47 @@
import { neode } from '../../bootstrap/neo4j'
import { UserInputError } from 'apollo-server'
const instance = neode()
const getUserAndBadge = async ({ badgeKey, userId }) => {
let user = await instance.first('User', 'id', userId)
const badge = await instance.first('Badge', 'id', badgeKey)
if (!user) throw new UserInputError("Couldn't find a user with that id")
if (!badge) throw new UserInputError("Couldn't find a badge with that id")
return { user, badge }
}
export default {
Mutation: {
reward: async (_object, params, context, _resolveInfo) => {
const { fromBadgeId, toUserId } = params
const session = context.driver.session()
let transactionRes = await session.run(
`MATCH (badge:Badge {id: $badgeId}), (rewardedUser:User {id: $rewardedUserId})
MERGE (badge)-[:REWARDED]->(rewardedUser)
RETURN rewardedUser {.id}`,
{
badgeId: fromBadgeId,
rewardedUserId: toUserId,
},
)
const [rewardedUser] = transactionRes.records.map(record => {
return record.get('rewardedUser')
})
session.close()
return rewardedUser.id
const { user, badge } = await getUserAndBadge(params)
await user.relateTo(badge, 'rewarded')
return user.toJson()
},
unreward: async (_object, params, context, _resolveInfo) => {
const { fromBadgeId, toUserId } = params
const { badgeKey, userId } = params
const { user } = await getUserAndBadge(params)
const session = context.driver.session()
let transactionRes = await session.run(
`MATCH (badge:Badge {id: $badgeId})-[reward:REWARDED]->(rewardedUser:User {id: $rewardedUserId})
DELETE reward
RETURN rewardedUser {.id}`,
{
badgeId: fromBadgeId,
rewardedUserId: toUserId,
},
)
const [rewardedUser] = transactionRes.records.map(record => {
return record.get('rewardedUser')
})
session.close()
return rewardedUser.id
try {
// silly neode cannot remove relationships
await session.run(
`
MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
DELETE reward
RETURN rewardedUser
`,
{
badgeKey,
userId,
},
)
} catch (err) {
throw err
} finally {
session.close()
}
return user.toJson()
},
},
}

View File

@ -1,12 +1,20 @@
import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories'
import { host, login } from '../../jest/helpers'
import gql from 'graphql-tag'
const factory = Factory()
let user
let badge
describe('rewards', () => {
const variables = {
from: 'indiegogo_en_rhino',
to: 'u1',
}
beforeEach(async () => {
await factory.create('User', {
user = await factory.create('User', {
id: 'u1',
role: 'user',
email: 'user@example.org',
@ -22,9 +30,8 @@ describe('rewards', () => {
role: 'admin',
email: 'admin@example.org',
})
await factory.create('Badge', {
id: 'b6',
key: 'indiegogo_en_rhino',
badge = await factory.create('Badge', {
id: 'indiegogo_en_rhino',
type: 'crowdfunding',
status: 'permanent',
icon: '/img/badges/indiegogo_en_rhino.svg',
@ -35,21 +42,19 @@ describe('rewards', () => {
await factory.cleanDatabase()
})
describe('RewardBadge', () => {
const mutation = `
mutation(
$from: ID!
$to: ID!
) {
reward(fromBadgeId: $from, toUserId: $to)
describe('reward', () => {
const mutation = gql`
mutation($from: ID!, $to: ID!) {
reward(badgeKey: $from, userId: $to) {
id
badges {
id
}
}
}
`
describe('unauthenticated', () => {
const variables = {
from: 'b6',
to: 'u1',
}
let client
it('throws authorization error', async () => {
@ -65,74 +70,94 @@ describe('rewards', () => {
client = new GraphQLClient(host, { headers })
})
describe('badge for id does not exist', () => {
it('rejects with a telling error message', async () => {
await expect(
client.request(mutation, {
...variables,
from: 'bullshit',
}),
).rejects.toThrow("Couldn't find a badge with that id")
})
})
describe('user for id does not exist', () => {
it('rejects with a telling error message', async () => {
await expect(
client.request(mutation, {
...variables,
to: 'bullshit',
}),
).rejects.toThrow("Couldn't find a user with that id")
})
})
it('rewards a badge to user', async () => {
const variables = {
from: 'b6',
to: 'u1',
}
const expected = {
reward: 'u1',
reward: {
id: 'u1',
badges: [{ id: 'indiegogo_en_rhino' }],
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
it('rewards a second different badge to same user', async () => {
await factory.create('Badge', {
id: 'b1',
key: 'indiegogo_en_racoon',
type: 'crowdfunding',
status: 'permanent',
id: 'indiegogo_en_racoon',
icon: '/img/badges/indiegogo_en_racoon.svg',
})
const variables = {
from: 'b1',
to: 'u1',
}
const expected = {
reward: 'u1',
reward: {
id: 'u1',
badges: [{ id: 'indiegogo_en_racoon' }, { id: 'indiegogo_en_rhino' }],
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
await client.request(mutation, variables)
await expect(
client.request(mutation, {
...variables,
from: 'indiegogo_en_racoon',
}),
).resolves.toEqual(expected)
})
it('rewards the same badge as well to another user', async () => {
const variables1 = {
from: 'b6',
to: 'u1',
}
await client.request(mutation, variables1)
const variables2 = {
from: 'b6',
to: 'u2',
}
const expected = {
reward: 'u2',
reward: {
id: 'u2',
badges: [{ id: 'indiegogo_en_rhino' }],
},
}
await expect(client.request(mutation, variables2)).resolves.toEqual(expected)
await expect(
client.request(mutation, {
...variables,
to: 'u2',
}),
).resolves.toEqual(expected)
})
it('returns the original reward if a reward is attempted a second time', async () => {
const variables = {
from: 'b6',
to: 'u1',
}
it('creates no duplicate reward relationships', async () => {
await client.request(mutation, variables)
await client.request(mutation, variables)
const query = `{
User( id: "u1" ) {
badgesCount
const query = gql`
{
User(id: "u1") {
badgesCount
badges {
id
}
}
}
}
`
const expected = { User: [{ badgesCount: 1 }] }
const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }
await expect(client.request(query)).resolves.toEqual(expected)
})
})
describe('authenticated moderator', () => {
const variables = {
from: 'b6',
to: 'u1',
}
let client
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
@ -147,27 +172,41 @@ describe('rewards', () => {
})
})
describe('RemoveReward', () => {
describe('unreward', () => {
beforeEach(async () => {
await factory.relate('User', 'Badges', { from: 'b6', to: 'u1' })
await user.relateTo(badge, 'rewarded')
})
const variables = {
from: 'b6',
to: 'u1',
}
const expected = {
unreward: 'u1',
}
const expected = { unreward: { id: 'u1', badges: [] } }
const mutation = `
mutation(
$from: ID!
$to: ID!
) {
unreward(fromBadgeId: $from, toUserId: $to)
const mutation = gql`
mutation($from: ID!, $to: ID!) {
unreward(badgeKey: $from, userId: $to) {
id
badges {
id
}
}
}
`
describe('check test setup', () => {
it('user has one badge', async () => {
const query = gql`
{
User(id: "u1") {
badgesCount
badges {
id
}
}
}
`
const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }
const client = new GraphQLClient(host)
await expect(client.request(query)).resolves.toEqual(expected)
})
})
describe('unauthenticated', () => {
let client
@ -188,12 +227,9 @@ describe('rewards', () => {
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
it('fails to remove a not existing badge from user', async () => {
it('does not crash when unrewarding multiple times', async () => {
await client.request(mutation, variables)
await expect(client.request(mutation, variables)).rejects.toThrow(
"Cannot read property 'id' of undefined",
)
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})

View File

@ -139,7 +139,7 @@ export default {
organizationsCreated: '-[:CREATED_ORGA]->(related:Organization)',
organizationsOwned: '-[:OWNING_ORGA]->(related:Organization)',
categories: '-[:CATEGORIZED]->(related:Category)',
badges: '-[:REWARDED]->(related:Badge)',
badges: '<-[:REWARDED]-(related:Badge)',
}),
},
}

View File

@ -147,7 +147,7 @@ describe('users', () => {
}
`
beforeEach(async () => {
asAuthor = await factory.create('User', {
await factory.create('User', {
email: 'test@example.org',
password: '1234',
id: 'u343',
@ -191,6 +191,7 @@ describe('users', () => {
describe('attempting to delete my own account', () => {
let expectedResponse
beforeEach(async () => {
asAuthor = Factory()
await asAuthor.authenticateAs({
email: 'test@example.org',
password: '1234',

View File

@ -1,4 +0,0 @@
enum BadgeStatus {
permanent
temporary
}

View File

@ -1,4 +0,0 @@
enum BadgeType {
role
crowdfunding
}

View File

@ -0,0 +1,7 @@
enum Emotion {
surprised
cry
happy
angry
funny
}

View File

@ -28,8 +28,6 @@ type Mutation {
report(id: ID!, description: String): Report
disable(id: ID!): ID
enable(id: ID!): ID
reward(fromBadgeId: ID!, toUserId: ID!): ID
unreward(fromBadgeId: ID!, toUserId: ID!): ID
# Shout the given Type and ID
shout(id: ID!, type: ShoutTypeEnum): Boolean!
# Unshout the given Type and ID

View File

@ -1,324 +0,0 @@
scalar Upload
type Query {
isLoggedIn: Boolean!
# Get the currently logged in User based on the given JWT Token
currentUser: User
# Get the latest Network Statistics
statistics: Statistics!
findPosts(filter: String!, limit: Int = 10): [Post]! @cypher(
statement: """
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
YIELD node as post, score
MATCH (post)<-[:WROTE]-(user:User)
WHERE score >= 0.2
AND NOT user.deleted = true AND NOT user.disabled = true
AND NOT post.deleted = true AND NOT post.disabled = true
RETURN post
LIMIT $limit
"""
)
CommentByPost(postId: ID!): [Comment]!
}
type Mutation {
# Get a JWT Token for the given Email and password
login(email: String!, password: String!): String!
signup(email: String!, password: String!): Boolean!
changePassword(oldPassword:String!, newPassword: String!): String!
report(id: ID!, description: String): Report
disable(id: ID!): ID
enable(id: ID!): ID
reward(fromBadgeId: ID!, toUserId: ID!): ID
unreward(fromBadgeId: ID!, toUserId: ID!): ID
# Shout the given Type and ID
shout(id: ID!, type: ShoutTypeEnum): Boolean!
# Unshout the given Type and ID
unshout(id: ID!, type: ShoutTypeEnum): Boolean!
# Follow the given Type and ID
follow(id: ID!, type: FollowTypeEnum): Boolean!
# Unfollow the given Type and ID
unfollow(id: ID!, type: FollowTypeEnum): Boolean!
}
type Statistics {
countUsers: Int!
countPosts: Int!
countComments: Int!
countNotifications: Int!
countOrganizations: Int!
countProjects: Int!
countInvites: Int!
countFollows: Int!
countShouts: Int!
}
type Notification {
id: ID!
read: Boolean,
user: User @relation(name: "NOTIFIED", direction: "OUT")
post: Post @relation(name: "NOTIFIED", direction: "IN")
createdAt: String
}
scalar Date
scalar Time
scalar DateTime
enum VisibilityEnum {
public
friends
private
}
enum UserGroupEnum {
admin
moderator
user
}
type Location {
id: ID!
name: String!
nameEN: String
nameDE: String
nameFR: String
nameNL: String
nameIT: String
nameES: String
namePT: String
namePL: String
type: String!
lat: Float
lng: Float
parent: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
}
type User {
id: ID!
actorId: String
name: String
email: String!
slug: String
password: String!
avatar: String
avatarUpload: Upload
deleted: Boolean
disabled: Boolean
disabledBy: User @relation(name: "DISABLED", direction: "IN")
role: UserGroupEnum
publicKey: String
privateKey: String
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
locationName: String
about: String
socialMedia: [SocialMedia]! @relation(name: "OWNED", direction: "OUT")
createdAt: String
updatedAt: String
notifications(read: Boolean): [Notification]! @relation(name: "NOTIFIED", direction: "IN")
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)")
following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(DISTINCT r)")
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)")
# Is the currently logged in user following that user?
followedByCurrentUser: Boolean! @cypher(
statement: """
MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1
"""
)
#contributions: [WrittenPost]!
#contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
# @cypher(
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
# )
contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
contributionsCount: Int! @cypher(
statement: """
MATCH (this)-[:WROTE]->(r:Post)
WHERE (NOT exists(r.deleted) OR r.deleted = false)
AND (NOT exists(r.disabled) OR r.disabled = false)
RETURN COUNT(r)
"""
)
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT")
organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT")
blacklisted: [User]! @relation(name: "BLACKLISTED", direction: "OUT")
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
}
type Post {
id: ID!
activityId: String
objectId: String
author: User @relation(name: "WROTE", direction: "IN")
title: String!
slug: String
content: String!
contentExcerpt: String
image: String
imageUpload: Upload
visibility: VisibilityEnum
deleted: Boolean
disabled: Boolean
disabledBy: User @relation(name: "DISABLED", direction: "IN")
createdAt: String
updatedAt: String
relatedContributions: [Post]! @cypher(
statement: """
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
RETURN DISTINCT post
LIMIT 10
"""
)
tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
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)")
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)")
# Has the currently logged in user shouted that post?
shoutedByCurrentUser: Boolean! @cypher(
statement: """
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1
"""
)
}
type Comment {
id: ID!
activityId: String
postId: ID
author: User @relation(name: "WROTE", direction: "IN")
content: String!
contentExcerpt: String
post: Post @relation(name: "COMMENTS", direction: "OUT")
createdAt: String
updatedAt: String
deleted: Boolean
disabled: Boolean
disabledBy: User @relation(name: "DISABLED", direction: "IN")
}
type Report {
id: ID!
submitter: User @relation(name: "REPORTED", direction: "IN")
description: String
type: String! @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
createdAt: String
comment: Comment @relation(name: "REPORTED", direction: "OUT")
post: Post @relation(name: "REPORTED", direction: "OUT")
user: User @relation(name: "REPORTED", direction: "OUT")
}
type Category {
id: ID!
name: String!
slug: String
icon: String!
posts: [Post]! @relation(name: "CATEGORIZED", direction: "IN")
postCount: Int! @cypher(statement: "MATCH (this)<-[:CATEGORIZED]-(r:Post) RETURN COUNT(r)")
}
type Badge {
id: ID!
key: String!
type: BadgeTypeEnum!
status: BadgeStatusEnum!
icon: String!
rewarded: [User]! @relation(name: "REWARDED", direction: "OUT")
}
enum BadgeTypeEnum {
role
crowdfunding
}
enum BadgeStatusEnum {
permanent
temporary
}
enum ShoutTypeEnum {
Post
Organization
Project
}
enum FollowTypeEnum {
User
Organization
Project
}
type Reward {
id: ID!
user: User @relation(name: "REWARDED", direction: "IN")
rewarderId: ID
createdAt: String
badge: Badge @relation(name: "REWARDED", direction: "OUT")
}
type Organization {
id: ID!
createdBy: User @relation(name: "CREATED_ORGA", direction: "IN")
ownedBy: [User] @relation(name: "OWNING_ORGA", direction: "IN")
name: String!
slug: String
description: String!
descriptionExcerpt: String
deleted: Boolean
disabled: Boolean
tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
}
type Tag {
id: ID!
name: String!
taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN")
taggedOrganizations: [Organization]! @relation(name: "TAGGED", direction: "IN")
taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(DISTINCT p)")
taggedCountUnique: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p)<-[:WROTE]-(u:User) RETURN COUNT(DISTINCT u)")
deleted: Boolean
disabled: Boolean
}
type SharedInboxEndpoint {
id: ID!
uri: String
}
type SocialMedia {
id: ID!
url: String
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
}

View File

@ -1,6 +1,5 @@
type Badge {
id: ID!
key: String!
type: BadgeType!
status: BadgeStatus!
icon: String!
@ -10,4 +9,23 @@ type Badge {
updatedAt: String
rewarded: [User]! @relation(name: "REWARDED", direction: "OUT")
}
}
enum BadgeStatus {
permanent
temporary
}
enum BadgeType {
role
crowdfunding
}
type Query {
Badge: [Badge]
}
type Mutation {
reward(badgeKey: ID!, userId: ID!): User
unreward(badgeKey: ID!, userId: ID!): User
}

View File

@ -0,0 +1,10 @@
type EMOTED @relation(name: "EMOTED") {
from: User
to: Post
emotion: Emotion
#createdAt: DateTime
#updatedAt: DateTime
createdAt: String
updatedAt: String
}

View File

@ -48,6 +48,8 @@ type Post {
RETURN COUNT(u) >= 1
"""
)
emotions: [EMOTED]
}
type Mutation {

View File

@ -73,6 +73,8 @@ type User {
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
emotions: [EMOTED]
}

View File

@ -1,28 +1,15 @@
import uuid from 'uuid/v4'
export default function(params) {
const {
id = uuid(),
key = '',
type = 'crowdfunding',
status = 'permanent',
icon = '/img/badges/indiegogo_en_panda.svg',
} = params
export default function create() {
return {
mutation: `
mutation(
$id: ID
$key: String!
$type: BadgeType!
$status: BadgeStatus!
$icon: String!
) {
CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) {
id
}
factory: async ({ args, neodeInstance }) => {
const defaults = {
type: 'crowdfunding',
status: 'permanent',
}
`,
variables: { id, key, type, status, icon },
args = {
...defaults,
...args,
}
return neodeInstance.create('Badge', args)
},
}
}

View File

@ -73,6 +73,7 @@ export default function Factory(options = {}) {
const { factory, mutation, variables } = this.factories[node](args)
if (factory) {
this.lastResponse = await factory({ args, neodeInstance })
return this.lastResponse
} else {
this.lastResponse = await this.graphQLClient.request(mutation, variables)
}

View File

@ -3,7 +3,7 @@ import uuid from 'uuid/v4'
import encryptPassword from '../../helpers/encryptPassword'
import slugify from 'slug'
export default function create(params) {
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
@ -21,8 +21,7 @@ export default function create(params) {
...args,
}
args = await encryptPassword(args)
const user = await neodeInstance.create('User', args)
return user.toJson()
return neodeInstance.create('User', args)
},
}
}

View File

@ -5,52 +5,42 @@ import Factory from './factories'
;(async function() {
try {
const f = Factory()
await Promise.all([
const [racoon, rabbit, wolf, bear, turtle, rhino] = await Promise.all([
f.create('Badge', {
id: 'b1',
key: 'indiegogo_en_racoon',
type: 'crowdfunding',
status: 'permanent',
id: 'indiegogo_en_racoon',
icon: '/img/badges/indiegogo_en_racoon.svg',
}),
f.create('Badge', {
id: 'b2',
key: 'indiegogo_en_rabbit',
type: 'crowdfunding',
status: 'permanent',
id: 'indiegogo_en_rabbit',
icon: '/img/badges/indiegogo_en_rabbit.svg',
}),
f.create('Badge', {
id: 'b3',
key: 'indiegogo_en_wolf',
type: 'crowdfunding',
status: 'permanent',
id: 'indiegogo_en_wolf',
icon: '/img/badges/indiegogo_en_wolf.svg',
}),
f.create('Badge', {
id: 'b4',
key: 'indiegogo_en_bear',
type: 'crowdfunding',
status: 'permanent',
id: 'indiegogo_en_bear',
icon: '/img/badges/indiegogo_en_bear.svg',
}),
f.create('Badge', {
id: 'b5',
key: 'indiegogo_en_turtle',
type: 'crowdfunding',
status: 'permanent',
id: 'indiegogo_en_turtle',
icon: '/img/badges/indiegogo_en_turtle.svg',
}),
f.create('Badge', {
id: 'b6',
key: 'indiegogo_en_rhino',
type: 'crowdfunding',
status: 'permanent',
id: 'indiegogo_en_rhino',
icon: '/img/badges/indiegogo_en_rhino.svg',
}),
])
await Promise.all([
const [
peterLustig,
bobDerBaumeister,
jennyRostock,
tick, // eslint-disable-line no-unused-vars
trick, // eslint-disable-line no-unused-vars
track, // eslint-disable-line no-unused-vars
dagobert,
] = await Promise.all([
f.create('User', {
id: 'u1',
name: 'Peter Lustig',
@ -123,30 +113,16 @@ import Factory from './factories'
])
await Promise.all([
f.relate('User', 'Badges', {
from: 'b6',
to: 'u1',
}),
f.relate('User', 'Badges', {
from: 'b5',
to: 'u2',
}),
f.relate('User', 'Badges', {
from: 'b4',
to: 'u3',
}),
f.relate('User', 'Badges', {
from: 'b3',
to: 'u4',
}),
f.relate('User', 'Badges', {
from: 'b2',
to: 'u5',
}),
f.relate('User', 'Badges', {
from: 'b1',
to: 'u6',
}),
peterLustig.relateTo(racoon, 'rewarded'),
peterLustig.relateTo(rhino, 'rewarded'),
peterLustig.relateTo(wolf, 'rewarded'),
bobDerBaumeister.relateTo(racoon, 'rewarded'),
bobDerBaumeister.relateTo(turtle, 'rewarded'),
jennyRostock.relateTo(bear, 'rewarded'),
dagobert.relateTo(rabbit, 'rewarded'),
])
await Promise.all([
f.relate('User', 'Friends', {
from: 'u1',
to: 'u2',

View File

@ -9,10 +9,10 @@
dependencies:
apollo-env "0.5.1"
"@apollographql/graphql-playground-html@1.6.20":
version "1.6.20"
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.20.tgz#bf9f2acdf319c0959fad8ec1239741dd2ead4e8d"
integrity sha512-3LWZa80HcP70Pl+H4KhLDJ7S0px+9/c8GTXdl6SpunRecUaB27g/oOQnAjNHLHdbWuGE0uyqcuGiTfbKB3ilaQ==
"@apollographql/graphql-playground-html@1.6.24":
version "1.6.24"
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz#3ce939cb127fb8aaa3ffc1e90dff9b8af9f2e3dc"
integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==
"@babel/cli@~7.5.0":
version "7.5.0"
@ -787,7 +787,7 @@
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.0.1.tgz#9712fa2ad124ac64668ab06ba847b1eaf83a03fd"
integrity sha512-cctMYH5RLbElaUpZn3IJaUj9QNQD8iXDnl7xNY6KB1aFD2ciJrwpo3kvZowIT75uA+silJFDnSR2kGakALUymg==
"@hapi/joi@^15.1.0":
"@hapi/joi@^15.0.3", "@hapi/joi@^15.1.0":
version "15.1.0"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.0.tgz#940cb749b5c55c26ab3b34ce362e82b6162c8e7a"
integrity sha512-n6kaRQO8S+kepUTbXL9O/UOL788Odqs38/VOfoCrATDtTvyfiO3fgjlSRaNkHabpTLgM7qru9ifqXlXbXk8SeQ==
@ -1343,13 +1343,13 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
apollo-cache-control@0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.7.4.tgz#0cb5c7be0e0dd0c44b1257144cd7f9f2a3c374e6"
integrity sha512-TVACHwcEF4wfHo5H9FLnoNjo0SLDo2jPW+bXs9aw0Y4Z2UisskSAPnIYOqUPnU8SoeNvs7zWgbLizq11SRTJtg==
apollo-cache-control@0.7.5:
version "0.7.5"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.7.5.tgz#5d8b949bd9b4f03ca32c7d7e429f509c6881eefc"
integrity sha512-zCPwHjbo/VlmXl0sclZfBq/MlVVeGUAg02Q259OIXSgHBvn9BbExyz+EkO/DJvZfGMquxqS1X1BFO3VKuLUTdw==
dependencies:
apollo-server-env "2.4.0"
graphql-extensions "0.7.4"
graphql-extensions "0.7.7"
apollo-cache-control@^0.1.0:
version "0.1.1"
@ -1406,17 +1406,17 @@ apollo-engine-reporting-protobuf@0.3.1:
dependencies:
protobufjs "^6.8.6"
apollo-engine-reporting@1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.5.tgz#075424d39dfe77a20f96e8e33b7ae52d58c38e1e"
integrity sha512-pSwjPgXK/elFsR22LXALtT3jI4fpEpeTNTHgNwLVLohaolusMYgBc/9FnVyFWFfMFS9k+3RmfeQdHhZ6T7WKFQ==
apollo-engine-reporting@1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.6.tgz#579ba2da85ff848bd92be1b0f1ad61f0c57e3585"
integrity sha512-oCoFAUBGveg1i1Sao/2gNsf1kirJBT6vw6Zan9BCNUkyh68ewDts+xRg32VnD9lDhaHpXVJ3tVtuaV44HmdSEw==
dependencies:
apollo-engine-reporting-protobuf "0.3.1"
apollo-graphql "^0.3.3"
apollo-server-core "2.6.7"
apollo-server-core "2.6.9"
apollo-server-env "2.4.0"
async-retry "^1.2.1"
graphql-extensions "0.7.6"
graphql-extensions "0.7.7"
apollo-env@0.5.1:
version "0.5.1"
@ -1486,50 +1486,24 @@ apollo-server-caching@0.4.0:
dependencies:
lru-cache "^5.0.0"
apollo-server-core@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.7.tgz#85b0310f40cfec43a702569c73af16d88776a6f0"
integrity sha512-HfOGLvEwPgDWTvd3ZKRPEkEnICKb7xadn1Mci4+auMTsL/NVkfpjPa8cdzubi/kS2/MvioIn7Bg74gmiSLghGQ==
apollo-server-core@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.9.tgz#75542ad206782e5c31a023b54962e9fdc6404a91"
integrity sha512-r2/Kjm1UmxoTViUt5EcExWXkWl0riXsuGyS1q5LpHKKnA+6b+t4LQKECkRU4EWNpuuzJQn7aF7MmMdvURxoEig==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
"@apollographql/graphql-playground-html" "1.6.20"
"@apollographql/graphql-playground-html" "1.6.24"
"@types/ws" "^6.0.0"
apollo-cache-control "0.7.4"
apollo-cache-control "0.7.5"
apollo-datasource "0.5.0"
apollo-engine-reporting "1.3.5"
apollo-engine-reporting "1.3.6"
apollo-server-caching "0.4.0"
apollo-server-env "2.4.0"
apollo-server-errors "2.3.0"
apollo-server-plugin-base "0.5.6"
apollo-tracing "0.7.3"
apollo-server-errors "2.3.1"
apollo-server-plugin-base "0.5.8"
apollo-tracing "0.7.4"
fast-json-stable-stringify "^2.0.0"
graphql-extensions "0.7.6"
graphql-subscriptions "^1.0.0"
graphql-tag "^2.9.2"
graphql-tools "^4.0.0"
graphql-upload "^8.0.2"
sha.js "^2.4.11"
subscriptions-transport-ws "^0.9.11"
ws "^6.0.0"
apollo-server-core@2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.8.tgz#c8758b5f26b5f3b9fef51b911265b80a9ce5251d"
integrity sha512-Jxw+6R/2I2LiZ6kjRFTzPpdjw7HfuVLfNU+svgNlxioLducxBH/wqUs3qYTf4eVUUtWy+nSS/BUf/Ullo+Ur0Q==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
"@apollographql/graphql-playground-html" "1.6.20"
"@types/ws" "^6.0.0"
apollo-cache-control "0.7.4"
apollo-datasource "0.5.0"
apollo-engine-reporting "1.3.5"
apollo-server-caching "0.4.0"
apollo-server-env "2.4.0"
apollo-server-errors "2.3.0"
apollo-server-plugin-base "0.5.7"
apollo-tracing "0.7.3"
fast-json-stable-stringify "^2.0.0"
graphql-extensions "0.7.6"
graphql-extensions "0.7.7"
graphql-subscriptions "^1.0.0"
graphql-tag "^2.9.2"
graphql-tools "^4.0.0"
@ -1555,23 +1529,23 @@ apollo-server-env@2.4.0:
node-fetch "^2.1.2"
util.promisify "^1.0.0"
apollo-server-errors@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.0.tgz#700622b66a16dffcad3b017e4796749814edc061"
integrity sha512-rUvzwMo2ZQgzzPh2kcJyfbRSfVKRMhfIlhY7BzUfM4x6ZT0aijlgsf714Ll3Mbf5Fxii32kD0A/DmKsTecpccw==
apollo-server-errors@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.1.tgz#033cf331463ebb99a563f8354180b41ac6714eb6"
integrity sha512-errZvnh0vUQChecT7M4A/h94dnBSRL213dNxpM5ueMypaLYgnp4hiCTWIEaooo9E4yMGd1qA6WaNbLDG2+bjcg==
apollo-server-express@2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.8.tgz#9f3e29f7087af669f05d75dfd335b4a9383ba48e"
integrity sha512-LQzVHknQDkHWffc2qK9dr/qNxQ/WecSKiye5/w10tXrOy3aruTFe67ysG/vMnFZ/puroqiZ2njHzhHZztqQ4sA==
apollo-server-express@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.9.tgz#176dab7f2cd5a99655c8eb382ad9b10797422a7b"
integrity sha512-iTkdIdX7m9EAlmL/ZPkKR+x/xuFk1HYZWuJIJG57hHUhcOxj50u7F1E5+5fDwl5RFIdepQ61azF31hhNZuNi4g==
dependencies:
"@apollographql/graphql-playground-html" "1.6.20"
"@apollographql/graphql-playground-html" "1.6.24"
"@types/accepts" "^1.3.5"
"@types/body-parser" "1.17.0"
"@types/cors" "^2.8.4"
"@types/express" "4.17.0"
accepts "^1.3.5"
apollo-server-core "2.6.8"
apollo-server-core "2.6.9"
body-parser "^1.18.3"
cors "^2.8.4"
graphql-subscriptions "^1.0.0"
@ -1599,41 +1573,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"
integrity sha512-GmkOcb5he2x5gat+TuiTvabnBf1m4jzdecal3XbXBh/Jg+kx4hcvO3TTDFQ9CuTprtzdcVyA11iqG7iOMOt7vA==
apollo-server-plugin-base@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.6.tgz#3a7128437a0f845e7d873fa43ef091ff7bf27975"
integrity sha512-wJvcPqfm/kiBwY5JZT85t2A4pcHv24xdQIpWMNt1zsnx77lIZqJmhsc22eSUSrlnYqUMXC4XMVgSUfAO4oI9wg==
apollo-server-plugin-base@0.5.8:
version "0.5.8"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.8.tgz#77b4127aff4e3514a9d49e3cc61256aee4d9422e"
integrity sha512-ICbaXr0ycQZL5llbtZhg8zyHbxuZ4khdAJsJgiZaUXXP6+F47XfDQ5uwnl/4Sq9fvkpwS0ctvfZ1D+Ks4NvUzA==
apollo-server-plugin-base@0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.7.tgz#267faeb5c2de7fa8d3be469cb99f82f601be7aed"
integrity sha512-HeEwEZ92c2XYRV+0CFLbstW3vUJ4idCxR9E9Q3wwvhXrq8gaGzqyDoC8EzAzRxCJUKcEn7xQOpT/AUTC/KtkRA==
apollo-server-testing@~2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.8.tgz#e75364df7fdc2d6a11023f8a0f72a14309b90800"
integrity sha512-pch2I+8QhdXBMnGDctVth4BcZ5hocwY/ogtBMoQuv7H2HBnlDOz7dCM9BH4TW3+Tk6cFgvLTaDtLJ+NxMCtyVA==
apollo-server-testing@~2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.9.tgz#6c1d20a89c0676bf32714405d729c302d62adfb1"
integrity sha512-MQfXAjNsI63O9sY60tQnGy102sqJSr++Yzm+IR44WrK3Z7FHUDisoh6UATly04EDGtO034xtqukzdUNQCK7+rw==
dependencies:
apollo-server-core "2.6.8"
apollo-server-core "2.6.9"
apollo-server@~2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.8.tgz#5f3cf5cf4f2feccbded0cb03fa37dcd8260e5c6a"
integrity sha512-BxwaGxnD3GPuZAAqsexVHFvDlF/s2X8pILgYQ4x+VhUkMeQ12DHQtKPuxn2v2GYwH0U/GDXNohkgwxF/5eTDsQ==
apollo-server@~2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.9.tgz#10e70488b35bf5171612dfd3f030e4ef94c75295"
integrity sha512-thZxUHVM1CLl3503gMCVirxN9J/33s5C1R+hHMEfLaUSoDlXSMA81Y9LCOi9+6d0C9l5DwiZCFXeZI/fKic2RA==
dependencies:
apollo-server-core "2.6.8"
apollo-server-express "2.6.8"
apollo-server-core "2.6.9"
apollo-server-express "2.6.9"
express "^4.0.0"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
apollo-tracing@0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.7.3.tgz#8533e3e2dca2d5a25e8439ce498ea33ff4d159ee"
integrity sha512-H6fSC+awQGnfDyYdGIB0UQUhcUC3n5Vy+ujacJ0bY6R+vwWeZOQvu7wRHNjk/rbOSTLCo9A0OcVX7huRyu9SZg==
apollo-tracing@0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.7.4.tgz#f24d1065100b6d8bf581202859ea0e85ba7bf30d"
integrity sha512-vA0FJCBkFpwdWyVF5UtCqN+enShejyiqSGqq8NxXHU1+GEYTngWa56x9OGsyhX+z4aoDIa3HPKPnP3pjzA0qpg==
dependencies:
apollo-server-env "2.4.0"
graphql-extensions "0.7.4"
graphql-extensions "0.7.7"
apollo-tracing@^0.1.0:
version "0.1.4"
@ -2508,17 +2477,12 @@ core-js-pure@3.1.2:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.1.2.tgz#62fc435f35b7374b9b782013cdcb2f97e9f6dffa"
integrity sha512-5ckIdBF26B3ldK9PM177y2ZcATP2oweam9RskHSoqfZCrJ2As6wVg8zJ1zTriFsZf6clj/N1ThDFRGaomMsh9w==
core-js@^2.4.0, core-js@^2.5.3, core-js@^2.5.7:
version "2.6.2"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.2.tgz#267988d7268323b349e20b4588211655f0e83944"
integrity sha512-NdBPF/RVwPW6jr0NCILuyN9RiqLo2b1mddWHkUL+VnvcB7dzlnBJ1bXYntjpTGOgkZiiLWj2JxmOr7eGE3qK6g==
core-js@^2.4.0, core-js@^2.5.3, core-js@^2.5.7, core-js@^2.6.5:
version "2.6.9"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
core-js@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0.tgz#a8dbfa978d29bfc263bfb66c556d0ca924c28957"
integrity sha512-WBmxlgH2122EzEJ6GH8o9L/FeoUKxxxZ6q6VUxoTlsE4EvbTWKJb447eyVxTEuq0LpXjlq/kCB2qgBvsYRkLvQ==
core-js@^3.0.1:
core-js@^3.0.0, core-js@^3.0.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.3.tgz#95700bca5f248f5f78c0ec63e784eca663ec4138"
integrity sha512-PWZ+ZfuaKf178BIAg+CRsljwjIMRV8MY00CbZczkR6Zk5LfkSkjGoaab3+bqRQWVITNZxQB7TFYz+CFcyuamvA==
@ -3827,19 +3791,13 @@ graphql-deduplicator@^2.0.1:
resolved "https://registry.yarnpkg.com/graphql-deduplicator/-/graphql-deduplicator-2.0.2.tgz#d8608161cf6be97725e178df0c41f6a1f9f778f3"
integrity sha512-0CGmTmQh4UvJfsaTPppJAcHwHln8Ayat7yXXxdnuWT+Mb1dBzkbErabCWzjXyKh/RefqlGTTA7EQOZHofMaKJA==
graphql-extensions@0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.7.4.tgz#78327712822281d5778b9210a55dc59c93a9c184"
integrity sha512-Ly+DiTDU+UtlfPGQkqmBX2SWMr9OT3JxMRwpB9K86rDNDBTJtG6AE2kliQKKE+hg1+945KAimO7Ep+YAvS7ywg==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
graphql-extensions@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.7.6.tgz#80cdddf08b0af12525529d1922ee2ea0d0cc8ecf"
integrity sha512-RV00O3YFD1diehvdja180BlKOGWgeigr/8/Wzr6lXwLcFtk6FecQC/7nf6oW1qhuXczHyNjt/uCr0WWbWq6mYg==
graphql-extensions@0.7.7:
version "0.7.7"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.7.7.tgz#19f4dea35391065de72b25def98f8396887bdf43"
integrity sha512-xiTbVGPUpLbF86Bc+zxI/v/axRkwZx3s+y2/kUb2c2MxNZeNhMZEw1dSutuhY2f2JkRkYFJii0ucjIVqPAQ/Lg==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
apollo-server-env "2.4.0"
graphql-extensions@^0.0.x, graphql-extensions@~0.0.9:
version "0.0.10"
@ -5023,7 +4981,7 @@ jmespath@0.15.0:
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
joi@^13.0.0, joi@^13.7.0:
joi@^13.7.0:
version "13.7.0"
resolved "https://registry.yarnpkg.com/joi/-/joi-13.7.0.tgz#cfd85ebfe67e8a1900432400b4d03bbd93fb879f"
integrity sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==
@ -5382,9 +5340,9 @@ lodash.isstring@^4.0.1:
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.mergewith@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash.once@^4.0.0:
version "4.1.1"
@ -5401,10 +5359,10 @@ lodash@=3.10.1:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.13:
version "4.17.13"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==
lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.14:
version "4.17.14"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
long@^4.0.0:
version "4.0.0"
@ -5837,10 +5795,10 @@ node-releases@^1.1.19:
dependencies:
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==
nodemailer@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.0.tgz#a89b0c62d3937bdcdeecbf55687bd7911b627e12"
integrity sha512-TEHBNBPHv7Ie/0o3HXnb7xrPSSQmH1dXwQKRaMKDBGt/ZN54lvDVujP6hKkO/vjkIYL9rK8kHSG11+G42Nhxuw==
nodemon@~1.19.1:
version "1.19.1"
@ -8037,13 +7995,13 @@ w3c-hr-time@^1.0.1:
dependencies:
browser-process-hrtime "^0.1.2"
wait-on@~3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-3.2.0.tgz#c83924df0fc42a675c678324c49c769d378bcb85"
integrity sha512-QUGNKlKLDyY6W/qHdxaRlXUAgLPe+3mLL/tRByHpRNcHs/c7dZXbu+OnJWGNux6tU1WFh/Z8aEwvbuzSAu79Zg==
wait-on@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-3.3.0.tgz#9940981d047a72a9544a97b8b5fca45b2170a082"
integrity sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ==
dependencies:
core-js "^2.5.7"
joi "^13.0.0"
"@hapi/joi" "^15.0.3"
core-js "^2.6.5"
minimist "^1.2.0"
request "^2.88.0"
rx "^4.1.0"

View File

@ -22,16 +22,16 @@ Feature: Tags and Categories
When I navigate to the administration dashboard
And I click on the menu item "Categories"
Then I can see the following table:
| | Name | Posts |
| | Just For Fun | 2 |
| | Happyness & Values | 1 |
| | Health & Wellbeing | 0 |
| | Name | Posts |
| | Just For Fun | 2 |
| | Happyness & Values | 1 |
| | Health & Wellbeing | 0 |
Scenario: See an overview of tags
When I navigate to the administration dashboard
And I click on the menu item "Tags"
Then I can see the following table:
| | Name | Users | Posts |
| 1 | Democracy | 3 | 4 |
| 2 | Nature | 2 | 3 |
| 3 | Ecology | 1 | 1 |
| | Name | Users | Posts |
| 1 | Democracy | 3 | 4 |
| 2 | Nature | 2 | 3 |
| 3 | Ecology | 1 | 1 |

View File

@ -1,36 +1,36 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps'
import { When, Then } from "cypress-cucumber-preprocessor/steps";
/* global cy */
When('I visit my profile page', () => {
cy.openPage('profile/peter-pan')
})
When("I visit my profile page", () => {
cy.openPage("profile/peter-pan");
});
Then('I should be able to change my profile picture', () => {
const avatarUpload = 'onourjourney.png'
Then("I should be able to change my profile picture", () => {
const avatarUpload = "onourjourney.png";
cy.fixture(avatarUpload, 'base64').then(fileContent => {
cy.get('#customdropzone').upload(
{ fileContent, fileName: avatarUpload, mimeType: 'image/png' },
{ subjectType: 'drag-n-drop' }
)
})
cy.get('.profile-avatar img')
.should('have.attr', 'src')
.and('contains', 'onourjourney')
cy.contains('.iziToast-message', 'Upload successful').should(
'have.length',
cy.fixture(avatarUpload, "base64").then(fileContent => {
cy.get("#customdropzone").upload(
{ fileContent, fileName: avatarUpload, mimeType: "image/png" },
{ subjectType: "drag-n-drop", force: true }
);
});
cy.get(".profile-avatar img")
.should("have.attr", "src")
.and("contains", "onourjourney");
cy.contains(".iziToast-message", "Upload successful").should(
"have.length",
1
)
})
);
});
When("I visit another user's profile page", () => {
cy.openPage('profile/peter-pan')
})
cy.openPage("profile/peter-pan");
});
Then('I cannot upload a picture', () => {
cy.get('.ds-card-content')
Then("I cannot upload a picture", () => {
cy.get(".ds-card-content")
.children()
.should('not.have.id', 'customdropzone')
.should('have.class', 'ds-avatar')
})
.should("not.have.id", "customdropzone")
.should("have.class", "ds-avatar");
});

View File

@ -23,24 +23,27 @@ Cypress.Commands.add('factory', () => {
Cypress.Commands.add(
'create',
{ prevSubject: true },
(factory, node, properties) => {
return factory.create(node, properties)
async (factory, node, properties) => {
await factory.create(node, properties)
return factory
}
)
Cypress.Commands.add(
'relate',
{ prevSubject: true },
(factory, node, relationship, properties) => {
return factory.relate(node, relationship, properties)
async (factory, node, relationship, properties) => {
await factory.relate(node, relationship, properties)
return factory
}
)
Cypress.Commands.add(
'mutate',
{ prevSubject: true },
(factory, mutation, variables) => {
return factory.mutate(mutation, variables)
async (factory, mutation, variables) => {
await factory.mutate(mutation, variables)
return factory
}
)

View File

@ -25,7 +25,7 @@
[?] type: String, // in nitro this is a defined enum - seems good for now
[X] required: true
},
[X] key: {
[X] id: {
[X] type: String,
[X] required: true
},
@ -43,7 +43,7 @@
CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as badge
MERGE(b:Badge {id: badge._id["$oid"]})
ON CREATE SET
b.key = badge.key,
b.id = badge.key,
b.type = badge.type,
b.icon = replace(badge.image.path, 'https://api-alpha.human-connection.org', ''),
b.status = badge.status,

View File

@ -0,0 +1 @@
MATCH (u:User)-[e:EMOTED]->(c:Post) DETACH DELETE e;

View File

@ -5,31 +5,54 @@
// [-] Omitted in Nitro
// [?] Unclear / has work to be done for Nitro
{
[ ] userId: {
[ ] type: String,
[ ] required: true,
[X] userId: {
[X] type: String,
[X] required: true,
[-] index: true
},
[ ] contributionId: {
[ ] type: String,
[ ] required: true,
[X] contributionId: {
[X] type: String,
[X] required: true,
[-] index: true
},
[ ] rated: {
[ ] type: String,
[?] rated: {
[X] type: String,
[ ] required: true,
[ ] enum: ['funny', 'happy', 'surprised', 'cry', 'angry']
[?] enum: ['funny', 'happy', 'surprised', 'cry', 'angry']
},
[ ] createdAt: {
[ ] type: Date,
[ ] default: Date.now
[X] createdAt: {
[X] type: Date,
[X] default: Date.now
},
[ ] updatedAt: {
[ ] type: Date,
[ ] default: Date.now
[X] updatedAt: {
[X] type: Date,
[X] default: Date.now
},
[ ] wasSeeded: { type: Boolean }
[-] wasSeeded: { type: Boolean }
}
*/
CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as emotion;
CALL apoc.load.json("file:${IMPORT_CHUNK_PATH_CQL_FILE}") YIELD value as emotion
MATCH (u:User {id: emotion.userId}),
(c:Post {id: emotion.contributionId})
MERGE (u)-[e:EMOTED {
id: emotion._id["$oid"],
emotion: emotion.rated,
createdAt: datetime(emotion.createdAt.`$date`),
updatedAt: datetime(emotion.updatedAt.`$date`)
}]->(c)
RETURN e;
/*
// Queries
// user sets an emotion emotion:
// MERGE (u)-[e:EMOTED {id: ..., emotion: "funny", createdAt: ..., updatedAt: ...}]->(c)
// user removes emotion
// MATCH (u)-[e:EMOTED]->(c) DELETE e
// contribution distributions over every `emotion` property value for one post
// MATCH (u:User)-[e:EMOTED]->(c:Post {id: "5a70bbc8508f5b000b443b1a"}) RETURN e.emotion,COUNT(e.emotion)
// contribution distributions over every `emotion` property value for one user (advanced - "whats the primary emotion used by the user?")
// MATCH (u:User{id:"5a663b1ac64291000bf302a1"})-[e:EMOTED]->(c:Post) RETURN e.emotion,COUNT(e.emotion)
// contribution distributions over every `emotion` property value for all posts created by one user (advanced - "how do others react to my contributions?")
// MATCH (u:User)-[e:EMOTED]->(c:Post)<-[w:WROTE]-(a:User{id:"5a663b1ac64291000bf302a1"}) RETURN e.emotion,COUNT(e.emotion)
// if we can filter the above an a variable timescale that would be great (should be possible on createdAt and updatedAt fields)
*/

View File

@ -1 +1 @@
// this is just a relation between users(?) - no need to delete
MATCH (u1:User)-[f:FOLLOWS]->(u2:User) DETACH DELETE f;

View File

@ -60,8 +60,8 @@ delete_collection "contributions" "contributions_post"
delete_collection "contributions" "contributions_cando"
delete_collection "shouts" "shouts"
delete_collection "comments" "comments"
delete_collection "emotions" "emotions"
#delete_collection "emotions"
#delete_collection "invites"
#delete_collection "notifications"
#delete_collection "organizations"
@ -82,12 +82,12 @@ import_collection "users" "users/users.cql"
import_collection "follows_users" "follows/follows.cql"
#import_collection "follows_organizations" "follows/follows.cql"
import_collection "contributions_post" "contributions/contributions.cql"
import_collection "contributions_cando" "contributions/contributions.cql"
#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" "emotions/emotions.cql"
# import_collection "emotions"
# import_collection "invites"
# import_collection "notifications"
# import_collection "organizations"

View File

@ -101,7 +101,7 @@ ON CREATE SET
u.name = user.name,
u.slug = user.slug,
u.email = user.email,
u.password = user.password,
u.encryptedPassword = user.password,
u.avatar = replace(user.avatar, 'https://api-alpha.human-connection.org', ''),
u.coverImg = replace(user.coverImg, 'https://api-alpha.human-connection.org', ''),
u.wasInvited = user.wasInvited,

View File

@ -26,9 +26,4 @@ services:
ports:
- 4001:4001
- 4123:4123
neo4j:
environment:
- NEO4J_AUTH=none
ports:
- 7687:7687
- 7474:7474

View File

@ -12,6 +12,7 @@ services:
networks:
- hc-network
environment:
- NUXT_BUILD=.nuxt-dist
- HOST=0.0.0.0
- GRAPHQL_URI=http://backend:4000
- MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.bZ8KK9l70omjXbEkkbHGsQ"

View File

@ -24,7 +24,7 @@
"cross-env": "^5.2.0",
"cypress": "^3.4.0",
"cypress-cucumber-preprocessor": "^1.12.0",
"cypress-file-upload": "^3.2.0",
"cypress-file-upload": "^3.3.1",
"cypress-plugin-retries": "^1.2.2",
"dotenv": "^8.0.0",
"faker": "Marak/faker.js#master",

2
webapp/.gitignore vendored
View File

@ -61,6 +61,8 @@ typings/
# nuxt.js build output
.nuxt
# also the build output in docker container
.nuxt-dist
# Nuxt generate
dist

View File

@ -1,6 +1,6 @@
<template>
<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.id" class="hc-badge-container">
<img :title="badge.key" :src="badge.icon | proxyApiUrl" class="hc-badge" />
</div>
</div>

View File

@ -27,7 +27,7 @@
</template>
<script>
import gql from 'graphql-tag'
import CategoryQuery from '~/graphql/CategoryQuery.js'
export default {
props: {
@ -85,13 +85,7 @@ export default {
apollo: {
Category: {
query() {
return gql(`{
Category {
id
name
icon
}
}`)
return CategoryQuery()
},
result(result) {
this.categories = result.data.Category

View File

@ -0,0 +1,134 @@
import { mount, createLocalVue } from '@vue/test-utils'
import VTooltip from 'v-tooltip'
import Styleguide from '@human-connection/styleguide'
import Vuex from 'vuex'
import FilterPosts from './FilterPosts.vue'
import FilterPostsMenuItem from './FilterPostsMenuItems.vue'
import { mutations } from '~/store/posts'
const localVue = createLocalVue()
localVue.use(Styleguide)
localVue.use(VTooltip)
localVue.use(Vuex)
describe('FilterPosts.vue', () => {
let wrapper
let mocks
let propsData
let menuToggle
let allCategoriesButton
let environmentAndNatureButton
let consumptionAndSustainabiltyButton
let democracyAndPoliticsButton
beforeEach(() => {
mocks = {
$apollo: {
query: jest
.fn()
.mockResolvedValueOnce({
data: { Post: { title: 'Post with Category', category: [{ id: 'cat4' }] } },
})
.mockRejectedValue({ message: 'We were unable to filter' }),
},
$t: jest.fn(),
$i18n: {
locale: () => 'en',
},
$toast: {
error: jest.fn(),
},
}
propsData = {
categories: [
{ id: 'cat4', name: 'Environment & Nature', icon: 'tree' },
{ id: 'cat15', name: 'Consumption & Sustainability', icon: 'shopping-cart' },
{ id: 'cat9', name: 'Democracy & Politics', icon: 'university' },
],
}
})
describe('mount', () => {
const store = new Vuex.Store({
mutations: {
'posts/SET_POSTS': mutations.SET_POSTS,
},
})
const Wrapper = () => {
return mount(FilterPosts, { mocks, localVue, propsData, store })
}
beforeEach(() => {
wrapper = Wrapper()
menuToggle = wrapper.findAll('a').at(0)
menuToggle.trigger('click')
})
it('groups the categories by pair', () => {
expect(wrapper.vm.chunk).toEqual([
[
{ id: 'cat4', name: 'Environment & Nature', icon: 'tree' },
{ id: 'cat15', name: 'Consumption & Sustainability', icon: 'shopping-cart' },
],
[{ id: 'cat9', name: 'Democracy & Politics', icon: 'university' }],
])
})
it('starts with all categories button active', () => {
allCategoriesButton = wrapper.findAll('button').at(0)
expect(allCategoriesButton.attributes().class).toContain('ds-button-primary')
})
it('adds a categories id to selectedCategoryIds when clicked', () => {
environmentAndNatureButton = wrapper.findAll('button').at(1)
environmentAndNatureButton.trigger('click')
const filterPostsMenuItem = wrapper.find(FilterPostsMenuItem)
expect(filterPostsMenuItem.vm.selectedCategoryIds).toEqual(['cat4'])
})
it('sets primary to true when the button is clicked', () => {
democracyAndPoliticsButton = wrapper.findAll('button').at(3)
democracyAndPoliticsButton.trigger('click')
expect(democracyAndPoliticsButton.attributes().class).toContain('ds-button-primary')
})
it('queries a post by its categories', () => {
consumptionAndSustainabiltyButton = wrapper.findAll('button').at(2)
consumptionAndSustainabiltyButton.trigger('click')
expect(mocks.$apollo.query).toHaveBeenCalledWith(
expect.objectContaining({
variables: {
filter: { categories_some: { id_in: ['cat15'] } },
first: expect.any(Number),
offset: expect.any(Number),
},
}),
)
})
it('supports a query of multiple categories', () => {
environmentAndNatureButton = wrapper.findAll('button').at(1)
environmentAndNatureButton.trigger('click')
consumptionAndSustainabiltyButton = wrapper.findAll('button').at(2)
consumptionAndSustainabiltyButton.trigger('click')
expect(mocks.$apollo.query).toHaveBeenCalledWith(
expect.objectContaining({
variables: {
filter: { categories_some: { id_in: ['cat4', 'cat15'] } },
first: expect.any(Number),
offset: expect.any(Number),
},
}),
)
})
it('toggles the categoryIds when clicked more than once', () => {
environmentAndNatureButton = wrapper.findAll('button').at(1)
environmentAndNatureButton.trigger('click')
environmentAndNatureButton.trigger('click')
const filterPostsMenuItem = wrapper.find(FilterPostsMenuItem)
expect(filterPostsMenuItem.vm.selectedCategoryIds).toEqual([])
})
})
})

View File

@ -0,0 +1,61 @@
<template>
<dropdown ref="menu" :placement="placement" :offset="offset">
<a slot="default" slot-scope="{ toggleMenu }" href="#" @click.prevent="toggleMenu()">
<ds-icon style="margin: 12px 0px 0px 10px;" name="filter" size="large" />
<ds-icon style="margin: 7px 0px 0px 2px" size="xx-small" name="angle-down" />
</a>
<template slot="popover">
<filter-posts-menu-items :chunk="chunk" @filterPosts="filterPosts" />
</template>
</dropdown>
</template>
<script>
import _ from 'lodash'
import Dropdown from '~/components/Dropdown'
import { filterPosts } from '~/graphql/PostQuery.js'
import { mapMutations } from 'vuex'
import FilterPostsMenuItems from '~/components/FilterPosts/FilterPostsMenuItems'
export default {
components: {
Dropdown,
FilterPostsMenuItems,
},
props: {
placement: { type: String },
offset: { type: [String, Number] },
categories: { type: Array, default: () => [] },
},
data() {
return {
pageSize: 12,
}
},
computed: {
chunk() {
return _.chunk(this.categories, 2)
},
},
methods: {
...mapMutations({
setPosts: 'posts/SET_POSTS',
}),
filterPosts(categoryIds) {
const filter = categoryIds.length ? { categories_some: { id_in: categoryIds } } : {}
this.$apollo
.query({
query: filterPosts(this.$i18n),
variables: {
filter: filter,
first: this.pageSize,
offset: 0,
},
})
.then(({ data: { Post } }) => {
this.setPosts(Post)
})
.catch(error => this.$toast.error(error.message))
},
},
}
</script>

View File

@ -0,0 +1,126 @@
<template>
<ds-container>
<ds-space />
<ds-flex id="filter-posts-header">
<ds-heading tag="h4">{{ $t('filter-posts.header') }}</ds-heading>
<ds-space margin-bottom="large" />
</ds-flex>
<ds-flex>
<ds-flex-item
:width="{ base: '100%', sm: '100%', md: '100%', lg: '5%' }"
class="categories-menu-item"
>
<ds-flex>
<ds-flex-item width="10%" />
<ds-flex-item width="100%">
<ds-button
icon="check"
@click.stop.prevent="toggleCategory()"
:primary="allCategories"
/>
<ds-flex-item>
<label class="category-labels">{{ $t('filter-posts.all') }}</label>
</ds-flex-item>
<ds-space />
</ds-flex-item>
</ds-flex>
</ds-flex-item>
<ds-flex-item :width="{ base: '0%', sm: '0%', md: '0%', lg: '4%' }" />
<ds-flex-item
:width="{ base: '0%', sm: '0%', md: '0%', lg: '3%' }"
id="categories-menu-divider"
/>
<ds-flex-item
:width="{ base: '50%', sm: '50%', md: '50%', lg: '11%' }"
v-for="index in chunk.length"
:key="index"
>
<ds-flex v-for="category in chunk[index - 1]" :key="category.id" class="categories-menu">
<ds-flex class="categories-menu">
<ds-flex-item width="100%" class="categories-menu-item">
<ds-button
:icon="category.icon"
:primary="isActive(category.id)"
@click.stop.prevent="toggleCategory(category.id)"
/>
<ds-space margin-bottom="small" />
</ds-flex-item>
<ds-flex>
<ds-flex-item class="categories-menu-item">
<label class="category-labels">{{ category.name }}</label>
</ds-flex-item>
<ds-space margin-bottom="xx-large" />
</ds-flex>
</ds-flex>
</ds-flex>
</ds-flex-item>
</ds-flex>
</ds-container>
</template>
<script>
export default {
props: {
chunk: { type: Array, default: () => [] },
},
data() {
return {
selectedCategoryIds: [],
allCategories: true,
}
},
methods: {
isActive(id) {
const index = this.selectedCategoryIds.indexOf(id)
if (index > -1) {
return true
}
return false
},
toggleCategory(id) {
if (!id) {
this.selectedCategoryIds = []
this.allCategories = true
} else {
const index = this.selectedCategoryIds.indexOf(id)
if (index > -1) {
this.selectedCategoryIds.splice(index, 1)
} else {
this.selectedCategoryIds.push(id)
}
this.allCategories = false
}
this.$emit('filterPosts', this.selectedCategoryIds)
},
},
}
</script>
<style lang="scss">
#filter-posts-header {
display: block;
}
.categories-menu-item {
text-align: center;
}
.categories-menu {
justify-content: center;
}
.category-labels {
font-size: $font-size-small;
}
@media only screen and (min-width: 960px) {
#categories-menu-divider {
border-left: 1px solid $border-color-soft;
margin: 9px 0px 40px 0px;
}
}
@media only screen and (max-width: 960px) {
#filter-posts-header {
text-align: center;
}
}
</style>

View File

@ -47,7 +47,8 @@ export default {
},
watch: {
Post(post) {
this.comments = post[0].comments || []
const [first] = post
this.comments = (first && first.comments) || []
},
},
apollo: {

View File

@ -2,7 +2,7 @@
<ds-button v-if="totalNotifications <= 0" class="notifications-menu" disabled icon="bell">
{{ totalNotifications }}
</ds-button>
<dropdown v-else class="notifications-menu">
<dropdown v-else class="notifications-menu" :placement="placement">
<template slot="default" slot-scope="{ toggleMenu }">
<ds-button primary icon="bell" @click.prevent="toggleMenu">
{{ totalNotifications }}
@ -48,6 +48,9 @@ export default {
NotificationList,
Dropdown,
},
props: {
placement: { type: String },
},
computed: {
totalNotifications() {
return (this.notifications || []).length

View File

@ -0,0 +1,11 @@
import gql from 'graphql-tag'
export default () => {
return gql(`{
Category {
id
name
icon
}
}`)
}

View File

@ -25,7 +25,6 @@ export default app => {
}
badges {
id
key
icon
}
}

View File

@ -29,7 +29,6 @@ export default i18n => {
}
badges {
id
key
icon
}
}

View File

@ -30,7 +30,6 @@ export default i18n => {
}
badges {
id
key
icon
}
}
@ -61,7 +60,6 @@ export default i18n => {
}
badges {
id
key
icon
}
}
@ -77,3 +75,48 @@ export default i18n => {
}
`)
}
export const filterPosts = i18n => {
const lang = i18n.locale().toUpperCase()
return gql(`
query Post($filter: _PostFilter, $first: Int, $offset: Int) {
Post(filter: $filter, first: $first, offset: $offset) {
id
title
contentExcerpt
createdAt
disabled
deleted
slug
image
author {
id
avatar
slug
name
disabled
deleted
contributionsCount
shoutedCount
commentsCount
followedByCount
followedByCurrentUser
location {
name: name${lang}
}
badges {
id
icon
}
}
commentsCount
categories {
id
name
icon
}
shoutedCount
}
}
`)
}

View File

@ -19,7 +19,6 @@ export default i18n => {
createdAt
badges {
id
key
icon
}
badgesCount
@ -39,7 +38,6 @@ export default i18n => {
commentsCount
badges {
id
key
icon
}
location {
@ -61,7 +59,6 @@ export default i18n => {
commentsCount
badges {
id
key
icon
}
location {

View File

@ -3,14 +3,24 @@
<div class="main-navigation">
<ds-container class="main-navigation-container" style="padding: 10px 10px;">
<div>
<ds-flex>
<ds-flex-item :width="{ base: '49px', md: '150px' }">
<nuxt-link to="/">
<ds-flex class="main-navigation-flex">
<ds-flex-item :width="{ lg: '3.5%' }" />
<ds-flex-item :width="{ base: '80%', sm: '80%', md: '80%', lg: '15%' }">
<a @click="redirectToRoot">
<ds-logo />
</nuxt-link>
</a>
</ds-flex-item>
<ds-flex-item>
<div id="nav-search-box" v-on:click="unfolded" @blur.capture="foldedup">
<ds-flex-item
:width="{ base: '20%', sm: '20%', md: '20%', lg: '0%' }"
class="mobile-hamburger-menu"
>
<ds-button icon="bars" @click="toggleMobileMenuView" right />
</ds-flex-item>
<ds-flex-item
:width="{ base: '85%', sm: '85%', md: '50%', lg: '50%' }"
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
>
<div id="nav-search-box">
<search-input
id="nav-search"
:delay="300"
@ -22,17 +32,36 @@
/>
</div>
</ds-flex-item>
<ds-flex-item width="200px" style="background-color:white">
<div class="main-navigation-right" style="float:right">
<ds-flex-item
:width="{ base: '15%', sm: '15%', md: '10%', lg: '10%' }"
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
>
<no-ssr>
<filter-posts placement="top-start" offset="8" :categories="categories" />
</no-ssr>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '10%', lg: '2%' }" />
<ds-flex-item
:width="{ base: '100%', sm: '100%', md: '100%', lg: '13%' }"
style="background-color:white"
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
>
<div
class="main-navigation-right"
:class="{
'desktop-view': !toggleMobileMenu,
'hide-mobile-menu': !toggleMobileMenu,
}"
>
<no-ssr>
<locale-switch class="topbar-locale-switch" placement="bottom" offset="23" />
<locale-switch class="topbar-locale-switch" placement="top" offset="8" />
</no-ssr>
<template v-if="isLoggedIn">
<no-ssr>
<notification-menu />
<notification-menu placement="top" />
</no-ssr>
<no-ssr>
<dropdown class="avatar-menu">
<dropdown class="avatar-menu" offset="8">
<template slot="default" slot-scope="{ toggleMenu }">
<a
class="avatar-menu-trigger"
@ -118,6 +147,8 @@ import NotificationMenu from '~/components/notifications/NotificationMenu'
import Dropdown from '~/components/Dropdown'
import HcAvatar from '~/components/Avatar/Avatar.vue'
import seo from '~/mixins/seo'
import FilterPosts from '~/components/FilterPosts/FilterPosts.vue'
import CategoryQuery from '~/graphql/CategoryQuery.js'
export default {
components: {
@ -127,11 +158,14 @@ export default {
Modal,
NotificationMenu,
HcAvatar,
FilterPosts,
},
mixins: [seo],
data() {
return {
mobileSearchVisible: false,
toggleMobileMenu: false,
categories: [],
}
},
computed: {
@ -180,10 +214,16 @@ export default {
return routes
},
},
watch: {
Category(category) {
this.categories = category || []
},
},
methods: {
...mapActions({
quickSearchClear: 'search/quickClear',
quickSearch: 'search/quickSearch',
fetchPosts: 'posts/fetchPosts',
}),
goToPost(item) {
this.$nextTick(() => {
@ -200,23 +240,24 @@ export default {
}
return this.$route.path.indexOf(url) === 0
},
unfolded: function() {
document.getElementById('nav-search-box').classList.add('unfolded')
toggleMobileMenuView() {
this.toggleMobileMenu = !this.toggleMobileMenu
},
foldedup: function() {
document.getElementById('nav-search-box').classList.remove('unfolded')
redirectToRoot() {
this.$router.replace('/')
this.fetchPosts({ i18n: this.$i18n, filter: {} })
},
},
apollo: {
Category: {
query() {
return CategoryQuery()
},
fetchPolicy: 'cache-and-network',
},
},
}
</script>
<style>
.unfolded {
position: absolute;
right: 0px;
left: 0px;
z-index: 1;
}
</style>
<style lang="scss">
.topbar-locale-switch {
@ -228,7 +269,7 @@ export default {
.main-container {
padding-top: 6rem;
padding-bottom: 6rem;
padding-bottom: 5rem;
}
.main-navigation {
@ -242,6 +283,14 @@ export default {
flex: 1;
}
.main-navigation-right .desktop-view {
float: right;
}
.avatar-menu {
margin: 2px 0px 0px 5px;
}
.avatar-menu-trigger {
user-select: none;
display: flex;
@ -285,6 +334,24 @@ export default {
}
}
}
@media only screen and (min-width: 960px) {
.mobile-hamburger-menu {
display: none;
}
}
@media only screen and (max-width: 960px) {
#nav-search-box,
.main-navigation-right {
margin: 10px 0px;
}
.hide-mobile-menu {
display: none;
}
}
.ds-footer {
text-align: center;
position: fixed;

View File

@ -4,6 +4,10 @@
"hashtag-search": "Suche nach #{hashtag}",
"clearSearch": "Suche löschen"
},
"filter-posts": {
"header": "Themenkategorien",
"all": "Alle"
},
"site": {
"made": "Mit &#10084; gemacht",
"imprint": "Impressum",
@ -49,8 +53,8 @@
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail Adresse!",
"invalid-invitation-token": "Es sieht so aus, als ob der Einladungscode schon eingelöst wurde. Jeder Code kann nur einmalig benutzt werden."
},
"submit": "Konto erstellen",
"success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt"
"submit": "Konto erstellen",
"success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt"
}
},
"create-user-account": {
@ -408,5 +412,4 @@
"terms": {
"text": "<div ><ol><li><strong>UNFALLGEFAHR: </strong>Das ist eine Testversion! Alle Daten, Dein Profil und die Server können jederzeit komplett vernichtet, verloren, verbrannt und vielleicht auch in der Nähe von Alpha Centauri synchronisiert werden. Die Benutzung läuft auf eigene Gefahr. Mit kommerziellen Nebenwirkungen ist jedoch nicht zu rechnen.</li><br><li><strong>DU UND DEINE DATEN: </strong>Bitte beachte, dass wir die Inhalte der Alphaversion zu Werbezwecken, Webpräsentationen usw. verwenden, aber wir glauben, dass das auch in Deinem Interesse ist. Am besten keinen Nachnamen eingeben und bei noch mehr Datensparsamkeit ein Profilfoto ohne Identität verwenden. Mehr in unserer <a href='/pages/privacy' target='_blank'>Datenschutzerklärung</a>.</li><br><li><strong>BAUSTELLEN: </strong>Das ist immer noch eine Testversion. Wenn etwas nicht funktioniert, blockiert, irritiert, falsch angezeigt, verbogen oder nicht anklickbar ist, bitten wir dies zu entschuldigen. Fehler, Käfer und Bugs bitte melden! <a href='http://localhost:3000/%22https://human-connection.org/alpha/#bugreport%5C%22' target='_blank'>https://human-connection.org/support</a></li><br><li><strong>VERHALTENSCODEX</strong>: Die Verhaltensregeln dienen als Leitsätze für den persönlichen Auftritt und den Umgang untereinander. Wer als Nutzer im Human Connection Netzwerk aktiv ist, Beiträge verfasst, kommentiert oder mit anderen Nutzern, auch außerhalb des Netzwerkes, Kontakt aufnimmt, erkennt diese Verhaltensregeln als verbindlich an: <a href='https://alpha.human-connection.org/pages/code-of-conduct' target='_blank'>https://alpha.human-connection.org/pages/code-of-conduct</a></li><br><li><strong>MODERATION: </strong>Solange kein Community-Moderationssystem lauffähig ist, entscheidet ein Regenbogen-Einhorn darüber, ob Du körperlich und psychisch stabil genug bist, unsere Testversion zu bedienen. Das Einhorn kann Dich jederzeit von der Alpha entfernen. Also sei nett und lass Regenbogenfutter da!</li><br><li><strong>FAIRNESS: </strong>Sollte Dir die Alphaversion unseres Netzwerks wider Erwarten, egal aus welchen Gründen, nicht gefallen, überweisen wir Dir Deine gespendeten Monatsbeiträge innerhalb der ersten 2 Monate gerne zurück. Einfach Mail an: <a href='mailto:info@human-connection.org' target='_blank'>info@human-connection.org </a><strong>Achtung: Viele Funktionen werden erst nach und nach eingebaut. </strong></li><br><li><strong>FRAGEN?</strong> Die Termine und Links zu den Zoom-Räumen findest Du hier: <a href='http://localhost:3000/%22https://human-connection.org/events-und-news//%22' target='_blank'>https://human-connection.org/veranstaltungen/</a></li><br><li><strong>VON MENSCHEN FÜR MENSCHEN: </strong>Bitte hilf uns weitere monatlichen Spender für Human Connection zu bekommen, damit das Netzwerk so schnell wie möglich offiziell an den Start gehen kann. <a href='http://localhost:3000/%22https://human-connection.org/alpha/#bugreport%5C%22' target='_blank'>https://human-connection.org</a></li></ol><p>Jetzt aber viel Spaß mit der Alpha von Human Connection! Für den ersten Weltfrieden. ♥︎</p><br><p><strong>Herzlichst,</strong></p><p><strong>Euer Human Connection Team</strong></p></div>"
}
}

View File

@ -4,6 +4,10 @@
"hashtag-search": "Searching for #{hashtag}",
"clearSearch": "Clear search"
},
"filter-posts": {
"header": "Categories of Content",
"all": "All"
},
"site": {
"made": "Made with &#10084;",
"imprint": "Imprint",
@ -50,8 +54,8 @@
"email-exists": "There is already a user account with this email address!",
"invalid-invitation-token": "It looks like as if the invitation has been used already. Invitation links can only be used once."
},
"submit": "Create an account",
"success": "A mail with a link to complete your registration has been sent to <b>{email}</b>"
"submit": "Create an account",
"success": "A mail with a link to complete your registration has been sent to <b>{email}</b>"
}
},
"create-user-account": {
@ -257,7 +261,6 @@
"more": "show more",
"less": "show less"
}
},
"quotes": {
"african": {

View File

@ -1,4 +1,7 @@
{
"filter-menu": {
"title": "Twoja bańka filtrująca"
},
"site": {
"made": "Z &#10084; zrobiony",
"imprint": "Nadruk",
@ -13,6 +16,7 @@
"responsible": "Odpowiedzialny zgodnie z § 55 Abs. 2 RStV (Niemcy)",
"bank": "rachunek bankowy",
"germany": "Niemcy"
},
"login": {
"copy": "Jeśli masz już konto Human Connection, zaloguj się tutaj.",
@ -20,9 +24,35 @@
"logout": "Wyloguj się",
"email": "Twój adres e-mail",
"password": "Twoje hasło",
"forgotPassword": "Zapomniałeś hasła?",
"moreInfo": "Co to jest Human Connection?",
"moreInfoURL": "https://human-connection.org/pl/",
"moreInfoHint": "na stronę prezentacji",
"hello": "Cześć"
},
"password-reset": {
"title": "Zresetuj hasło",
"form": {
"description": "Na podany adres e-mail zostanie wysłany email z resetem hasła.",
"submit": "Poproś o wiadomość e-mail",
"submitted": "Na adres <b>{email}</b> została wysłana wiadomość z dalszymi instrukcjami."
}
},
"verify-code": {
"form": {
"code": "Wprowadź swój kod",
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
"next": "Kontynuuj",
"change-password": {
"success": "Zmiana hasła zakończyła się sukcesem!",
"error": "Zmiana hasła nie powiodła się. Może kod bezpieczeństwa nie był poprawny?",
"help": "W przypadku problemów, zachęcamy do zwrócenia się o pomoc, wysyłając do nas wiadomość e-mail:"
}
}
},
"editor": {
"placeholder": "Zostaw swoje inspirujące myśli...."
},
"profile": {
"name": "Mój profil",
"memberSince": "Członek od",
@ -31,7 +61,27 @@
"following": "Obserwowani",
"shouted": "Krzyknij",
"commented": "Skomentuj",
"userAnonym": "Anonymous"
"userAnonym": "Anonimowy",
"socialMedia": "Gdzie indziej mogę znaleźć",
"network": {
"title": "Sieć",
"following": "jest następująca:",
"followingNobody": "nie podąża za nikim.",
"followedBy": "po którym następuje:",
"followedByNobody": "nie jest śledzona przez nikogo.",
"and": "i",
"more": "więcej"
}
},
"notifications": {
"menu": {
"mentioned": "wspomniała o tobie na posterunku."
}
},
"search": {
"placeholder": "Wyszukiwanie",
"hint": "Czego szukasz?",
"failed": "Nic nie znaleziono"
},
"settings": {
"name": "Ustawienia",
@ -40,10 +90,28 @@
"labelName": "Twoje dane",
"namePlaceholder": "Anonymous",
"labelCity": "Twoje miasto lub region",
"labelBio": "O Tobie"
"labelBio": "O Tobie",
"success": "Twoje dane zostały pomyślnie zaktualizowane!"
},
"security": {
"name": "Bezpieczeństwo"
"name": "Bezpieczeństwo",
"change-password": {
"button": "Zmień hasło",
"success": "Hasło zostało pomyślnie zmienione!",
"label-old-password": "Twoje stare hasło",
"label-new-password": "Twoje nowe hasło",
"label-new-password-confirm": "Potwierdź nowe hasło",
"message-old-password-required": "Wprowadź swoje stare hasło",
"message-new-password-required": "Wprowadź nowe hasło",
"message-new-password-confirm-required": "Potwierdź nowe hasło.",
"message-new-password-missmatch": "Wpisz ponownie to samo hasło.",
"passwordSecurity": "Zabezpieczenie hasłem",
"passwordStrength0": "Bardzo niepewne hasło",
"passwordStrength1": "Niepewne hasło",
"passwordStrength2": "Hasło pośredniczące",
"passwordStrength3": "Silne hasło",
"passwordStrength4": "Bardzo mocne hasło"
}
},
"invites": {
"name": "Zaproszenia"
@ -51,29 +119,42 @@
"download": {
"name": "Pobierz dane"
},
"delete": {
"name": "Usuń konto"
"deleteUserAccount": {
"name": "Usuwanie danych",
"contributionsCount": "Usuń moje stanowiska.",
"commentsCount": "Usuń moje komentarze {liczba}.",
"accountDescription": "Bądź świadomy, że Twój post i komentarze są ważne dla naszej społeczności. Jeśli nadal chcesz je usunąć, musisz zaznaczyć je poniżej.",
"accountWarning": "<b>Nie możesz zarządzać</b> i <b>Nie możesz REKOVER</b> swoje konto, posty lub komentarze po usunięciu konta!",
"success": "Konto zostało pomyślnie usunięte",
"pleaseConfirm": "<b class='is-danger'>Niszczycielskie działanie!</b> Typ <b>{potwierdź}</b> aby potwierdzić"
},
"organizations": {
"name": "Moje organizacje"
"name": "My Organizations"
},
"languages": {
"name": "Języki"
"name": "Languages"
},
"social-media": {
"name": "Social media",
"placeholder": "Add social media url",
"submit": "Add link",
"successAdd": "Added social media. Updated user profile!",
"successDelete": "Deleted social media. Updated user profile!"
}
},
"admin": {
"name": "Administrator",
"name": "Admin",
"dashboard": {
"name": "Tablica rozdzielcza",
"users": "Użytkownicy",
"posts": "Posty",
"posts": "Stanowiska",
"comments": "Komentarze",
"notifications": "Powiadomienia",
"organizations": "Organizacje",
"projects": "Projekty",
"invites": "Zaproszenia",
"follows": "Obserwowań",
"shouts": "Okrzyk"
"invites": "Zaprasza",
"follows": "Podąża za",
"shouts": "Zalecane"
},
"organizations": {
"name": "Organizacje"
@ -90,116 +171,193 @@
"categories": {
"name": "Kategorie",
"categoryName": "Nazwa",
"postCount": "Posty"
"postCount": "Stanowiska"
},
"tags": {
"name": "Tagi",
"name": "Znaczniki",
"tagCountUnique": "Użytkownicy",
"tagCount": "Posty"
"tagCount": "Stanowiska"
},
"settings": {
"name": "Ustawienia"
}
},
"post": {
"name": "Post",
"name": "Poczta",
"moreInfo": {
"name": "Więcej informacji"
},
"takeAction": {
"name": "Podejmij działanie"
"name": "Podejmij działania"
},
"menu": {
"edit": "Edytuj Post",
"delete": "Usuń wpis"
},
"comment": {
"submit": "Komentarz",
"submitted": "Przedłożony komentarz"
}
},
"comment": {
"content": {
"unavailable-placeholder": " ...ten komentarz nie jest już dostępny."
},
"menu": {
"edit": "Edytuj komentarz",
"delete": "Usuń komentarz"
},
"show": {
"more": "Pokaż więcej",
"less": "Pokaż mniej"
}
},
"quotes": {
"african": {
"quote": "Wielu małych ludzi w wielu małych miejscach robi wiele małych rzeczy, które mogą zmienić oblicze świata.",
"quote": "Wielu małych ludzi w wielu małych miejscowościach robi wiele małych rzeczy, które mogą zmienić oblicze świata.",
"author": "Afrykańskie przysłowie"
}
},
"common": {
"post": "Post ::: Posty",
"post": "Poczta ::: Posty",
"comment": "Komentarz ::: Komentarze",
"letsTalk": "Porozmawiajmy",
"versus": "Versus",
"versus": "werset",
"moreInfo": "Więcej informacji",
"takeAction": "Podejmij działanie",
"shout": "okrzyk okrzyki",
"takeAction": "Podejmij działania",
"shout": "przekazanie sprawy ::: Polecam tę stronę",
"user": "Użytkownik ::: Użytkownicy",
"category": "kategoria kategorie",
"organization": "Organizacja ::: Organizacje",
"category": "Kategoria ::: Kategorie",
"organization": "Organization ::: Organizations",
"project": "Projekt ::: Projekty",
"tag": "Tag ::: Tagi",
"name": "imię",
"loadMore": "załaduj więcej",
"loading": "ładowanie",
"reportContent": "Raport"
"tag": "Znacznik ::: Znaczniki",
"name": "Nazwa",
"loadMore": "Obciążenie więcej",
"loading": "załadunek",
"reportContent": "Sprawozdanie",
"validations": {
"email": "musi być ważny adres e-mail.",
"verification-code": "musi mieć długość 6 znaków."
}
},
"actions": {
"loading": "ładowanie",
"loadMore": "załaduj więcej",
"create": "Stwórz",
"save": "Zapisz",
"edit": "Edytuj",
"loading": "załadunek",
"loadMore": "Obciążenie więcej",
"create": "Tworzenie",
"save": "Oszczędzaj",
"edit": "Edycja",
"delete": "Usuń",
"cancel": "Anuluj"
"cancel": "Odwołaj"
},
"moderation": {
"name": "Moderacja",
"name": "Umiarkowanie",
"reports": {
"empty": "Gratulacje, moderacja nie jest potrzebna",
"name": "Raporty",
"reporter": "zgłoszone przez"
"empty": "Gratulacje, nic do umiarkowanego.",
"name": "Sprawozdania",
"submitter": "zgłaszane przez",
"disabledBy": "niepełnosprawni przez"
}
},
"disable": {
"submit": "Niepełnosprawność",
"cancel": "Odwołaj",
"success": "Niepełnosprawni skutecznie",
"user": {
"title": "Ukryj użytkownika",
"title": "Wyłączenie użytkownika",
"type": "Użytkownik",
"message": "Czy na pewno chcesz wyłączyć użytkownika \" <b> {name} </b> \"?"
"message": "Czy naprawdę chcesz wyłączyć użytkownika \"<b>{name}</b>\"?"
},
"contribution": {
"title": "Ukryj wpis",
"type": "Wpis / Post",
"message": "Czy na pewno chcesz ukryć wpis \" <b> tytuł} </b> \"?"
"title": "Wyłącz Wkład",
"type": "Wkład",
"message": "Naprawdę chcesz unieszkodliwić ten wkład \"<b>{name}</b>\"?"
},
"comment": {
"title": "Ukryj wpis",
"title": "Wyłącz komentarz",
"type": "Komentarz",
"message": "Czy na pewno chcesz ukryć komentarz użytkownika\"<b>(Imie/Avatar</b>\"?"
"message": "Naprawdę chcesz wyłączyć komentarz \"<b>{name}</b>\"?"
}
},
"delete": {
"submit": "Usuń",
"cancel": "Odwołaj",
"contribution": {
"title": "Usuń Post",
"type": "Wkład",
"message": "Naprawdę chcesz usunąć post \"<b>{name}</b>\"?",
"success": "Wyślij pomyślnie usunięty!"
},
"comment": {
"title": "Usuń komentarz",
"type": "Komentarz",
"message": "Czy naprawdę chcesz usunąć komentarz \"<b>{name}</b>\"?",
"success": "Komentarz został pomyślnie usunięty!"
}
},
"report": {
"submit": "Wyślij raport",
"cancel": "Anuluj",
"submit": "Sprawozdanie",
"cancel": "Odwołaj",
"success": "Dzięki za zgłoszenie!",
"user": {
"title": "Zgłoś użytkownika",
"title": "Raport Użytkownik",
"type": "Użytkownik",
"message": "Czy na pewno chcesz zgłosić użytkownika \" <b> {Imie} </b> \"?"
"message": "Naprawdę chcesz zgłosić użytkownika \"<b>{name}</b>\"?",
"error": "Zgłosiłeś już użytkownika!"
},
"contribution": {
"title": "Zgłoś wpis",
"type": "Wpis / Post",
"message": "Czy na pewno chcesz zgłosić ten wpis użytkownika \" <b> {Imie} </b> \"?"
"title": "Wkład w raport",
"type": "Wkład",
"message": "Naprawdę chcesz zgłosić wkład, jaki wniosłaś do programu \"<b>{name}</b>\"?",
"error": "Zgłosiłeś już ten wkład!"
},
"comment": {
"title": "Zgłoś komentarz",
"title": "Sprawozdanie Komentarz",
"type": "Komentarz",
"message": "Czy na pewno chcesz zgłosić komentarz użytkownika\"<b>(Imie/Avatar</b>\"?"
"message": "Naprawdę chcesz zgłosić komentarz od \"<b>{name}</b>\"?",
"error": "Zgłosiłeś już komentarz!"
}
},
"followButton": {
"follow": "naśladować",
"following": "w skutek"
},
"shoutButton": {
"shouted": "wykrzyczany"
},
"release": {
"submit": "Zwolnienie",
"cancel": "Odwołaj",
"success": "Wydany z powodzeniem!",
"user": {
"title": "Zwolnienie użytkownika",
"type": "Użytkownik",
"message": "Naprawdę chcesz uwolnić użytkownika \"<b>{name}</b>\"?"
},
"contribution": {
"title": "Zwolnienie Wkład",
"type": "Wkład",
"message": "Naprawdę chcesz uwolnić swój wkład \"<b>{name}</b>\"?"
},
"comment": {
"title": "Zwolnienie komentarz",
"type": "komentarz",
"message": "Czy naprawdę chcesz opublikować komentarz od \"<b>{name}</b>\"?"
}
},
"user": {
"avatar": {
"submitted": "Przesłanie udane"
}
},
"contribution": {
"edit": "Edytuj wpis",
"delete": "Usuń wpis"
},
"comment": {
"edit": "Edytuj komentarz",
"delete": "Usuń komentarz"
},
"followButton": {
"follow": "Obserwuj",
"following": "Obserwowani"
},
"shoutButton": {
"shouted": "krzyczeć"
"newPost": "Utwórz nowy post",
"filterFollow": "Filtrowanie wkładu użytkowników, za którymi podążam",
"filterALL": "Wyświetl wszystkie wkłady",
"success": "Zachowany!",
"languageSelectLabel": "Język",
"categories": {
"infoSelectedNoOfMaxCategories": "{chosen} z {max} wybrane kategorie"
}
}
}

View File

@ -10,7 +10,10 @@ const styleguideStyles = process.env.STYLEGUIDE_DEV
]
: '@human-connection/styleguide/dist/shared.scss'
const buildDir = process.env.NUXT_BUILD || '.nuxt'
module.exports = {
buildDir,
mode: 'universal',
dev: dev,

View File

@ -29,7 +29,6 @@
"!**/?(*.)+(spec|test).js?(x)"
],
"coverageReporters": [
"text",
"lcov"
],
"transform": {
@ -65,7 +64,7 @@
"graphql": "~14.4.2",
"isemail": "^3.2.0",
"jsonwebtoken": "~8.5.1",
"linkify-it": "~2.1.0",
"linkify-it": "~2.2.0",
"nuxt": "~2.8.1",
"nuxt-dropzone": "^1.0.2",
"nuxt-env": "~0.1.0",

View File

@ -10,7 +10,7 @@
/>
</ds-flex-item>
<hc-post-card
v-for="(post, index) in uniq(Post)"
v-for="(post, index) in posts"
:key="post.id"
:post="post"
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
@ -33,11 +33,11 @@
<script>
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
import gql from 'graphql-tag'
import uniqBy from 'lodash/uniqBy'
import HcPostCard from '~/components/PostCard'
import HcLoadMore from '~/components/LoadMore.vue'
import { mapGetters } from 'vuex'
import { mapGetters, mapMutations } from 'vuex'
import { filterPosts } from '~/graphql/PostQuery.js'
export default {
components: {
@ -49,7 +49,6 @@ export default {
const { hashtag = null } = this.$route.query
return {
// Initialize your apollo data
Post: [],
page: 1,
pageSize: 12,
filter: {},
@ -61,18 +60,27 @@ export default {
this.changeFilterBubble({ tags_some: { name: this.hashtag } })
}
},
watch: {
Post(post) {
this.setPosts(this.Post)
},
},
computed: {
...mapGetters({
currentUser: 'auth/user',
posts: 'posts/posts',
}),
tags() {
return this.Post ? this.Post[0].tags.map(tag => tag.name) : '-'
return this.posts ? this.posts.tags.map(tag => tag.name) : '-'
},
offset() {
return (this.page - 1) * this.pageSize
},
},
methods: {
...mapMutations({
setPosts: 'posts/SET_POSTS',
}),
changeFilterBubble(filter) {
if (this.hashtag) {
filter = {
@ -129,48 +137,7 @@ export default {
apollo: {
Post: {
query() {
return gql(`
query Post($filter: _PostFilter, $first: Int, $offset: Int) {
Post(filter: $filter, first: $first, offset: $offset) {
id
title
contentExcerpt
createdAt
disabled
deleted
slug
image
author {
id
avatar
slug
name
disabled
deleted
contributionsCount
shoutedCount
commentsCount
followedByCount
followedByCurrentUser
location {
name: name${this.$i18n.locale().toUpperCase()}
}
badges {
id
key
icon
}
}
commentsCount
categories {
id
name
icon
}
shoutedCount
}
}
`)
return filterPosts(this.$i18n)
},
variables() {
return {

View File

@ -111,7 +111,6 @@ export default {
}
badges {
id
key
icon
}
}

View File

@ -206,7 +206,7 @@
:key="post.id"
:post="post"
:width="{ base: '100%', md: '100%', xl: '50%' }"
@removePostFromList="activePosts.splice(index, 1)"
@removePostFromList="removePostFromList(index)"
/>
</template>
<template v-else-if="$apollo.loading">
@ -331,6 +331,10 @@ export default {
},
},
methods: {
removePostFromList(index) {
this.activePosts.splice(index, 1)
this.$apollo.queries.User.refetch()
},
handleTab(tab) {
this.tabActive = tab
this.Post = null

76
webapp/store/posts.js Normal file
View File

@ -0,0 +1,76 @@
import gql from 'graphql-tag'
export const state = () => {
return {
posts: [],
}
}
export const mutations = {
SET_POSTS(state, posts) {
state.posts = posts || null
},
}
export const getters = {
posts(state) {
return state.posts || []
},
}
export const actions = {
async fetchPosts({ commit, dispatch }, { i18n, filter }) {
const client = this.app.apolloProvider.defaultClient
const {
data: { Post },
} = await client.query({
query: gql(`
query Post($filter: _PostFilter, $first: Int, $offset: Int) {
Post(filter: $filter, first: $first, offset: $offset) {
id
title
contentExcerpt
createdAt
disabled
deleted
slug
image
author {
id
avatar
slug
name
disabled
deleted
contributionsCount
shoutedCount
commentsCount
followedByCount
followedByCurrentUser
location {
name: name${i18n.locale().toUpperCase()}
}
badges {
id
icon
}
}
commentsCount
categories {
id
name
icon
}
shoutedCount
}
}`),
variables: {
filter,
first: 12,
offset: 0,
},
})
commit('SET_POSTS', Post)
return Post
},
}

View File

@ -6784,10 +6784,10 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
linkify-it@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db"
integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==
linkify-it@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
dependencies:
uc.micro "^1.0.1"
@ -6870,7 +6870,7 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
lodash._reinterpolate@~3.0.0:
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
@ -6931,19 +6931,19 @@ lodash.tail@^4.1.1:
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
lodash.template@^4.2.4, lodash.template@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
integrity sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
dependencies:
lodash._reinterpolate "~3.0.0"
lodash._reinterpolate "^3.0.0"
lodash.templatesettings "^4.0.0"
lodash.templatesettings@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316"
integrity sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
dependencies:
lodash._reinterpolate "~3.0.0"
lodash._reinterpolate "^3.0.0"
lodash.uniq@^4.5.0:
version "4.5.0"
@ -6956,9 +6956,9 @@ lodash.uniqueid@^4.0.1:
integrity sha1-MmjyanyI5PSxdY1nknGBTjH6WyY=
lodash@4.x, lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
version "4.17.14"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
log-symbols@^2.2.0:
version "2.2.0"

View File

@ -1827,10 +1827,10 @@ cypress-cucumber-preprocessor@^1.12.0:
glob "^7.1.2"
through "^2.3.8"
cypress-file-upload@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.2.0.tgz#a48282e1fea385ba6aef9ec3296a934026f5fd67"
integrity sha512-C1nFgURTgvtz9MpP7sYKjhKSdgQvDhUs3f4w6hvEH33wDDQUkmXwrozKDvxXSdccc07M7wH4O5JF61sTkvY8lA==
cypress-file-upload@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.3.1.tgz#19bb6c296ffc492dbfae8a7511c94d6b4d0ad4d5"
integrity sha512-iUtq/a30i73JXx9sUj5HhmuEV9pHMV2/7C06H8/zFDSgFweFSwKL0SSprQu8Ewf7cAEsExBKigwlLQYFdTW8PA==
cypress-plugin-retries@^1.2.2:
version "1.2.2"