diff --git a/backend/neo4j/Dockerfile b/backend/neo4j/Dockerfile
index f6e71811b..a02d41f38 100644
--- a/backend/neo4j/Dockerfile
+++ b/backend/neo4j/Dockerfile
@@ -1,3 +1,3 @@
-FROM neo4j:3.5.0
+FROM neo4j:3.5.4
RUN wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5.0.1/apoc-3.5.0.1-all.jar -P plugins/
COPY migrate.sh /usr/local/bin/migrate
diff --git a/backend/package.json b/backend/package.json
index 2457b9dee..a3f03f1fb 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -10,14 +10,13 @@
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,graphql",
"lint": "eslint src --config .eslintrc.js",
"test": "run-s test:jest test:cucumber",
- "test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev 2> /dev/null",
+ "test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev",
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev",
"test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand",
"test:cucumber:cmd": "wait-on tcp:4001 tcp:4123 && cucumber-js --require-module @babel/register --exit test/",
"test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand",
"test:jest": "run-p --race test:before:* 'test:jest:cmd {@}' --",
- "test:cucumber": " cross-env CLIENT_URI=http://localhost:4123 run-p --race test:before:server test:cucumber:before:seeder 'test:cucumber:cmd {@}' --",
- "test:cucumber:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions yarn run dev",
+ "test:cucumber": " cross-env CLIENT_URI=http://localhost:4123 run-p --race test:before:* 'test:cucumber:cmd {@}' --",
"test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --",
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
"db:reset": "babel-node src/seed/reset-db.js",
@@ -39,7 +38,7 @@
"apollo-link-http": "~1.5.14",
"apollo-server": "~2.4.8",
"bcryptjs": "~2.4.3",
- "cheerio": "~1.0.0-rc.2",
+ "cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
"cross-env": "~5.2.0",
"date-fns": "2.0.0-alpha.27",
@@ -51,7 +50,7 @@
"graphql-custom-directives": "~0.2.14",
"graphql-iso-date": "~3.6.1",
"graphql-middleware": "~3.0.2",
- "graphql-shield": "~5.3.1",
+ "graphql-shield": "~5.3.2",
"graphql-tag": "~2.10.1",
"graphql-yoga": "~1.17.4",
"helmet": "~3.16.0",
@@ -71,7 +70,7 @@
"wait-on": "~3.2.0"
},
"devDependencies": {
- "@babel/cli": "~7.2.3",
+ "@babel/cli": "~7.4.3",
"@babel/core": "~7.4.3",
"@babel/node": "~7.2.2",
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
@@ -92,7 +91,7 @@
"eslint-plugin-standard": "~4.0.0",
"graphql-request": "~1.8.2",
"jest": "~24.7.1",
- "nodemon": "~1.18.10",
+ "nodemon": "~1.18.11",
"supertest": "~4.0.2"
}
-}
+}
\ No newline at end of file
diff --git a/backend/src/graphql-schema.js b/backend/src/graphql-schema.js
index 57b2ffb6c..c17b967d2 100644
--- a/backend/src/graphql-schema.js
+++ b/backend/src/graphql-schema.js
@@ -7,6 +7,7 @@ import reports from './resolvers/reports.js'
import posts from './resolvers/posts.js'
import moderation from './resolvers/moderation.js'
import rewards from './resolvers/rewards.js'
+import notifications from './resolvers/notifications'
export const typeDefs = fs
.readFileSync(
@@ -17,13 +18,15 @@ export const typeDefs = fs
export const resolvers = {
Query: {
...statistics.Query,
- ...userManagement.Query
+ ...userManagement.Query,
+ ...notifications.Query
},
Mutation: {
...userManagement.Mutation,
...reports.Mutation,
...posts.Mutation,
...moderation.Mutation,
- ...rewards.Mutation
+ ...rewards.Mutation,
+ ...notifications.Mutation
}
}
diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js
index 8f86a88e6..8d893a78b 100644
--- a/backend/src/middleware/index.js
+++ b/backend/src/middleware/index.js
@@ -10,6 +10,7 @@ import permissionsMiddleware from './permissionsMiddleware'
import userMiddleware from './userMiddleware'
import includedFieldsMiddleware from './includedFieldsMiddleware'
import orderByMiddleware from './orderByMiddleware'
+import notificationsMiddleware from './notificationsMiddleware'
export default schema => {
let middleware = [
@@ -19,6 +20,7 @@ export default schema => {
excerptMiddleware,
xssMiddleware,
fixImageUrlsMiddleware,
+ notificationsMiddleware,
softDeleteMiddleware,
userMiddleware,
includedFieldsMiddleware,
diff --git a/backend/src/middleware/notifications/mentions.js b/backend/src/middleware/notifications/mentions.js
new file mode 100644
index 000000000..137c23f1c
--- /dev/null
+++ b/backend/src/middleware/notifications/mentions.js
@@ -0,0 +1,10 @@
+const MENTION_REGEX = /\s@([\w_-]+)/g
+
+export function extractSlugs (content) {
+ let slugs = []
+ let match
+ while ((match = MENTION_REGEX.exec(content)) != null) {
+ slugs.push(match[1])
+ }
+ return slugs
+}
diff --git a/backend/src/middleware/notifications/mentions.spec.js b/backend/src/middleware/notifications/mentions.spec.js
new file mode 100644
index 000000000..f12df7f07
--- /dev/null
+++ b/backend/src/middleware/notifications/mentions.spec.js
@@ -0,0 +1,30 @@
+import { extractSlugs } from './mentions'
+
+describe('extract', () => {
+ describe('finds mentions in the form of', () => {
+ it('@user', () => {
+ const content = 'Hello @user'
+ expect(extractSlugs(content)).toEqual(['user'])
+ })
+
+ it('@user-with-dash', () => {
+ const content = 'Hello @user-with-dash'
+ expect(extractSlugs(content)).toEqual(['user-with-dash'])
+ })
+
+ it('@user.', () => {
+ const content = 'Hello @user.'
+ expect(extractSlugs(content)).toEqual(['user'])
+ })
+
+ it('@user-With-Capital-LETTERS', () => {
+ const content = 'Hello @user-With-Capital-LETTERS'
+ expect(extractSlugs(content)).toEqual(['user-With-Capital-LETTERS'])
+ })
+ })
+
+ it('ignores email addresses', () => {
+ const content = 'Hello somebody@example.org'
+ expect(extractSlugs(content)).toEqual([])
+ })
+})
diff --git a/backend/src/middleware/notificationsMiddleware.js b/backend/src/middleware/notificationsMiddleware.js
new file mode 100644
index 000000000..30205278b
--- /dev/null
+++ b/backend/src/middleware/notificationsMiddleware.js
@@ -0,0 +1,27 @@
+import { extractSlugs } from './notifications/mentions'
+
+const notify = async (resolve, root, args, context, resolveInfo) => {
+ const post = await resolve(root, args, context, resolveInfo)
+
+ const session = context.driver.session()
+ const { content, id: postId } = post
+ const slugs = extractSlugs(content)
+ const createdAt = (new Date()).toISOString()
+ const cypher = `
+ match(u:User) where u.slug in $slugs
+ match(p:Post) where p.id = $postId
+ create(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt})
+ merge (n)-[:NOTIFIED]->(u)
+ merge (p)-[:NOTIFIED]->(n)
+ `
+ await session.run(cypher, { slugs, createdAt, postId })
+ session.close()
+
+ return post
+}
+
+export default {
+ Mutation: {
+ CreatePost: notify
+ }
+}
diff --git a/backend/src/middleware/notificationsMiddleware.spec.js b/backend/src/middleware/notificationsMiddleware.spec.js
new file mode 100644
index 000000000..e6fc78c52
--- /dev/null
+++ b/backend/src/middleware/notificationsMiddleware.spec.js
@@ -0,0 +1,85 @@
+import Factory from '../seed/factories'
+import { GraphQLClient } from 'graphql-request'
+import { host, login } from '../jest/helpers'
+
+const factory = Factory()
+let client
+
+beforeEach(async () => {
+ await factory.create('User', {
+ id: 'you',
+ name: 'Al Capone',
+ slug: 'al-capone',
+ email: 'test@example.org',
+ password: '1234'
+ })
+})
+
+afterEach(async () => {
+ await factory.cleanDatabase()
+})
+
+describe('currentUser { notifications }', () => {
+ const query = `query($read: Boolean) {
+ currentUser {
+ notifications(read: $read, orderBy: createdAt_desc) {
+ read
+ post {
+ content
+ }
+ }
+ }
+ }`
+
+ describe('authenticated', () => {
+ let headers
+ beforeEach(async () => {
+ headers = await login({ email: 'test@example.org', password: '1234' })
+ client = new GraphQLClient(host, { headers })
+ })
+
+ describe('given another user', () => {
+ let authorClient
+ let authorParams
+ let authorHeaders
+
+ beforeEach(async () => {
+ authorParams = {
+ email: 'author@example.org',
+ password: '1234',
+ id: 'author'
+ }
+ await factory.create('User', authorParams)
+ authorHeaders = await login(authorParams)
+ })
+
+ describe('who mentions me in a post', () => {
+ beforeEach(async () => {
+ const content = 'Hey @al-capone how do you do?'
+ const title = 'Mentioning Al Capone'
+ const createPostMutation = `
+ mutation($title: String!, $content: String!) {
+ CreatePost(title: $title, content: $content) {
+ title
+ content
+ }
+ }
+ `
+ authorClient = new GraphQLClient(host, { headers: authorHeaders })
+ await authorClient.request(createPostMutation, { title, content })
+ })
+
+ it('sends you a notification', async () => {
+ const expected = {
+ currentUser: {
+ notifications: [
+ { read: false, post: { content: 'Hey @al-capone how do you do?' } }
+ ]
+ }
+ }
+ await expect(client.request(query, { read: false })).resolves.toEqual(expected)
+ })
+ })
+ })
+ })
+})
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index 495bc9145..4ff334806 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -20,6 +20,21 @@ const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, context, info)
return context.user.id === parent.id
})
+const belongsToMe = rule({ cache: 'no_cache' })(async (_, args, context) => {
+ const { driver, user: { id: userId } } = context
+ const { id: notificationId } = args
+ const session = driver.session()
+ const result = await session.run(`
+ MATCH (u:User {id: $userId})<-[:NOTIFIED]-(n:Notification {id: $notificationId})
+ RETURN n
+ `, { userId, notificationId })
+ const [notification] = result.records.map((record) => {
+ return record.get('n')
+ })
+ session.close()
+ return Boolean(notification)
+})
+
const onlyEnabledContent = rule({ cache: 'strict' })(async (parent, args, ctx, info) => {
const { disabled, deleted } = args
return !(disabled || deleted)
@@ -50,6 +65,7 @@ const permissions = shield({
Post: or(onlyEnabledContent, isModerator)
},
Mutation: {
+ UpdateNotification: belongsToMe,
CreatePost: isAuthenticated,
UpdatePost: isAuthor,
DeletePost: isAuthor,
diff --git a/backend/src/resolvers/notifications.js b/backend/src/resolvers/notifications.js
new file mode 100644
index 000000000..bc3da0acf
--- /dev/null
+++ b/backend/src/resolvers/notifications.js
@@ -0,0 +1,14 @@
+import { neo4jgraphql } from 'neo4j-graphql-js'
+
+export default {
+ Query: {
+ Notification: (object, params, context, resolveInfo) => {
+ return neo4jgraphql(object, params, context, resolveInfo, false)
+ }
+ },
+ Mutation: {
+ UpdateNotification: (object, params, context, resolveInfo) => {
+ return neo4jgraphql(object, params, context, resolveInfo, false)
+ }
+ }
+}
diff --git a/backend/src/resolvers/notifications.spec.js b/backend/src/resolvers/notifications.spec.js
index 50ded7bc4..799bc1594 100644
--- a/backend/src/resolvers/notifications.spec.js
+++ b/backend/src/resolvers/notifications.spec.js
@@ -5,13 +5,14 @@ import { host, login } from '../jest/helpers'
const factory = Factory()
let client
+let userParams = {
+ id: 'you',
+ email: 'test@example.org',
+ password: '1234'
+}
beforeEach(async () => {
- await factory.create('User', {
- id: 'you',
- email: 'test@example.org',
- password: '1234'
- })
+ await factory.create('User', userParams)
})
afterEach(async () => {
@@ -118,3 +119,63 @@ describe('currentUser { notifications }', () => {
})
})
})
+
+describe('UpdateNotification', () => {
+ const mutation = `mutation($id: ID!, $read: Boolean){
+ UpdateNotification(id: $id, read: $read) {
+ id read
+ }
+ }`
+ const variables = { id: 'to-be-updated', read: true }
+
+ describe('given a notifications', () => {
+ let headers
+
+ beforeEach(async () => {
+ const mentionedParams = {
+ id: 'mentioned-1',
+ email: 'mentioned@example.org',
+ password: '1234',
+ slug: 'mentioned'
+ }
+ await factory.create('User', mentionedParams)
+ await factory.create('Notification', { id: 'to-be-updated' })
+ await factory.authenticateAs(userParams)
+ await factory.create('Post', { id: 'p1' })
+ await Promise.all([
+ factory.relate('Notification', 'User', { from: 'to-be-updated', to: 'mentioned-1' }),
+ factory.relate('Notification', 'Post', { from: 'p1', to: 'to-be-updated' })
+ ])
+ })
+
+ describe('unauthenticated', () => {
+ it('throws authorization error', async () => {
+ client = new GraphQLClient(host)
+ await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
+ })
+ })
+
+ describe('authenticated', () => {
+ beforeEach(async () => {
+ headers = await login({ email: 'test@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('and owner', () => {
+ beforeEach(async () => {
+ headers = await login({ email: 'mentioned@example.org', password: '1234' })
+ client = new GraphQLClient(host, { headers })
+ })
+
+ it('updates notification', async () => {
+ const expected = { UpdateNotification: { id: 'to-be-updated', read: true } }
+ await expect(client.request(mutation, variables)).resolves.toEqual(expected)
+ })
+ })
+ })
+ })
+})
diff --git a/backend/src/server.js b/backend/src/server.js
index efa9a17c0..fe0d4ee1d 100644
--- a/backend/src/server.js
+++ b/backend/src/server.js
@@ -28,10 +28,10 @@ let schema = makeAugmentedSchema({
resolvers,
config: {
query: {
- exclude: ['Statistics', 'LoggedInUser']
+ exclude: ['Notfication', 'Statistics', 'LoggedInUser']
},
mutation: {
- exclude: ['Statistics', 'LoggedInUser']
+ exclude: ['Notfication', 'Statistics', 'LoggedInUser']
},
debug: debug
}
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 68e6cb931..29de5d201 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -14,22 +14,22 @@
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.6.tgz#022209e28a2b547dcde15b219f0c50f47aa5beb3"
integrity sha512-lqK94b+caNtmKFs5oUVXlSpN3sm5IXZ+KfhMxOtr0LR2SqErzkoJilitjDvJ1WbjHlxLI7WtCjRmOLdOGJqtMQ==
-"@babel/cli@~7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.2.3.tgz#1b262e42a3e959d28ab3d205ba2718e1923cfee6"
- integrity sha512-bfna97nmJV6nDJhXNPeEfxyMjWnt6+IjUAaDPiYRTBlm8L41n8nvw6UAqUCbvpFfU246gHPxW7sfWwqtF4FcYA==
+"@babel/cli@~7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.4.3.tgz#353048551306ff42e5855b788b6ccd9477289774"
+ integrity sha512-cbC5H9iTDV9H7sMxK5rUm18UbdVPNTPqgdzmQAkOUP3YLysgDWLZaysVAfylK49rgTlzL01a6tXyq9rCb3yLhQ==
dependencies:
commander "^2.8.1"
convert-source-map "^1.1.0"
fs-readdir-recursive "^1.1.0"
glob "^7.0.0"
- lodash "^4.17.10"
+ lodash "^4.17.11"
mkdirp "^0.5.1"
output-file-sync "^2.0.0"
slash "^2.0.0"
source-map "^0.5.0"
optionalDependencies:
- chokidar "^2.0.3"
+ chokidar "^2.0.4"
"@babel/code-frame@^7.0.0":
version "7.0.0"
@@ -1104,6 +1104,11 @@
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
+"@types/yup@0.26.9":
+ version "0.26.9"
+ resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.9.tgz#8a619ac4d2b8dcacb0d81345746018303b479919"
+ integrity sha512-C7HdLLs1ZNPbYeNsSX++fMosxWAwzVeUs9wc76XlKJrKvLEyNwXMDUjag75EVAPxlZ36YiRJ6iTy4zc5Dbtndw==
+
"@types/zen-observable@^0.5.3":
version "0.5.4"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.4.tgz#b863a4191e525206819e008097ebf0fb2e3a1cdc"
@@ -2132,22 +2137,22 @@ check-error@^1.0.2:
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
-cheerio@~1.0.0-rc.2:
- version "1.0.0-rc.2"
- resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
- integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=
+cheerio@~1.0.0-rc.3:
+ version "1.0.0-rc.3"
+ resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
+ integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==
dependencies:
css-select "~1.2.0"
- dom-serializer "~0.1.0"
+ dom-serializer "~0.1.1"
entities "~1.1.1"
htmlparser2 "^3.9.1"
lodash "^4.15.0"
parse5 "^3.0.1"
-chokidar@^2.0.3, chokidar@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.0.tgz#5fcb70d0b28ebe0867eb0f09d5f6a08f29a1efa0"
- integrity sha512-5t6G2SH8eO6lCvYOoUpaRnF5Qfd//gd7qJAkwRUw9qlGVkiQ13uwQngqbWWaurOsaAm9+kUGbITADxt6H0XFNQ==
+chokidar@^2.0.4, chokidar@^2.1.5:
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
+ integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==
dependencies:
anymatch "^2.0.0"
async-each "^1.0.1"
@@ -2159,7 +2164,7 @@ chokidar@^2.0.3, chokidar@^2.1.0:
normalize-path "^3.0.0"
path-is-absolute "^1.0.0"
readdirp "^2.2.1"
- upath "^1.1.0"
+ upath "^1.1.1"
optionalDependencies:
fsevents "^1.2.7"
@@ -2727,24 +2732,19 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
-dom-serializer@0, dom-serializer@~0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
- integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=
+dom-serializer@0, dom-serializer@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
+ integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
dependencies:
- domelementtype "~1.1.1"
- entities "~1.1.1"
+ domelementtype "^1.3.0"
+ entities "^1.1.1"
domelementtype@1, domelementtype@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
-domelementtype@~1.1.1:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
- integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=
-
domexception@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
@@ -3743,11 +3743,12 @@ graphql-request@~1.8.2:
dependencies:
cross-fetch "2.2.2"
-graphql-shield@~5.3.1:
- version "5.3.1"
- resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.1.tgz#34cff4d1bfdcc3caa6fc348afb11503dde1893cd"
- integrity sha512-vVJ7rjkR7miWi/Zspr7/ibmtdL2gEHagCtpsJY534DyRE70r+PurCp2kR/e1fZhb4JdmTYCS+sokyYfH974/+w==
+graphql-shield@~5.3.2:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.2.tgz#2d47907ed9882a0636cb8ade6087123309d215ef"
+ integrity sha512-fib7rSr5aS/WHL3+Aa5LXhcCuPGEIDXmzfGtFjUXkUiZ6E5u+bDSL+9KRXo/p14A28GkJF+1Vu1hlg9H/QFG1w==
dependencies:
+ "@types/yup" "0.26.9"
lightercollective "^0.2.0"
object-hash "^1.3.1"
yup "^0.27.0"
@@ -5677,12 +5678,12 @@ node-releases@^1.1.13:
dependencies:
semver "^5.3.0"
-nodemon@~1.18.10:
- version "1.18.10"
- resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.10.tgz#3ba63f64eb4c283cf3e4f75f30817e9d4f393afe"
- integrity sha512-we51yBb1TfEvZamFchRgcfLbVYgg0xlGbyXmOtbBzDwxwgewYS/YbZ5tnlnsH51+AoSTTsT3A2E/FloUbtH8cQ==
+nodemon@~1.18.11:
+ version "1.18.11"
+ resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.11.tgz#d836ab663776e7995570b963da5bfc807e53f6b8"
+ integrity sha512-KdN3tm1zkarlqNo4+W9raU3ihM4H15MVMSE/f9rYDZmFgDHAfAJsomYrHhApAkuUemYjFyEeXlpCOQ2v5gtBEw==
dependencies:
- chokidar "^2.1.0"
+ chokidar "^2.1.5"
debug "^3.1.0"
ignore-by-default "^1.0.1"
minimatch "^3.0.4"
@@ -7643,10 +7644,10 @@ unzip-response@^2.0.1:
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
-upath@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
- integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==
+upath@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
+ integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==
update-notifier@^2.5.0:
version "2.5.0"
diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js
index 8944b7c25..9478d8d4e 100644
--- a/cypress/integration/common/steps.js
+++ b/cypress/integration/common/steps.js
@@ -228,7 +228,7 @@ Then('I get redirected to {string}', route => {
})
Then('the post was saved successfully', () => {
- cy.get('.ds-card-header > .ds-heading').should('contain', lastPost.title)
+ cy.get('.ds-card-content > .ds-heading').should('contain', lastPost.title)
cy.get('.content').should('contain', lastPost.content)
})
diff --git a/package.json b/package.json
index be1e4a90d..b22dd158c 100644
--- a/package.json
+++ b/package.json
@@ -25,4 +25,4 @@
"neo4j-driver": "^1.7.3",
"npm-run-all": "^4.1.5"
}
-}
+}
\ No newline at end of file
diff --git a/webapp/components/Category/Readme.md b/webapp/components/Category/Readme.md
new file mode 100644
index 000000000..50e07f966
--- /dev/null
+++ b/webapp/components/Category/Readme.md
@@ -0,0 +1,7 @@
+### Example
+
+Category "IT, Internet & Data Privacy" with icon "mouse-cursor"
+
+```
+