diff --git a/.travis.yml b/.travis.yml
index eb0c6f89a..c266b5f0a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,6 +18,7 @@ install:
- docker-compose -f docker-compose.yml -f docker-compose.travis.yml up --build -d
# avoid "Database constraints have changed after this transaction started"
- wait-on http://localhost:7474
+ - docker-compose exec neo4j db_setup
script:
- export CYPRESS_RETRIES=1
diff --git a/backend/package.json b/backend/package.json
index c3b03e454..1e4c2b8a1 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -49,24 +49,24 @@
"apollo-client": "~2.6.4",
"apollo-link-context": "~1.0.18",
"apollo-link-http": "~1.5.15",
- "apollo-server": "~2.8.2",
- "apollo-server-express": "^2.8.1",
+ "apollo-server": "~2.9.0",
+ "apollo-server-express": "^2.9.0",
"babel-plugin-transform-runtime": "^6.23.0",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
"cross-env": "~5.2.0",
- "date-fns": "2.0.0",
+ "date-fns": "2.0.1",
"debug": "~4.1.1",
"dotenv": "~8.1.0",
"express": "^4.17.1",
"faker": "Marak/faker.js#master",
- "graphql": "^14.5.0",
+ "graphql": "^14.5.3",
"graphql-custom-directives": "~0.2.14",
"graphql-iso-date": "~3.6.1",
"graphql-middleware": "~3.0.5",
"graphql-middleware-sentry": "^3.2.0",
- "graphql-shield": "~6.0.5",
+ "graphql-shield": "~6.0.6",
"graphql-tag": "~2.10.1",
"helmet": "~3.20.0",
"jsonwebtoken": "~8.5.1",
@@ -110,15 +110,15 @@
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.5.5",
"@babel/register": "~7.5.5",
- "apollo-server-testing": "~2.8.2",
+ "apollo-server-testing": "~2.9.0",
"babel-core": "~7.0.0-0",
- "babel-eslint": "~10.0.2",
+ "babel-eslint": "~10.0.3",
"babel-jest": "~24.9.0",
"chai": "~4.2.0",
"cucumber": "~5.1.0",
"eslint": "~6.2.1",
"eslint-config-prettier": "~6.1.0",
- "eslint-config-standard": "~14.0.0",
+ "eslint-config-standard": "~14.0.1",
"eslint-plugin-import": "~2.18.2",
"eslint-plugin-jest": "~22.15.2",
"eslint-plugin-node": "~9.1.0",
diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.js b/backend/src/middleware/handleHtmlContent/handleContentData.js
deleted file mode 100644
index 403b2044e..000000000
--- a/backend/src/middleware/handleHtmlContent/handleContentData.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import extractMentionedUsers from './notifications/extractMentionedUsers'
-import extractHashtags from './hashtags/extractHashtags'
-
-const notifyMentions = async (label, id, idsOfMentionedUsers, context) => {
- if (!idsOfMentionedUsers.length) return
-
- const session = context.driver.session()
- const createdAt = new Date().toISOString()
- let cypher
- if (label === 'Post') {
- cypher = `
- MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
- MATCH (user: User)
- WHERE user.id in $idsOfMentionedUsers
- AND NOT (user)<-[:BLOCKED]-(author)
- CREATE (notification: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt })
- MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
- `
- } else {
- cypher = `
- MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
- MATCH (user: User)
- WHERE user.id in $idsOfMentionedUsers
- AND NOT (user)<-[:BLOCKED]-(author)
- AND NOT (user)<-[:BLOCKED]-(postAuthor)
- CREATE (notification: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt })
- MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
- `
- }
- await session.run(cypher, {
- idsOfMentionedUsers,
- label,
- createdAt,
- id,
- })
- session.close()
-}
-
-const updateHashtagsOfPost = async (postId, hashtags, context) => {
- if (!hashtags.length) return
-
- const session = context.driver.session()
- // We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement
- // functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted
- // and no new Hashtags and relations will be created.
- const cypherDeletePreviousRelations = `
- MATCH (p: Post { id: $postId })-[previousRelations: TAGGED]->(t: Tag)
- DELETE previousRelations
- RETURN p, t
- `
- const cypherCreateNewTagsAndRelations = `
- MATCH (p: Post { id: $postId})
- UNWIND $hashtags AS tagName
- MERGE (t: Tag { id: tagName, disabled: false, deleted: false })
- MERGE (p)-[:TAGGED]->(t)
- RETURN p, t
- `
- await session.run(cypherDeletePreviousRelations, {
- postId,
- })
- await session.run(cypherCreateNewTagsAndRelations, {
- postId,
- hashtags,
- })
- session.close()
-}
-
-const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
- const idsOfMentionedUsers = extractMentionedUsers(args.content)
- const hashtags = extractHashtags(args.content)
-
- const post = await resolve(root, args, context, resolveInfo)
-
- await notifyMentions('Post', post.id, idsOfMentionedUsers, context)
- await updateHashtagsOfPost(post.id, hashtags, context)
-
- return post
-}
-
-const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
- const idsOfMentionedUsers = extractMentionedUsers(args.content)
- const comment = await resolve(root, args, context, resolveInfo)
-
- await notifyMentions('Comment', comment.id, idsOfMentionedUsers, context)
-
- return comment
-}
-
-export default {
- Mutation: {
- CreatePost: handleContentDataOfPost,
- UpdatePost: handleContentDataOfPost,
- CreateComment: handleContentDataOfComment,
- UpdateComment: handleContentDataOfComment,
- },
-}
diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
deleted file mode 100644
index 2925f92cf..000000000
--- a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
+++ /dev/null
@@ -1,392 +0,0 @@
-import { gql } from '../../jest/helpers'
-import Factory from '../../seed/factories'
-import { createTestClient } from 'apollo-server-testing'
-import { neode, getDriver } from '../../bootstrap/neo4j'
-import createServer from '../../server'
-
-let server
-let query
-let mutate
-let user
-let authenticatedUser
-const factory = Factory()
-const driver = getDriver()
-const instance = neode()
-const categoryIds = ['cat9']
-const createPostMutation = gql`
- mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]!) {
- CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
- id
- title
- content
- }
- }
-`
-const updatePostMutation = gql`
- mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]!) {
- UpdatePost(id: $id, content: $content, title: $title, categoryIds: $categoryIds) {
- title
- content
- }
- }
-`
-
-beforeAll(() => {
- const createServerResult = createServer({
- context: () => {
- return {
- user: authenticatedUser,
- neode: instance,
- driver,
- }
- },
- })
- server = createServerResult.server
- const createTestClientResult = createTestClient(server)
- query = createTestClientResult.query
- mutate = createTestClientResult.mutate
-})
-
-beforeEach(async () => {
- user = await instance.create('User', {
- id: 'you',
- name: 'Al Capone',
- slug: 'al-capone',
- email: 'test@example.org',
- password: '1234',
- })
- await instance.create('Category', {
- id: 'cat9',
- name: 'Democracy & Politics',
- icon: 'university',
- })
-})
-
-afterEach(async () => {
- await factory.cleanDatabase()
-})
-
-describe('notifications', () => {
- const notificationQuery = gql`
- query($read: Boolean) {
- currentUser {
- notifications(read: $read, orderBy: createdAt_desc) {
- read
- post {
- content
- }
- comment {
- content
- }
- }
- }
- }
- `
-
- describe('authenticated', () => {
- beforeEach(async () => {
- authenticatedUser = user
- })
-
- describe('given another user', () => {
- let postAuthor
- beforeEach(async () => {
- postAuthor = await instance.create('User', {
- email: 'post-author@example.org',
- password: '1234',
- id: 'postAuthor',
- })
- })
-
- describe('who mentions me in a post', () => {
- const title = 'Mentioning Al Capone'
- const content =
- 'Hey @al-capone how do you do?'
-
- const createPostAction = async () => {
- authenticatedUser = await postAuthor.toJson()
- await mutate({
- mutation: createPostMutation,
- variables: { id: 'p47', title, content, categoryIds },
- })
- authenticatedUser = await user.toJson()
- }
-
- it('sends you a notification', async () => {
- await createPostAction()
- const expectedContent =
- 'Hey @al-capone how do you do?'
- const expected = expect.objectContaining({
- data: {
- currentUser: {
- notifications: [
- {
- read: false,
- post: {
- content: expectedContent,
- },
- comment: null,
- },
- ],
- },
- },
- })
- const { query } = createTestClient(server)
- await expect(
- query({
- query: notificationQuery,
- variables: {
- read: false,
- },
- }),
- ).resolves.toEqual(expected)
- })
-
- describe('who mentions me many times', () => {
- const updatePostAction = async () => {
- const updatedContent = `
- One more mention to
-
- @al-capone
-
- and again:
-
- @al-capone
-
- and again
-
- @al-capone
-
- `
- authenticatedUser = await postAuthor.toJson()
- await mutate({
- mutation: updatePostMutation,
- variables: {
- id: 'p47',
- title,
- content: updatedContent,
- categoryIds,
- },
- })
- authenticatedUser = await user.toJson()
- }
-
- it('creates exactly one more notification', async () => {
- await createPostAction()
- await updatePostAction()
- const expectedContent =
- ' Hey Dude, #Democracy should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone. Hey Dude, #Elections should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone. Hey Dude, #Democracy should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone. Hey Dude, #Elections should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.
One more mention to
@al-capone
and again:
@al-capone
and again
@al-capone
'
- const expected = expect.objectContaining({
- data: {
- currentUser: {
- notifications: [
- {
- read: false,
- post: {
- content: expectedContent,
- },
- comment: null,
- },
- {
- read: false,
- post: {
- content: expectedContent,
- },
- comment: null,
- },
- ],
- },
- },
- })
- await expect(
- query({
- query: notificationQuery,
- variables: {
- read: false,
- },
- }),
- ).resolves.toEqual(expected)
- })
- })
-
- describe('but the author of the post blocked me', () => {
- beforeEach(async () => {
- await postAuthor.relateTo(user, 'blocked')
- })
-
- it('sends no notification', async () => {
- await createPostAction()
- const expected = expect.objectContaining({
- data: {
- currentUser: {
- notifications: [],
- },
- },
- })
- const { query } = createTestClient(server)
- await expect(
- query({
- query: notificationQuery,
- variables: {
- read: false,
- },
- }),
- ).resolves.toEqual(expected)
- })
- })
-
- describe('but the author of the post blocked me and a mentioner mentions me in a comment', () => {
- const createCommentOnPostAction = async () => {
- await createPostAction()
- const createCommentMutation = gql`
- mutation($id: ID, $postId: ID!, $commentContent: String!) {
- CreateComment(id: $id, postId: $postId, content: $commentContent) {
- id
- content
- }
- }
- `
- authenticatedUser = await commentMentioner.toJson()
- await mutate({
- mutation: createCommentMutation,
- variables: {
- id: 'c47',
- postId: 'p47',
- commentContent:
- 'One mention of me with .',
- },
- })
- authenticatedUser = await user.toJson()
- }
- let commentMentioner
-
- beforeEach(async () => {
- await postAuthor.relateTo(user, 'blocked')
- commentMentioner = await instance.create('User', {
- id: 'mentioner',
- name: 'Mr Mentioner',
- slug: 'mr-mentioner',
- email: 'mentioner@example.org',
- password: '1234',
- })
- })
-
- it('sends no notification', async () => {
- await createCommentOnPostAction()
- const expected = expect.objectContaining({
- data: {
- currentUser: {
- notifications: [],
- },
- },
- })
- const { query } = createTestClient(server)
- await expect(
- query({
- query: notificationQuery,
- variables: {
- read: false,
- },
- }),
- ).resolves.toEqual(expected)
- })
- })
- })
- })
- })
-})
-
-describe('Hashtags', () => {
- const id = 'p135'
- const title = 'Two Hashtags'
- const content =
- '
One more mention to
@al-capone
and again:
@al-capone
and again
@al-capone
'
+ const expected = expect.objectContaining({
+ data: {
+ currentUser: {
+ notifications: [
+ {
+ read: false,
+ reason: 'mentioned_in_post',
+ post: {
+ content: expectedContent,
+ },
+ comment: null,
+ },
+ {
+ read: false,
+ reason: 'mentioned_in_post',
+ post: {
+ content: expectedContent,
+ },
+ comment: null,
+ },
+ ],
+ },
+ },
+ })
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toEqual(expected)
+ })
+ })
+
+ describe('but the author of the post blocked me', () => {
+ beforeEach(async () => {
+ await postAuthor.relateTo(notifiedUser, 'blocked')
+ })
+
+ it('sends no notification', async () => {
+ await createPostAction()
+ const expected = expect.objectContaining({
+ data: {
+ currentUser: {
+ notifications: [],
+ },
+ },
+ })
+ const { query } = createTestClient(server)
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toEqual(expected)
+ })
+ })
+ })
+
+ describe('mentions me in a comment', () => {
+ beforeEach(async () => {
+ title = 'Post where I get mentioned in a comment'
+ postContent = 'Content of post where I get mentioned in a comment.'
+ })
+
+ describe('I am not blocked at all', () => {
+ beforeEach(async () => {
+ commentContent =
+ 'One mention about me with @al-capone.'
+ commentAuthor = await instance.create('User', {
+ id: 'commentAuthor',
+ name: 'Mrs Comment',
+ slug: 'mrs-comment',
+ email: 'comment-author@example.org',
+ password: '1234',
+ })
+ })
+
+ it('sends a notification', async () => {
+ await createCommentOnPostAction()
+ const expected = expect.objectContaining({
+ data: {
+ currentUser: {
+ notifications: [
+ {
+ read: false,
+ reason: 'mentioned_in_comment',
+ post: null,
+ comment: {
+ content: commentContent,
+ },
+ },
+ ],
+ },
+ },
+ })
+ const { query } = createTestClient(server)
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toEqual(expected)
+ })
+ })
+
+ describe('but the author of the post blocked me', () => {
+ beforeEach(async () => {
+ await postAuthor.relateTo(notifiedUser, 'blocked')
+ commentContent =
+ 'One mention about me with @al-capone.'
+ commentAuthor = await instance.create('User', {
+ id: 'commentAuthor',
+ name: 'Mrs Comment',
+ slug: 'mrs-comment',
+ email: 'comment-author@example.org',
+ password: '1234',
+ })
+ })
+
+ it('sends no notification', async () => {
+ await createCommentOnPostAction()
+ const expected = expect.objectContaining({
+ data: {
+ currentUser: {
+ notifications: [],
+ },
+ },
+ })
+ const { query } = createTestClient(server)
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toEqual(expected)
+ })
+ })
+ })
+ })
+ })
+})
diff --git a/backend/src/middleware/sentryMiddleware.js b/backend/src/middleware/sentryMiddleware.js
index b1130ad37..da8ef32d0 100644
--- a/backend/src/middleware/sentryMiddleware.js
+++ b/backend/src/middleware/sentryMiddleware.js
@@ -14,13 +14,16 @@ if (sentryConfigs.SENTRY_DSN_BACKEND) {
},
withScope: (scope, error, context) => {
scope.setUser({
- id: context.user.id,
+ id: context.user && context.user.id,
})
scope.setExtra('body', context.req.body)
scope.setExtra('origin', context.req.headers.origin)
scope.setExtra('user-agent', context.req.headers['user-agent'])
},
})
+} else {
+ // eslint-disable-next-line no-console
+ if (process.env.NODE_ENV !== 'test') console.log('Warning: Sentry middleware inactive.')
}
export default sentryMiddleware
diff --git a/backend/src/middleware/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js
index 6094911b1..134c85c0c 100644
--- a/backend/src/middleware/validation/validationMiddleware.js
+++ b/backend/src/middleware/validation/validationMiddleware.js
@@ -2,6 +2,8 @@ import { UserInputError } from 'apollo-server'
const COMMENT_MIN_LENGTH = 1
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
+const NO_CATEGORIES_ERR_MESSAGE =
+ 'You cannot save a post without at least one category or more than three'
const validateCommentCreation = async (resolve, root, args, context, info) => {
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
@@ -19,6 +21,7 @@ const validateCommentCreation = async (resolve, root, args, context, info) => {
postId,
},
)
+ session.close()
const [post] = postQueryRes.records.map(record => {
return record.get('post')
})
@@ -43,9 +46,33 @@ const validateUpdateComment = async (resolve, root, args, context, info) => {
const validatePost = async (resolve, root, args, context, info) => {
const { categoryIds } = args
if (!Array.isArray(categoryIds) || !categoryIds.length || categoryIds.length > 3) {
- throw new UserInputError(
- 'You cannot save a post without at least one category or more than three',
- )
+ throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE)
+ }
+ return resolve(root, args, context, info)
+}
+
+const validateUpdatePost = async (resolve, root, args, context, info) => {
+ const { id, categoryIds } = args
+ const session = context.driver.session()
+ const categoryQueryRes = await session.run(
+ `
+ MATCH (post:Post {id: $id})-[:CATEGORIZED]->(category:Category)
+ RETURN category`,
+ { id },
+ )
+ session.close()
+ const [category] = categoryQueryRes.records.map(record => {
+ return record.get('category')
+ })
+
+ if (category) {
+ if (categoryIds && categoryIds.length > 3) {
+ throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE)
+ }
+ } else {
+ if (!Array.isArray(categoryIds) || !categoryIds.length || categoryIds.length > 3) {
+ throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE)
+ }
}
return resolve(root, args, context, info)
}
@@ -55,6 +82,6 @@ export default {
CreateComment: validateCommentCreation,
UpdateComment: validateUpdateComment,
CreatePost: validatePost,
- UpdatePost: validatePost,
+ UpdatePost: validateUpdatePost,
},
}
diff --git a/backend/src/models/Notification.js b/backend/src/models/Notification.js
index b8690b8c1..b54a99574 100644
--- a/backend/src/models/Notification.js
+++ b/backend/src/models/Notification.js
@@ -1,9 +1,26 @@
import uuid from 'uuid/v4'
module.exports = {
- id: { type: 'uuid', primary: true, default: uuid },
- createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
- read: { type: 'boolean', default: false },
+ id: {
+ type: 'uuid',
+ primary: true,
+ default: uuid,
+ },
+ read: {
+ type: 'boolean',
+ default: false,
+ },
+ reason: {
+ type: 'string',
+ valid: ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_post'],
+ invalid: [null],
+ default: 'mentioned_in_post',
+ },
+ createdAt: {
+ type: 'string',
+ isoDate: true,
+ default: () => new Date().toISOString(),
+ },
user: {
type: 'relationship',
relationship: 'NOTIFIED',
diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js
index 6b173514f..3ca7727e4 100644
--- a/backend/src/schema/resolvers/notifications.spec.js
+++ b/backend/src/schema/resolvers/notifications.spec.js
@@ -69,23 +69,29 @@ describe('currentUser notifications', () => {
factory.create('User', neighborParams),
factory.create('Notification', {
id: 'post-mention-not-for-you',
+ reason: 'mentioned_in_post',
}),
factory.create('Notification', {
id: 'post-mention-already-seen',
read: true,
+ reason: 'mentioned_in_post',
}),
factory.create('Notification', {
id: 'post-mention-unseen',
+ reason: 'mentioned_in_post',
}),
factory.create('Notification', {
id: 'comment-mention-not-for-you',
+ reason: 'mentioned_in_comment',
}),
factory.create('Notification', {
id: 'comment-mention-already-seen',
read: true,
+ reason: 'mentioned_in_comment',
}),
factory.create('Notification', {
id: 'comment-mention-unseen',
+ reason: 'mentioned_in_comment',
}),
])
await factory.authenticateAs(neighborParams)
@@ -287,9 +293,11 @@ describe('UpdateNotification', () => {
factory.create('User', mentionedParams),
factory.create('Notification', {
id: 'post-mention-to-be-updated',
+ reason: 'mentioned_in_post',
}),
factory.create('Notification', {
id: 'comment-mention-to-be-updated',
+ reason: 'mentioned_in_comment',
}),
])
await factory.authenticateAs(userParams)
diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js
index 5bb0c4f81..46d7c414f 100644
--- a/backend/src/schema/resolvers/posts.js
+++ b/backend/src/schema/resolvers/posts.js
@@ -75,22 +75,28 @@ export default {
delete params.categoryIds
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
const session = context.driver.session()
- const cypherDeletePreviousRelations = `
- MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
- DELETE previousRelations
- RETURN post, category
+
+ let updatePostCypher = `MATCH (post:Post {id: $params.id})
+ SET post = $params
`
- await session.run(cypherDeletePreviousRelations, { params })
+ if (categoryIds && categoryIds.length) {
+ const cypherDeletePreviousRelations = `
+ MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
+ DELETE previousRelations
+ RETURN post, category
+ `
- const updatePostCypher = `MATCH (post:Post {id: $params.id})
- SET post = $params
- WITH post
+ await session.run(cypherDeletePreviousRelations, { params })
+
+ updatePostCypher += `WITH post
UNWIND $categoryIds AS categoryId
MATCH (category:Category {id: categoryId})
MERGE (post)-[:CATEGORIZED]->(category)
- RETURN post`
+ `
+ }
+ updatePostCypher += `RETURN post`
const updatePostVariables = { categoryIds, params }
const transactionRes = await session.run(updatePostCypher, updatePostVariables)
diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js
index 44618ecdc..62507af0e 100644
--- a/backend/src/schema/resolvers/posts.spec.js
+++ b/backend/src/schema/resolvers/posts.spec.js
@@ -13,6 +13,7 @@ let client
let userParams
let authorParams
+const postId = 'p3589'
const postTitle = 'I am a title'
const postContent = 'Some content'
const oldTitle = 'Old title'
@@ -96,7 +97,7 @@ beforeEach(async () => {
}),
])
createPostVariables = {
- id: 'p3589',
+ id: postId,
title: postTitle,
content: postContent,
categoryIds,
@@ -218,7 +219,7 @@ describe('CreatePost', () => {
Post: [
{
title: postTitle,
- id: 'p3589',
+ id: postId,
categories: expect.arrayContaining(categoryIdsArray),
},
],
@@ -246,17 +247,16 @@ describe('UpdatePost', () => {
await asAuthor.create('User', authorParams)
await asAuthor.authenticateAs(authorParams)
await asAuthor.create('Post', {
- id: 'p1',
+ id: postId,
title: oldTitle,
content: oldContent,
categoryIds,
})
updatePostVariables = {
- id: 'p1',
+ id: postId,
title: newTitle,
content: newContent,
- categoryIds: null,
}
})
@@ -291,55 +291,96 @@ describe('UpdatePost', () => {
})
it('updates a post', async () => {
- updatePostVariables.categoryIds = ['cat9']
- const expected = { UpdatePost: { id: 'p1', content: newContent } }
+ updatePostVariables.categoryIds = ['cat27']
+ const expected = { UpdatePost: { id: postId, content: newContent } }
await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual(
expected,
)
})
describe('categories', () => {
- beforeEach(async () => {
- await client.request(createPostMutation, createPostVariables)
- updatePostVariables = {
- id: 'p3589',
- title: newTitle,
- content: newContent,
- categoryIds: ['cat27'],
- }
+ it('allows a user to update other attributes without passing in categoryIds explicitly', async () => {
+ const expected = { UpdatePost: { id: postId, content: newContent } }
+ await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual(
+ expected,
+ )
})
it('allows a user to update the categories of a post', async () => {
+ updatePostVariables.categoryIds = ['cat27']
await client.request(updatePostMutation, updatePostVariables)
const expected = [{ id: 'cat27' }]
const postQueryWithCategoriesVariables = {
- id: 'p3589',
+ id: postId,
}
await expect(
client.request(postQueryWithCategories, postQueryWithCategoriesVariables),
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] })
})
- it('throws an error if categoryIds is not an array', async () => {
- updatePostVariables.categoryIds = null
- await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
- postSaveError,
- )
- })
-
- it('requires at least one category for successful update', async () => {
- updatePostVariables.categoryIds = []
- await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
- postSaveError,
- )
- })
-
it('allows a maximum of three category for a successful update', async () => {
updatePostVariables.categoryIds = ['cat9', 'cat27', 'cat15', 'cat4']
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
postSaveError,
)
})
+
+ describe('post created without categories somehow', () => {
+ let ownerNode, owner, postMutationAction
+ beforeEach(async () => {
+ const postSomehowCreated = await instance.create('Post', {
+ id: 'how-was-this-created',
+ title: postTitle,
+ content: postContent,
+ })
+ ownerNode = await instance.create('User', {
+ id: 'author-of-post-without-category',
+ name: 'Hacker',
+ slug: 'hacker',
+ email: 'hacker@example.org',
+ password: '1234',
+ })
+ owner = await ownerNode.toJson()
+ await postSomehowCreated.relateTo(ownerNode, 'author')
+ postMutationAction = async (user, mutation, variables) => {
+ const { server } = createServer({
+ context: () => {
+ return {
+ user,
+ neode: instance,
+ driver,
+ }
+ },
+ })
+ const { mutate } = createTestClient(server)
+
+ return mutate({
+ mutation,
+ variables,
+ })
+ }
+ updatePostVariables.id = 'how-was-this-created'
+ })
+
+ it('throws an error if categoryIds is not an array', async () => {
+ const mustAddCategoryToPost = await postMutationAction(
+ owner,
+ updatePostMutation,
+ updatePostVariables,
+ )
+ expect(mustAddCategoryToPost.errors[0]).toHaveProperty('message', postSaveError)
+ })
+
+ it('requires at least one category for successful update', async () => {
+ updatePostVariables.categoryIds = []
+ const mustAddCategoryToPost = await postMutationAction(
+ owner,
+ updatePostMutation,
+ updatePostVariables,
+ )
+ expect(mustAddCategoryToPost.errors[0]).toHaveProperty('message', postSaveError)
+ })
+ })
})
})
})
@@ -355,7 +396,7 @@ describe('DeletePost', () => {
`
const variables = {
- id: 'p1',
+ id: postId,
}
beforeEach(async () => {
@@ -363,7 +404,7 @@ describe('DeletePost', () => {
await asAuthor.create('User', authorParams)
await asAuthor.authenticateAs(authorParams)
await asAuthor.create('Post', {
- id: 'p1',
+ id: postId,
content: 'To be deleted',
categoryIds,
})
@@ -396,7 +437,7 @@ describe('DeletePost', () => {
})
it('deletes a post', async () => {
- const expected = { DeletePost: { id: 'p1', content: 'To be deleted' } }
+ const expected = { DeletePost: { id: postId, content: 'To be deleted' } }
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
diff --git a/backend/src/schema/types/enum/ReasonNotification.gql b/backend/src/schema/types/enum/ReasonNotification.gql
new file mode 100644
index 000000000..a66c446be
--- /dev/null
+++ b/backend/src/schema/types/enum/ReasonNotification.gql
@@ -0,0 +1,5 @@
+enum ReasonNotification {
+ mentioned_in_post
+ mentioned_in_comment
+ comment_on_post
+}
\ No newline at end of file
diff --git a/backend/src/schema/types/type/Notification.gql b/backend/src/schema/types/type/Notification.gql
index 0f94c2301..a3543445f 100644
--- a/backend/src/schema/types/type/Notification.gql
+++ b/backend/src/schema/types/type/Notification.gql
@@ -1,8 +1,9 @@
type Notification {
id: ID!
read: Boolean
+ reason: ReasonNotification
+ createdAt: String
user: User @relation(name: "NOTIFIED", direction: "OUT")
post: Post @relation(name: "NOTIFIED", direction: "IN")
comment: Comment @relation(name: "NOTIFIED", direction: "IN")
- createdAt: String
}
diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js
index df3886a6c..56518bd06 100644
--- a/backend/src/seed/factories/index.js
+++ b/backend/src/seed/factories/index.js
@@ -62,15 +62,26 @@ export default function Factory(options = {}) {
lastResponse: null,
neodeInstance,
async authenticateAs({ email, password }) {
- const headers = await authenticatedHeaders({ email, password }, seedServerHost)
+ const headers = await authenticatedHeaders(
+ {
+ email,
+ password,
+ },
+ seedServerHost,
+ )
this.lastResponse = headers
- this.graphQLClient = new GraphQLClient(seedServerHost, { headers })
+ this.graphQLClient = new GraphQLClient(seedServerHost, {
+ headers,
+ })
return this
},
async create(node, args = {}) {
const { factory, mutation, variables } = this.factories[node](args)
if (factory) {
- this.lastResponse = await factory({ args, neodeInstance })
+ this.lastResponse = await factory({
+ args,
+ neodeInstance,
+ })
return this.lastResponse
} else {
this.lastResponse = await this.graphQLClient.request(mutation, variables)
@@ -121,11 +132,15 @@ export default function Factory(options = {}) {
},
async invite({ email }) {
const mutation = ` mutation($email: String!) { invite( email: $email) } `
- this.lastResponse = await this.graphQLClient.request(mutation, { email })
+ this.lastResponse = await this.graphQLClient.request(mutation, {
+ email,
+ })
return this
},
async cleanDatabase() {
- this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver })
+ this.lastResponse = await cleanDatabase({
+ driver: this.neo4jDriver,
+ })
return this
},
async emote({ to, data }) {
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 7acb1fc0b..9d1abf176 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -1675,10 +1675,10 @@ apollo-server-caching@0.5.0:
dependencies:
lru-cache "^5.0.0"
-apollo-server-core@2.8.2:
- version "2.8.2"
- resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.8.2.tgz#d7e5a94c43457dd5c5a171c79b1c554b418581d4"
- integrity sha512-ePMy1Ci5PflvM9XUWdnF2C+B6kZF2mhmsoV+SUN7O2jWFb5cW2XvWd4Pppov6reusqkz4VlABgZDfjr+Ck09+g==
+apollo-server-core@2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.0.tgz#5db251093ee121a5f4d90a24d51aa4c21e421243"
+ integrity sha512-IvKIgqOqEEB8nszlpHWzlhAu4376So2PgNhFP6UrlfNTllt/WDti5YMOHnVimPWIDHmLPKFan0+wfzpsoRCRdg==
dependencies:
"@apollographql/apollo-tools" "^0.4.0"
"@apollographql/graphql-playground-html" "1.6.24"
@@ -1694,7 +1694,7 @@ apollo-server-core@2.8.2:
apollo-server-types "0.2.1"
apollo-tracing "0.8.1"
fast-json-stable-stringify "^2.0.0"
- graphql-extensions "0.9.2"
+ graphql-extensions "0.10.0"
graphql-tag "^2.9.2"
graphql-tools "^4.0.0"
graphql-upload "^8.0.2"
@@ -1715,10 +1715,10 @@ apollo-server-errors@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.8.2, apollo-server-express@^2.8.1:
- version "2.8.2"
- resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.8.2.tgz#cd1c6994cf5adceea057a088aff52f289bb36377"
- integrity sha512-eA7IupNbx3PjIW4E0uMjQU9WvxcHznzgdFWRxJ4RqDiIwrrwROb7dgmPm3TJaatU/etjGq482pdfJIlMDNYPeA==
+apollo-server-express@2.9.0, apollo-server-express@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.0.tgz#9d2a2d9823422ef26bca15931669d3153dc8a08b"
+ integrity sha512-+057V6Ui1BX69jUlV6YDQ7Xw9CCBfowN/GauvyF09KnsjYUJ+cB1xf4mkj/HAjaz4ReXQaALJNr2qPYPXS4R6w==
dependencies:
"@apollographql/graphql-playground-html" "1.6.24"
"@types/accepts" "^1.3.5"
@@ -1726,12 +1726,13 @@ apollo-server-express@2.8.2, apollo-server-express@^2.8.1:
"@types/cors" "^2.8.4"
"@types/express" "4.17.1"
accepts "^1.3.5"
- apollo-server-core "2.8.2"
+ apollo-server-core "2.9.0"
apollo-server-types "0.2.1"
body-parser "^1.18.3"
cors "^2.8.4"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
+ parseurl "^1.3.2"
subscriptions-transport-ws "^0.9.16"
type-is "^1.6.16"
@@ -1742,12 +1743,12 @@ apollo-server-plugin-base@0.6.1:
dependencies:
apollo-server-types "0.2.1"
-apollo-server-testing@~2.8.2:
- version "2.8.2"
- resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.8.2.tgz#8faa8f1778fa4893f5bf705f7cea84a69477aa3f"
- integrity sha512-ccp1DpmjdmLT98ww4NtSiDPbeIPlVZJ5Iy408ToyhAGwNXRHk5f8Czf+JAgSayvgt4cxCm1fzxnVe1OjO8oIvA==
+apollo-server-testing@~2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.0.tgz#fb5276e0761992ed493d84e998eaa4f696914519"
+ integrity sha512-N6c+wx5MaDZ0mWPzA11nKkkJjX+E3ubATY3G5ejprUsN8BiHENyEQ0EZh+dO0yL9+q/mUHix3p7Utax9odxBcw==
dependencies:
- apollo-server-core "2.8.2"
+ apollo-server-core "2.9.0"
apollo-server-types@0.2.1:
version "0.2.1"
@@ -1758,13 +1759,13 @@ apollo-server-types@0.2.1:
apollo-server-caching "0.5.0"
apollo-server-env "2.4.1"
-apollo-server@~2.8.2:
- version "2.8.2"
- resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.8.2.tgz#8dd9edef656f7466402be8d7715e788d73d2c50e"
- integrity sha512-7mZVsM+p8mf0cA3pTiMuEw8uYilQjZErKx092XNYRzAjoDdGddIC3GvUuhxMkmqvD2YhrWRNRL3QlxHZKnYXQw==
+apollo-server@~2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.0.tgz#32685712215d420ff5f3298b3b34e972e21ec1c6"
+ integrity sha512-KouRjMWn8pnR4KvVsFXT1GZYzH53J0+v9KwnLUKrLNo2G4KiZu5KhP+tEkF7uTlpHzdPMQAIbwjdXKzOH/r6ew==
dependencies:
- apollo-server-core "2.8.2"
- apollo-server-express "2.8.2"
+ apollo-server-core "2.9.0"
+ apollo-server-express "2.9.0"
express "^4.0.0"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
@@ -1976,17 +1977,17 @@ babel-core@~7.0.0-0:
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-babel-eslint@~10.0.2:
- version "10.0.2"
- resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
- integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
+babel-eslint@~10.0.3:
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
+ integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.0.0"
"@babel/traverse" "^7.0.0"
"@babel/types" "^7.0.0"
- eslint-scope "3.7.1"
eslint-visitor-keys "^1.0.0"
+ resolve "^1.12.0"
babel-jest@^24.9.0, babel-jest@~24.9.0:
version "24.9.0"
@@ -2833,10 +2834,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
-date-fns@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0.tgz#52f05c6ae1fe0e395670082c72b690ab781682d0"
- integrity sha512-nGZDA64Ktq5uTWV4LEH3qX+foV4AguT5qxwRlJDzJtf57d4xLNwtwrfb7SzKCoikoae8Bvxf0zdaEG/xWssp/w==
+date-fns@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.1.tgz#c5f30e31d3294918e6b6a82753a4e719120e203d"
+ integrity sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
@@ -3284,10 +3285,10 @@ eslint-config-prettier@~6.1.0:
dependencies:
get-stdin "^6.0.0"
-eslint-config-standard@~14.0.0:
- version "14.0.0"
- resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.0.0.tgz#1de7bf5af37542dc6eef879ab7eb5e5e0f830747"
- integrity sha512-bV6e2LFvJEetrLjVAy4KWPOUsIhPWr040c649MigTPR6yUtaGuOt6CEAyNeez2lRiC+2+vjGWa02byjs25EB3A==
+eslint-config-standard@~14.0.1:
+ version "14.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.0.1.tgz#375c3636fb4bd453cb95321d873de12e4eef790b"
+ integrity sha512-1RWsAKTDTZgA8bIM6PSC9aTGDAUlKqNkYNJlTZ5xYD/HYkIM6GlcefFvgcJ8xi0SWG5203rttKYX28zW+rKNOg==
eslint-import-resolver-node@^0.3.2:
version "0.3.2"
@@ -3366,14 +3367,6 @@ eslint-plugin-standard@~4.0.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
-eslint-scope@3.7.1:
- version "3.7.1"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
- integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=
- dependencies:
- esrecurse "^4.1.0"
- estraverse "^4.1.1"
-
eslint-scope@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
@@ -4079,6 +4072,15 @@ graphql-custom-directives@~0.2.14:
moment "^2.22.2"
numeral "^2.0.6"
+graphql-extensions@0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.0.tgz#ceafc42e16554930b0dc90f64d5727ee2a9e9cf9"
+ integrity sha512-qz9Ev0NgsRxdTYqYSCpYwBWS9r1imm+vCBt3PmHzqZlE7SEpUPGddn9oKcLRB/P8uXT6dsr60hDmDHukIxiVOw==
+ dependencies:
+ "@apollographql/apollo-tools" "^0.4.0"
+ apollo-server-env "2.4.1"
+ apollo-server-types "0.2.1"
+
graphql-extensions@0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.8.1.tgz#f5f1fed5fe49620c4e70c5d08bdbd0039e91c402"
@@ -4097,15 +4099,6 @@ graphql-extensions@0.9.1:
apollo-server-env "2.4.1"
apollo-server-types "0.2.1"
-graphql-extensions@0.9.2:
- version "0.9.2"
- resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.9.2.tgz#4bdd81d5d9102e20b7ad3d790b16624fb97c7ab7"
- integrity sha512-7yP6Mr6cDBadrM5dl4CIlp1wTMyPPpL64FtcsOABmaOdf9sOb/X7E3wJSi80UsB8sw0CY2V/HCeU3CIXParQjw==
- dependencies:
- "@apollographql/apollo-tools" "^0.4.0"
- apollo-server-env "2.4.1"
- apollo-server-types "0.2.1"
-
graphql-import@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.7.1.tgz#4add8d91a5f752d764b0a4a7a461fcd93136f223"
@@ -4138,10 +4131,10 @@ graphql-request@~1.8.2:
dependencies:
cross-fetch "2.2.2"
-graphql-shield@~6.0.5:
- version "6.0.5"
- resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-6.0.5.tgz#e55c7eb1984c684863c897746044bb216a285b41"
- integrity sha512-+uRVptAv6RvaM5GVqZjEsanlZ2OTmUgDu+x/UW/qD6+Zb+I6nTGZ7nII8LTFHuUdXrCICfxesyMODhQYXcEZWQ==
+graphql-shield@~6.0.6:
+ version "6.0.6"
+ resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-6.0.6.tgz#ef8c53f1dd972c2d1828ffd45ce9b1f877576534"
+ integrity sha512-rwhno5ZvEBbedQ8mEOi/Lk71J5CrpQCOcyuDIO+qb1hqm7cvWLtLVyZFrhVp7vN/vULV9oX30j0clC/1d05LpQ==
dependencies:
"@types/yup" "0.26.23"
lightercollective "^0.3.0"
@@ -4200,10 +4193,10 @@ graphql-upload@^8.0.2:
http-errors "^1.7.2"
object-path "^0.11.4"
-graphql@^14.2.1, graphql@^14.5.0:
- version "14.5.0"
- resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.0.tgz#4801e6460942c9c591944617f6dd224a9e531520"
- integrity sha512-wnGcTD181L2xPnIwHHjx/moV4ulxA2Kms9zcUY+B/SIrK+2N+iOC6WNgnR2zVTmg1Z8P+CZq5KXibTnatg3WUw==
+graphql@^14.2.1, graphql@^14.5.3:
+ version "14.5.3"
+ resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.3.tgz#e025851cc413e153220f4edbbb25d49f55104fa0"
+ integrity sha512-W8A8nt9BsMg0ZK2qA3DJIVU6muWhxZRYLTmc+5XGwzWzVdUdPVlAAg5hTBjiTISEnzsKL/onasu6vl3kgGTbYg==
dependencies:
iterall "^1.2.2"
@@ -6773,7 +6766,7 @@ parse5@^3.0.1:
dependencies:
"@types/node" "*"
-parseurl@~1.3.3:
+parseurl@^1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
@@ -7404,7 +7397,7 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0:
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
diff --git a/deployment/human-connection/deployment-neo4j.yaml b/deployment/human-connection/deployment-neo4j.yaml
index 2fcba9061..297f4b551 100644
--- a/deployment/human-connection/deployment-neo4j.yaml
+++ b/deployment/human-connection/deployment-neo4j.yaml
@@ -25,13 +25,20 @@
- name: nitro-neo4j
image: humanconnection/neo4j:latest
imagePullPolicy: Always
+ resources:
+ requests:
+ memory: "1G"
+ limits:
+ memory: "2G"
env:
- name: NEO4J_apoc_import_file_enabled
value: "true"
- name: NEO4J_dbms_memory_pagecache_size
- value: 1G
+ value: "490M"
- name: NEO4J_dbms_memory_heap_max__size
- value: 1G
+ value: "500M"
+ - name: NEO4J_dbms_memory_heap_initial__size
+ value: "500M"
- name: NEO4J_dbms_security_procedures_unrestricted
value: "algo.*,apoc.*"
envFrom:
diff --git a/neo4j/Dockerfile b/neo4j/Dockerfile
index 56175e423..1cfa04507 100644
--- a/neo4j/Dockerfile
+++ b/neo4j/Dockerfile
@@ -5,7 +5,6 @@ ARG BUILD_COMMIT
ENV BUILD_COMMIT=$BUILD_COMMIT
COPY db_setup.sh /usr/local/bin/db_setup
-COPY entrypoint.sh /docker-entrypoint-wrapper.sh
-RUN apt-get update && apt-get -y install procps wget
+
+RUN apt-get update && apt-get -y install wget htop
RUN wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5.0.4/apoc-3.5.0.4-all.jar -P plugins/
-ENTRYPOINT ["/docker-entrypoint-wrapper.sh"]
diff --git a/neo4j/README.md b/neo4j/README.md
index 78c4bc62e..fe8825734 100644
--- a/neo4j/README.md
+++ b/neo4j/README.md
@@ -18,6 +18,16 @@ docker-compose up
You can access Neo4J through [http://localhost:7474/](http://localhost:7474/)
for an interactive cypher shell and a visualization of the graph.
+### Database Indices and Constraints
+
+Database indices and constraints need to be created when the database is
+running. So start the container with the command above and run:
+
+```bash
+docker-compose exec neo4j db_setup
+```
+
+
## Installation without Docker
Install the community edition of [Neo4j](https://neo4j.com/) along with the plugin
@@ -35,6 +45,20 @@ Then make sure to allow Apoc procedures by adding the following line to your Neo
```
dbms.security.procedures.unrestricted=apoc.*
```
+### Database Indices and Constraints
+
+If you have `cypher-shell` available with your local installation of neo4j you
+can run:
+
+```bash
+# in folder neo4j/
+$ cp .env.template .env
+$ ./db_setup.sh
+```
+
+Otherwise, if you don't have `cypher-shell` available, copy the cypher
+statements [from the `db_setup.sh` script](https://github.com/Human-Connection/Human-Connection/blob/master/neo4j/db_setup.sh) and paste the scripts into your
+[database browser frontend](http://localhost:7474).
### Alternatives
@@ -50,21 +74,3 @@ in `backend/.env`.
Start Neo4J and confirm the database is running at [http://localhost:7474](http://localhost:7474).
-## Database Indices and Constraints
-
-If you are not running our dedicated Neo4J [docker image](https://hub.docker.com/r/humanconnection/neo4j),
-which is the case if you setup Neo4J locally without docker, then you have to
-setup unique indices and database constraints manually.
-
-If you have `cypher-shell` available with your local installation of neo4j you
-can run:
-
-```bash
-# in folder neo4j/
-$ cp .env.template .env
-$ ./db_setup.sh
-```
-
-Otherwise, if you don't have `cypher-shell` available, copy the cypher
-statements [from the `db_setup.sh` script](https://github.com/Human-Connection/Human-Connection/blob/master/neo4j/db_setup.sh) and paste the scripts into your
-[database browser frontend](http://localhost:7474).
diff --git a/neo4j/entrypoint.sh b/neo4j/entrypoint.sh
deleted file mode 100755
index f9c1afbe1..000000000
--- a/neo4j/entrypoint.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# credits: https://github.com/javamonkey79
-# https://github.com/neo4j/docker-neo4j/issues/166
-
-# turn on bash's job control
-set -m
-
-# Start the primary process and put it in the background
-/docker-entrypoint.sh neo4j &
-
-# Start the helper process
-db_setup
-
-# the my_helper_process might need to know how to wait on the
-# primary process to start before it does its work and returns
-
-
-# now we bring the primary process back into the foreground
-# and leave it there
-fg %1
diff --git a/package.json b/package.json
index 799a8e7ce..7569217fb 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"codecov": "^3.5.0",
"cross-env": "^5.2.0",
"cypress": "^3.4.1",
- "cypress-cucumber-preprocessor": "^1.14.1",
+ "cypress-cucumber-preprocessor": "^1.15.0",
"cypress-file-upload": "^3.3.3",
"cypress-plugin-retries": "^1.2.2",
"dotenv": "^8.1.0",
@@ -34,4 +34,4 @@
"npm-run-all": "^4.1.5",
"slug": "^1.1.0"
}
-}
\ No newline at end of file
+}
diff --git a/webapp/components/Comment.spec.js b/webapp/components/Comment.spec.js
index 4fdc48bbd..b9be448e4 100644
--- a/webapp/components/Comment.spec.js
+++ b/webapp/components/Comment.spec.js
@@ -8,7 +8,7 @@ const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
-config.stubs['no-ssr'] = '