mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of github.com:Human-Connection/Human-Connection into 1395-hashtags-imported-with-not-allowed-chars
This commit is contained in:
commit
d1309d2b8f
@ -49,7 +49,7 @@
|
||||
"apollo-client": "~2.6.4",
|
||||
"apollo-link-context": "~1.0.18",
|
||||
"apollo-link-http": "~1.5.15",
|
||||
"apollo-server": "~2.9.1",
|
||||
"apollo-server": "~2.9.3",
|
||||
"apollo-server-express": "^2.9.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"bcryptjs": "~2.4.3",
|
||||
@ -61,7 +61,7 @@
|
||||
"dotenv": "~8.1.0",
|
||||
"express": "^4.17.1",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"graphql": "^14.5.3",
|
||||
"graphql": "^14.5.4",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.5",
|
||||
@ -90,7 +90,7 @@
|
||||
"metascraper-video": "^5.6.5",
|
||||
"metascraper-youtube": "^5.6.3",
|
||||
"minimatch": "^3.0.4",
|
||||
"neo4j-driver": "~1.7.5",
|
||||
"neo4j-driver": "~1.7.6",
|
||||
"neo4j-graphql-js": "^2.7.2",
|
||||
"neode": "^0.3.2",
|
||||
"node-fetch": "~2.6.0",
|
||||
@ -117,12 +117,12 @@
|
||||
"babel-jest": "~24.9.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~6.2.2",
|
||||
"eslint": "~6.3.0",
|
||||
"eslint-config-prettier": "~6.1.0",
|
||||
"eslint-config-standard": "~14.1.0",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
"eslint-plugin-jest": "~22.15.2",
|
||||
"eslint-plugin-node": "~9.1.0",
|
||||
"eslint-plugin-jest": "~22.16.0",
|
||||
"eslint-plugin-node": "~9.2.0",
|
||||
"eslint-plugin-prettier": "~3.1.0",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
"eslint-plugin-standard": "~4.0.1",
|
||||
|
||||
@ -12,11 +12,9 @@ export default {
|
||||
CreatePost: setCreatedAt,
|
||||
CreateComment: setCreatedAt,
|
||||
CreateOrganization: setCreatedAt,
|
||||
CreateNotification: setCreatedAt,
|
||||
UpdateUser: setUpdatedAt,
|
||||
UpdatePost: setUpdatedAt,
|
||||
UpdateComment: setUpdatedAt,
|
||||
UpdateOrganization: setUpdatedAt,
|
||||
UpdateNotification: setUpdatedAt,
|
||||
},
|
||||
}
|
||||
|
||||
@ -4,13 +4,13 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
|
||||
if (!idsOfUsers.length) return
|
||||
|
||||
// Checked here, because it does not go through GraphQL checks at all in this file.
|
||||
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_post']
|
||||
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
|
||||
if (!reasonsAllowed.includes(reason)) {
|
||||
throw new Error('Notification reason is not allowed!')
|
||||
}
|
||||
if (
|
||||
(label === 'Post' && reason !== 'mentioned_in_post') ||
|
||||
(label === 'Comment' && !['mentioned_in_comment', 'comment_on_post'].includes(reason))
|
||||
(label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason))
|
||||
) {
|
||||
throw new Error('Notification does not fit the reason!')
|
||||
}
|
||||
@ -25,8 +25,9 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
|
||||
MATCH (user: User)
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)<-[:BLOCKED]-(author)
|
||||
CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt })
|
||||
MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = $createdAt
|
||||
`
|
||||
break
|
||||
}
|
||||
@ -37,20 +38,22 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)<-[:BLOCKED]-(author)
|
||||
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
||||
CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt })
|
||||
MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = $createdAt
|
||||
`
|
||||
break
|
||||
}
|
||||
case 'comment_on_post': {
|
||||
case 'commented_on_post': {
|
||||
cypher = `
|
||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
||||
MATCH (user: User)
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)<-[:BLOCKED]-(author)
|
||||
AND NOT (author)<-[:BLOCKED]-(user)
|
||||
CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt })
|
||||
MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = $createdAt
|
||||
`
|
||||
break
|
||||
}
|
||||
@ -105,7 +108,7 @@ const handleCreateComment = async (resolve, root, args, context, resolveInfo) =>
|
||||
return record.get('user')
|
||||
})
|
||||
if (context.user.id !== postAuthor.id) {
|
||||
await notifyUsers('Comment', comment.id, [postAuthor.id], 'comment_on_post', context)
|
||||
await notifyUsers('Comment', comment.id, [postAuthor.id], 'commented_on_post', context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -77,14 +77,18 @@ afterEach(async () => {
|
||||
describe('notifications', () => {
|
||||
const notificationQuery = gql`
|
||||
query($read: Boolean) {
|
||||
currentUser {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
read
|
||||
reason
|
||||
post {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
id
|
||||
content
|
||||
}
|
||||
comment {
|
||||
... on Comment {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
@ -154,18 +158,18 @@ describe('notifications', () => {
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'comment_on_post',
|
||||
post: null,
|
||||
comment: {
|
||||
content: commentContent,
|
||||
},
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
@ -183,11 +187,7 @@ describe('notifications', () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [],
|
||||
},
|
||||
},
|
||||
data: { notifications: [] },
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
await expect(
|
||||
@ -211,11 +211,7 @@ describe('notifications', () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [],
|
||||
},
|
||||
},
|
||||
data: { notifications: [] },
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
await expect(
|
||||
@ -253,18 +249,18 @@ describe('notifications', () => {
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
comment: null,
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'mentioned_in_post',
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'p47',
|
||||
content: expectedContent,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
@ -278,7 +274,7 @@ describe('notifications', () => {
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
describe('many times', () => {
|
||||
describe('updates the post and mentions me again', () => {
|
||||
const updatePostAction = async () => {
|
||||
const updatedContent = `
|
||||
One more mention to
|
||||
@ -307,33 +303,25 @@ describe('notifications', () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
}
|
||||
|
||||
it('creates exactly one more notification', async () => {
|
||||
it('creates no duplicate notification for the same resource', async () => {
|
||||
const expectedUpdatedContent =
|
||||
'<br>One more mention to<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>and again:<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>and again<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>'
|
||||
await createPostAction()
|
||||
await updatePostAction()
|
||||
const expectedContent =
|
||||
'<br>One more mention to<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>and again:<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>and again<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>'
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
comment: null,
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'mentioned_in_post',
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'p47',
|
||||
content: expectedUpdatedContent,
|
||||
},
|
||||
{
|
||||
read: false,
|
||||
reason: 'mentioned_in_post',
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
comment: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
@ -345,6 +333,68 @@ describe('notifications', () => {
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
describe('if the notification was marked as read earlier', () => {
|
||||
const markAsReadAction = async () => {
|
||||
const mutation = gql`
|
||||
mutation($id: ID!) {
|
||||
markAsRead(id: $id) {
|
||||
read
|
||||
}
|
||||
}
|
||||
`
|
||||
await mutate({ mutation, variables: { id: 'p47' } })
|
||||
}
|
||||
|
||||
describe('but the next mention happens after the notification was marked as read', () => {
|
||||
it('sets the `read` attribute to false again', async () => {
|
||||
await createPostAction()
|
||||
await markAsReadAction()
|
||||
const {
|
||||
data: {
|
||||
notifications: [{ read: readBefore }],
|
||||
},
|
||||
} = await query({
|
||||
query: notificationQuery,
|
||||
})
|
||||
await updatePostAction()
|
||||
const {
|
||||
data: {
|
||||
notifications: [{ read: readAfter }],
|
||||
},
|
||||
} = await query({
|
||||
query: notificationQuery,
|
||||
})
|
||||
expect(readBefore).toEqual(true)
|
||||
expect(readAfter).toEqual(false)
|
||||
})
|
||||
|
||||
it('updates the `createdAt` attribute', async () => {
|
||||
await createPostAction()
|
||||
await markAsReadAction()
|
||||
const {
|
||||
data: {
|
||||
notifications: [{ createdAt: createdAtBefore }],
|
||||
},
|
||||
} = await query({
|
||||
query: notificationQuery,
|
||||
})
|
||||
await updatePostAction()
|
||||
const {
|
||||
data: {
|
||||
notifications: [{ createdAt: createdAtAfter }],
|
||||
},
|
||||
} = await query({
|
||||
query: notificationQuery,
|
||||
})
|
||||
expect(createdAtBefore).toBeTruthy()
|
||||
expect(Date.parse(createdAtBefore)).toEqual(expect.any(Number))
|
||||
expect(createdAtAfter).toBeTruthy()
|
||||
expect(Date.parse(createdAtAfter)).toEqual(expect.any(Number))
|
||||
expect(createdAtBefore).not.toEqual(createdAtAfter)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('but the author of the post blocked me', () => {
|
||||
@ -355,11 +405,7 @@ describe('notifications', () => {
|
||||
it('sends no notification', async () => {
|
||||
await createPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [],
|
||||
},
|
||||
},
|
||||
data: { notifications: [] },
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
await expect(
|
||||
@ -397,18 +443,18 @@ describe('notifications', () => {
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'mentioned_in_comment',
|
||||
post: null,
|
||||
comment: {
|
||||
content: commentContent,
|
||||
},
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'mentioned_in_comment',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
@ -440,11 +486,7 @@ describe('notifications', () => {
|
||||
it('sends no notification', async () => {
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [],
|
||||
},
|
||||
},
|
||||
data: { notifications: [] },
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
await expect(
|
||||
|
||||
@ -41,32 +41,6 @@ const isMySocialMedia = rule({
|
||||
return socialMedia.ownedBy.node.id === user.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)
|
||||
})
|
||||
|
||||
/* TODO: decide if we want to remove this check: the check
|
||||
* `onlyEnabledContent` throws authorization errors only if you have
|
||||
* arguments for `disabled` or `deleted` assuming these are filter
|
||||
@ -149,7 +123,6 @@ const permissions = shield(
|
||||
Category: allow,
|
||||
Tag: allow,
|
||||
Report: isModerator,
|
||||
Notification: isAdmin,
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
Post: or(onlyEnabledContent, isModerator),
|
||||
@ -160,6 +133,7 @@ const permissions = shield(
|
||||
PostsEmotionsCountByEmotion: allow,
|
||||
PostsEmotionsByCurrentUser: allow,
|
||||
blockedUsers: isAuthenticated,
|
||||
notifications: isAuthenticated,
|
||||
},
|
||||
Mutation: {
|
||||
'*': deny,
|
||||
@ -168,7 +142,6 @@ const permissions = shield(
|
||||
Signup: isAdmin,
|
||||
SignupVerification: allow,
|
||||
CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)),
|
||||
UpdateNotification: belongsToMe,
|
||||
UpdateUser: onlyYourself,
|
||||
CreatePost: isAuthenticated,
|
||||
UpdatePost: isAuthor,
|
||||
@ -198,6 +171,7 @@ const permissions = shield(
|
||||
RemovePostEmotions: isAuthenticated,
|
||||
block: isAuthenticated,
|
||||
unblock: isAuthenticated,
|
||||
markAsRead: isAuthenticated,
|
||||
},
|
||||
User: {
|
||||
email: isMyOwn,
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
module.exports = {
|
||||
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',
|
||||
target: 'User',
|
||||
direction: 'out',
|
||||
},
|
||||
post: {
|
||||
type: 'relationship',
|
||||
relationship: 'NOTIFIED',
|
||||
target: 'Post',
|
||||
direction: 'in',
|
||||
},
|
||||
}
|
||||
@ -7,6 +7,5 @@ export default {
|
||||
EmailAddress: require('./EmailAddress.js'),
|
||||
SocialMedia: require('./SocialMedia.js'),
|
||||
Post: require('./Post.js'),
|
||||
Notification: require('./Notification.js'),
|
||||
Category: require('./Category.js'),
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ export default applyScalars(
|
||||
'Statistics',
|
||||
'LoggedInUser',
|
||||
'SocialMedia',
|
||||
'NOTIFIED',
|
||||
],
|
||||
// add 'User' here as soon as possible
|
||||
},
|
||||
@ -32,6 +33,7 @@ export default applyScalars(
|
||||
'Statistics',
|
||||
'LoggedInUser',
|
||||
'SocialMedia',
|
||||
'NOTIFIED',
|
||||
],
|
||||
// add 'User' here as soon as possible
|
||||
},
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
@ -52,4 +53,13 @@ export default {
|
||||
return comment
|
||||
},
|
||||
},
|
||||
Comment: {
|
||||
...Resolver('Comment', {
|
||||
hasOne: {
|
||||
author: '<-[:WROTE]-(related:User)',
|
||||
post: '-[:COMMENTS]->(related:Post)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@ -61,7 +61,6 @@ export default function Resolver(type, options = {}) {
|
||||
const id = parent[idAttribute]
|
||||
const statement = `
|
||||
MATCH(u:${type} {${idAttribute}: {id}})${connection}
|
||||
WHERE NOT related.deleted = true AND NOT related.disabled = true
|
||||
RETURN COUNT(DISTINCT(related)) as count
|
||||
`
|
||||
const result = await instance.cypher(statement, { id })
|
||||
|
||||
@ -1,14 +1,80 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
const resourceTypes = ['Post', 'Comment']
|
||||
|
||||
const transformReturnType = record => {
|
||||
return {
|
||||
...record.get('notification').properties,
|
||||
from: {
|
||||
__typename: record.get('resource').labels.find(l => resourceTypes.includes(l)),
|
||||
...record.get('resource').properties,
|
||||
},
|
||||
to: {
|
||||
...record.get('user').properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Notification: (object, params, context, resolveInfo) => {
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
notifications: async (parent, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
const session = context.driver.session()
|
||||
let notifications
|
||||
let whereClause
|
||||
let orderByClause
|
||||
switch (args.read) {
|
||||
case true:
|
||||
whereClause = 'WHERE notification.read = TRUE'
|
||||
break
|
||||
case false:
|
||||
whereClause = 'WHERE notification.read = FALSE'
|
||||
break
|
||||
default:
|
||||
whereClause = ''
|
||||
}
|
||||
switch (args.orderBy) {
|
||||
case 'createdAt_asc':
|
||||
orderByClause = 'ORDER BY notification.createdAt ASC'
|
||||
break
|
||||
case 'createdAt_desc':
|
||||
orderByClause = 'ORDER BY notification.createdAt DESC'
|
||||
break
|
||||
default:
|
||||
orderByClause = ''
|
||||
}
|
||||
|
||||
try {
|
||||
const cypher = `
|
||||
MATCH (resource)-[notification:NOTIFIED]->(user:User {id:$id})
|
||||
${whereClause}
|
||||
RETURN resource, notification, user
|
||||
${orderByClause}
|
||||
`
|
||||
const result = await session.run(cypher, { id: currentUser.id })
|
||||
notifications = await result.records.map(transformReturnType)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
return notifications
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
UpdateNotification: (object, params, context, resolveInfo) => {
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
markAsRead: async (parent, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
const session = context.driver.session()
|
||||
let notification
|
||||
try {
|
||||
const cypher = `
|
||||
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
|
||||
SET notification.read = TRUE
|
||||
RETURN resource, notification, user
|
||||
`
|
||||
const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id })
|
||||
const notifications = await result.records.map(transformReturnType)
|
||||
notification = notifications[0]
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
return notification
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,397 +1,309 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import Factory from '../../seed/factories'
|
||||
import { host, login, gql } from '../../jest/helpers'
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import { gql } from '../../jest/helpers'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../.././server'
|
||||
|
||||
let client
|
||||
const factory = Factory()
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
const userParams = {
|
||||
id: 'you',
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
}
|
||||
const categoryIds = ['cat9']
|
||||
|
||||
let authenticatedUser
|
||||
let user
|
||||
let variables
|
||||
let query
|
||||
let mutate
|
||||
|
||||
beforeAll(() => {
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
user: authenticatedUser,
|
||||
}
|
||||
},
|
||||
})
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', userParams)
|
||||
await instance.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
})
|
||||
authenticatedUser = null
|
||||
variables = { orderBy: 'createdAt_asc' }
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('Notification', () => {
|
||||
const notificationQuery = gql`
|
||||
query {
|
||||
Notification {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(notificationQuery)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
describe('given some notifications', () => {
|
||||
beforeEach(async () => {
|
||||
user = await factory.create('User', userParams)
|
||||
await factory.create('User', { id: 'neighbor' })
|
||||
await Promise.all(setupNotifications.map(s => neode.cypher(s)))
|
||||
})
|
||||
})
|
||||
const setupNotifications = [
|
||||
`MATCH(user:User {id: 'neighbor'})
|
||||
MERGE (:Post {id: 'p1', content: 'Not for you'})
|
||||
-[:NOTIFIED {createdAt: "2019-08-29T17:33:48.651Z", read: false, reason: "mentioned_in_post"}]
|
||||
->(user);
|
||||
`,
|
||||
`MATCH(user:User {id: 'you'})
|
||||
MERGE (:Post {id: 'p2', content: 'Already seen post mentioning'})
|
||||
-[:NOTIFIED {createdAt: "2019-08-30T17:33:48.651Z", read: true, reason: "mentioned_in_post"}]
|
||||
->(user);
|
||||
`,
|
||||
`MATCH(user:User {id: 'you'})
|
||||
MERGE (:Post {id: 'p3', content: 'You have been mentioned in a post'})
|
||||
-[:NOTIFIED {createdAt: "2019-08-31T17:33:48.651Z", read: false, reason: "mentioned_in_post"}]
|
||||
->(user);
|
||||
`,
|
||||
`MATCH(user:User {id: 'you'})
|
||||
MATCH(post:Post {id: 'p3'})
|
||||
CREATE (comment:Comment {id: 'c1', content: 'You have seen this comment mentioning already'})
|
||||
MERGE (comment)-[:COMMENTS]->(post)
|
||||
MERGE (comment)
|
||||
-[:NOTIFIED {createdAt: "2019-08-30T15:33:48.651Z", read: true, reason: "mentioned_in_comment"}]
|
||||
->(user);
|
||||
`,
|
||||
`MATCH(user:User {id: 'you'})
|
||||
MATCH(post:Post {id: 'p3'})
|
||||
CREATE (comment:Comment {id: 'c2', content: 'You have been mentioned in a comment'})
|
||||
MERGE (comment)-[:COMMENTS]->(post)
|
||||
MERGE (comment)
|
||||
-[:NOTIFIED {createdAt: "2019-08-30T19:33:48.651Z", read: false, reason: "mentioned_in_comment"}]
|
||||
->(user);
|
||||
`,
|
||||
`MATCH(user:User {id: 'neighbor'})
|
||||
MATCH(post:Post {id: 'p3'})
|
||||
CREATE (comment:Comment {id: 'c3', content: 'Somebody else was mentioned in a comment'})
|
||||
MERGE (comment)-[:COMMENTS]->(post)
|
||||
MERGE (comment)
|
||||
-[:NOTIFIED {createdAt: "2019-09-01T17:33:48.651Z", read: false, reason: "mentioned_in_comment"}]
|
||||
->(user);
|
||||
`,
|
||||
]
|
||||
|
||||
describe('currentUser notifications', () => {
|
||||
const variables = {}
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
client = new GraphQLClient(host, {
|
||||
headers,
|
||||
})
|
||||
})
|
||||
|
||||
describe('given some notifications', () => {
|
||||
beforeEach(async () => {
|
||||
const neighborParams = {
|
||||
email: 'neighbor@example.org',
|
||||
password: '1234',
|
||||
id: 'neighbor',
|
||||
describe('notifications', () => {
|
||||
const notificationQuery = gql`
|
||||
query($read: Boolean, $orderBy: NotificationOrdering) {
|
||||
notifications(read: $read, orderBy: $orderBy) {
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
read
|
||||
createdAt
|
||||
}
|
||||
await Promise.all([
|
||||
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)
|
||||
await factory.create('Post', { id: 'p1', categoryIds })
|
||||
await Promise.all([
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'post-mention-not-for-you',
|
||||
to: 'neighbor',
|
||||
}),
|
||||
factory.relate('Notification', 'Post', {
|
||||
from: 'p1',
|
||||
to: 'post-mention-not-for-you',
|
||||
}),
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'post-mention-unseen',
|
||||
to: 'you',
|
||||
}),
|
||||
factory.relate('Notification', 'Post', {
|
||||
from: 'p1',
|
||||
to: 'post-mention-unseen',
|
||||
}),
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'post-mention-already-seen',
|
||||
to: 'you',
|
||||
}),
|
||||
factory.relate('Notification', 'Post', {
|
||||
from: 'p1',
|
||||
to: 'post-mention-already-seen',
|
||||
}),
|
||||
])
|
||||
// Comment and its notifications
|
||||
await Promise.all([
|
||||
factory.create('Comment', {
|
||||
id: 'c1',
|
||||
postId: 'p1',
|
||||
}),
|
||||
])
|
||||
await Promise.all([
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'comment-mention-not-for-you',
|
||||
to: 'neighbor',
|
||||
}),
|
||||
factory.relate('Notification', 'Comment', {
|
||||
from: 'c1',
|
||||
to: 'comment-mention-not-for-you',
|
||||
}),
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'comment-mention-unseen',
|
||||
to: 'you',
|
||||
}),
|
||||
factory.relate('Notification', 'Comment', {
|
||||
from: 'c1',
|
||||
to: 'comment-mention-unseen',
|
||||
}),
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'comment-mention-already-seen',
|
||||
to: 'you',
|
||||
}),
|
||||
factory.relate('Notification', 'Comment', {
|
||||
from: 'c1',
|
||||
to: 'comment-mention-already-seen',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
describe('filter for read: false', () => {
|
||||
const queryCurrentUserNotificationsFilterRead = gql`
|
||||
query($read: Boolean) {
|
||||
currentUser {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
id
|
||||
post {
|
||||
id
|
||||
}
|
||||
comment {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = { read: false }
|
||||
it('returns only unread notifications of current user', async () => {
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: expect.arrayContaining([
|
||||
{
|
||||
id: 'post-mention-unseen',
|
||||
post: {
|
||||
id: 'p1',
|
||||
},
|
||||
comment: null,
|
||||
},
|
||||
{
|
||||
id: 'comment-mention-unseen',
|
||||
post: null,
|
||||
comment: {
|
||||
id: 'c1',
|
||||
},
|
||||
},
|
||||
]),
|
||||
},
|
||||
}
|
||||
await expect(
|
||||
client.request(queryCurrentUserNotificationsFilterRead, variables),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('no filters', () => {
|
||||
const queryCurrentUserNotifications = gql`
|
||||
query {
|
||||
currentUser {
|
||||
notifications(orderBy: createdAt_desc) {
|
||||
id
|
||||
post {
|
||||
id
|
||||
}
|
||||
comment {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
it('returns all notifications of current user', async () => {
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: expect.arrayContaining([
|
||||
{
|
||||
id: 'post-mention-unseen',
|
||||
post: {
|
||||
id: 'p1',
|
||||
},
|
||||
comment: null,
|
||||
},
|
||||
{
|
||||
id: 'post-mention-already-seen',
|
||||
post: {
|
||||
id: 'p1',
|
||||
},
|
||||
comment: null,
|
||||
},
|
||||
{
|
||||
id: 'comment-mention-unseen',
|
||||
comment: {
|
||||
id: 'c1',
|
||||
},
|
||||
post: null,
|
||||
},
|
||||
{
|
||||
id: 'comment-mention-already-seen',
|
||||
comment: {
|
||||
id: 'c1',
|
||||
},
|
||||
post: null,
|
||||
},
|
||||
]),
|
||||
},
|
||||
}
|
||||
await expect(client.request(queryCurrentUserNotifications, variables)).resolves.toEqual(
|
||||
expected,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UpdateNotification', () => {
|
||||
const mutationUpdateNotification = gql`
|
||||
mutation($id: ID!, $read: Boolean) {
|
||||
UpdateNotification(id: $id, read: $read) {
|
||||
id
|
||||
read
|
||||
}
|
||||
}
|
||||
`
|
||||
const variablesPostUpdateNotification = {
|
||||
id: 'post-mention-to-be-updated',
|
||||
read: true,
|
||||
}
|
||||
const variablesCommentUpdateNotification = {
|
||||
id: 'comment-mention-to-be-updated',
|
||||
read: true,
|
||||
}
|
||||
|
||||
describe('given some notifications', () => {
|
||||
let headers
|
||||
|
||||
beforeEach(async () => {
|
||||
const mentionedParams = {
|
||||
id: 'mentioned-1',
|
||||
email: 'mentioned@example.org',
|
||||
password: '1234',
|
||||
slug: 'mentioned',
|
||||
}
|
||||
await Promise.all([
|
||||
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)
|
||||
await factory.create('Post', { id: 'p1', categoryIds })
|
||||
await Promise.all([
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'post-mention-to-be-updated',
|
||||
to: 'mentioned-1',
|
||||
}),
|
||||
factory.relate('Notification', 'Post', {
|
||||
from: 'p1',
|
||||
to: 'post-mention-to-be-updated',
|
||||
}),
|
||||
])
|
||||
// Comment and its notifications
|
||||
await Promise.all([
|
||||
factory.create('Comment', {
|
||||
id: 'c1',
|
||||
postId: 'p1',
|
||||
}),
|
||||
])
|
||||
await Promise.all([
|
||||
factory.relate('Notification', 'User', {
|
||||
from: 'comment-mention-to-be-updated',
|
||||
to: 'mentioned-1',
|
||||
}),
|
||||
factory.relate('Notification', 'Comment', {
|
||||
from: 'p1',
|
||||
to: 'comment-mention-to-be-updated',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
`
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(
|
||||
client.request(mutationUpdateNotification, variablesPostUpdateNotification),
|
||||
).rejects.toThrow('Not Authorised')
|
||||
const result = await query({ query: notificationQuery })
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
client = new GraphQLClient(host, {
|
||||
headers,
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
describe('no filters', () => {
|
||||
it('returns all notifications of current user', async () => {
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
content: 'You have seen this comment mentioning already',
|
||||
},
|
||||
read: true,
|
||||
createdAt: '2019-08-30T15:33:48.651Z',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
content: 'Already seen post mentioning',
|
||||
},
|
||||
read: true,
|
||||
createdAt: '2019-08-30T17:33:48.651Z',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
content: 'You have been mentioned in a comment',
|
||||
},
|
||||
read: false,
|
||||
createdAt: '2019-08-30T19:33:48.651Z',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
content: 'You have been mentioned in a post',
|
||||
},
|
||||
read: false,
|
||||
createdAt: '2019-08-31T17:33:48.651Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
await expect(query({ query: notificationQuery, variables })).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
await expect(
|
||||
client.request(mutationUpdateNotification, variablesPostUpdateNotification),
|
||||
).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 post notification', async () => {
|
||||
const expected = {
|
||||
UpdateNotification: {
|
||||
id: 'post-mention-to-be-updated',
|
||||
read: true,
|
||||
describe('filter for read: false', () => {
|
||||
it('returns only unread notifications of current user', async () => {
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
content: 'You have been mentioned in a comment',
|
||||
},
|
||||
read: false,
|
||||
createdAt: '2019-08-30T19:33:48.651Z',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
content: 'You have been mentioned in a post',
|
||||
},
|
||||
read: false,
|
||||
createdAt: '2019-08-31T17:33:48.651Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
await expect(
|
||||
client.request(mutationUpdateNotification, variablesPostUpdateNotification),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
it('updates comment notification', async () => {
|
||||
const expected = {
|
||||
UpdateNotification: {
|
||||
id: 'comment-mention-to-be-updated',
|
||||
read: true,
|
||||
},
|
||||
}
|
||||
await expect(
|
||||
client.request(mutationUpdateNotification, variablesCommentUpdateNotification),
|
||||
query({ query: notificationQuery, variables: { ...variables, read: false } }),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('markAsRead', () => {
|
||||
const markAsReadMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
markAsRead(id: $id) {
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
read
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const result = await mutate({
|
||||
mutation: markAsReadMutation,
|
||||
variables: { ...variables, id: 'p1' },
|
||||
})
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
describe('not being notified at all', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
id: 'p1',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns null', async () => {
|
||||
const response = await mutate({ mutation: markAsReadMutation, variables })
|
||||
expect(response.data.markAsRead).toEqual(null)
|
||||
expect(response.errors).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('being notified', () => {
|
||||
describe('on a post', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
id: 'p3',
|
||||
}
|
||||
})
|
||||
|
||||
it('updates `read` attribute and returns NOTIFIED relationship', async () => {
|
||||
const { data } = await mutate({ mutation: markAsReadMutation, variables })
|
||||
expect(data).toEqual({
|
||||
markAsRead: {
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
content: 'You have been mentioned in a post',
|
||||
},
|
||||
read: true,
|
||||
createdAt: '2019-08-31T17:33:48.651Z',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('but notification was already marked as read', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
id: 'p2',
|
||||
}
|
||||
})
|
||||
it('returns null', async () => {
|
||||
const response = await mutate({ mutation: markAsReadMutation, variables })
|
||||
expect(response.data.markAsRead).toEqual(null)
|
||||
expect(response.errors).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('on a comment', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
id: 'c2',
|
||||
}
|
||||
})
|
||||
|
||||
it('updates `read` attribute and returns NOTIFIED relationship', async () => {
|
||||
const { data } = await mutate({ mutation: markAsReadMutation, variables })
|
||||
expect(data).toEqual({
|
||||
markAsRead: {
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
content: 'You have been mentioned in a comment',
|
||||
},
|
||||
read: true,
|
||||
createdAt: '2019-08-30T19:33:48.651Z',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,6 +3,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import fileUpload from './fileUpload'
|
||||
import { getBlockedUsers, getBlockedByUsers } from './users.js'
|
||||
import { mergeWith, isArray } from 'lodash'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const filterForBlockedUsers = async (params, context) => {
|
||||
if (!context.user) return params
|
||||
@ -11,6 +12,8 @@ const filterForBlockedUsers = async (params, context) => {
|
||||
getBlockedByUsers(context),
|
||||
])
|
||||
const badIds = [...blockedByUsers.map(b => b.id), ...blockedUsers.map(b => b.id)]
|
||||
if (!badIds.length) return params
|
||||
|
||||
params.filter = mergeWith(
|
||||
params.filter,
|
||||
{
|
||||
@ -179,4 +182,46 @@ export default {
|
||||
return emoted
|
||||
},
|
||||
},
|
||||
Post: {
|
||||
...Resolver('Post', {
|
||||
hasMany: {
|
||||
tags: '-[:TAGGED]->(related:Tag)',
|
||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||
comments: '<-[:COMMENTS]-(related:Comment)',
|
||||
shoutedBy: '<-[:SHOUTED]-(related:User)',
|
||||
emotions: '<-[related:EMOTED]',
|
||||
},
|
||||
hasOne: {
|
||||
author: '<-[:WROTE]-(related:User)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
},
|
||||
count: {
|
||||
shoutedCount:
|
||||
'<-[:SHOUTED]-(related:User) WHERE NOT related.deleted = true AND NOT related.disabled = true',
|
||||
emotionsCount: '<-[related:EMOTED]-(:User)',
|
||||
},
|
||||
boolean: {
|
||||
shoutedByCurrentUser:
|
||||
'<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
},
|
||||
}),
|
||||
relatedContributions: async (parent, params, context, resolveInfo) => {
|
||||
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
|
||||
const { id } = parent
|
||||
const statement = `
|
||||
MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||
RETURN DISTINCT post
|
||||
LIMIT 10
|
||||
`
|
||||
let relatedContributions
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
const result = await session.run(statement, { id })
|
||||
relatedContributions = result.records.map(r => r.get('post').properties)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
return relatedContributions
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -147,12 +147,15 @@ export default {
|
||||
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
},
|
||||
count: {
|
||||
contributionsCount: '-[:WROTE]->(related:Post)',
|
||||
contributionsCount:
|
||||
'-[:WROTE]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true',
|
||||
friendsCount: '<-[:FRIENDS]->(related:User)',
|
||||
followingCount: '-[:FOLLOWS]->(related:User)',
|
||||
followedByCount: '<-[:FOLLOWS]-(related:User)',
|
||||
commentedCount: '-[:WROTE]->(:Comment)-[:COMMENTS]->(related:Post)',
|
||||
shoutedCount: '-[:SHOUTED]->(related:Post)',
|
||||
commentedCount:
|
||||
'-[:WROTE]->(c:Comment)-[:COMMENTS]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true',
|
||||
shoutedCount:
|
||||
'-[:SHOUTED]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true',
|
||||
badgesCount: '<-[:REWARDED]-(related:Badge)',
|
||||
},
|
||||
hasOne: {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
enum ReasonNotification {
|
||||
mentioned_in_post
|
||||
mentioned_in_comment
|
||||
comment_on_post
|
||||
}
|
||||
commented_on_post
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ type Query {
|
||||
currentUser: User
|
||||
# Get the latest Network Statistics
|
||||
statistics: Statistics!
|
||||
findPosts(query: String!, limit: Int = 10): [Post]!
|
||||
findPosts(query: String!, limit: Int = 10, filter: _PostFilter): [Post]!
|
||||
@cypher(
|
||||
statement: """
|
||||
CALL db.index.fulltext.queryNodes('full_text_search', $query)
|
||||
|
||||
28
backend/src/schema/types/type/NOTIFIED.gql
Normal file
28
backend/src/schema/types/type/NOTIFIED.gql
Normal file
@ -0,0 +1,28 @@
|
||||
type NOTIFIED {
|
||||
from: NotificationSource
|
||||
to: User
|
||||
createdAt: String
|
||||
read: Boolean
|
||||
reason: NotificationReason
|
||||
}
|
||||
|
||||
union NotificationSource = Post | Comment
|
||||
|
||||
enum NotificationOrdering {
|
||||
createdAt_asc
|
||||
createdAt_desc
|
||||
}
|
||||
|
||||
enum NotificationReason {
|
||||
mentioned_in_post
|
||||
mentioned_in_comment
|
||||
commented_on_post
|
||||
}
|
||||
|
||||
type Query {
|
||||
notifications(read: Boolean, orderBy: NotificationOrdering): [NOTIFIED]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
markAsRead(id: ID!): NOTIFIED
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
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")
|
||||
}
|
||||
@ -24,8 +24,6 @@ type User {
|
||||
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)")
|
||||
|
||||
@ -64,7 +62,7 @@ type User {
|
||||
)
|
||||
|
||||
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
||||
commentedCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment)-[:COMMENTS]->(p:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true AND NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
|
||||
commentedCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(:Comment)-[:COMMENTS]->(p:Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
|
||||
|
||||
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)")
|
||||
|
||||
@ -8,7 +8,6 @@ import createComment from './comments.js'
|
||||
import createCategory from './categories.js'
|
||||
import createTag from './tags.js'
|
||||
import createReport from './reports.js'
|
||||
import createNotification from './notifications.js'
|
||||
|
||||
export const seedServerHost = 'http://127.0.0.1:4001'
|
||||
|
||||
@ -31,7 +30,6 @@ const factories = {
|
||||
Category: createCategory,
|
||||
Tag: createTag,
|
||||
Report: createReport,
|
||||
Notification: createNotification,
|
||||
}
|
||||
|
||||
export const cleanDatabase = async (options = {}) => {
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
export default function(params) {
|
||||
const { id = uuid(), read = false } = params
|
||||
|
||||
return {
|
||||
mutation: `
|
||||
mutation($id: ID, $read: Boolean) {
|
||||
CreateNotification(id: $id, read: $read) {
|
||||
id
|
||||
read
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id, read },
|
||||
}
|
||||
}
|
||||
@ -270,7 +270,7 @@ import Factory from './factories'
|
||||
const hashtag1 =
|
||||
'See <a class="hashtag" href="/search/hashtag/NaturphilosophieYoga">#NaturphilosophieYoga</a> can really help you!'
|
||||
const hashtagAndMention1 =
|
||||
'The new physics of <a class="hashtag" href="/search/hashtag/QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" href="/search/hashtag/QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u3" href="/profile/u1">@peter-lustig</a> got that already. ;-)'
|
||||
'The new physics of <a class="hashtag" href="/search/hashtag/QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" href="/search/hashtag/QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> got that already. ;-)'
|
||||
|
||||
await Promise.all([
|
||||
asAdmin.create('Post', {
|
||||
|
||||
@ -689,7 +689,7 @@
|
||||
core-js "^2.6.5"
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.5.5":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
||||
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
|
||||
@ -1546,6 +1546,14 @@ apollo-cache-control@0.8.2:
|
||||
apollo-server-env "2.4.2"
|
||||
graphql-extensions "0.10.1"
|
||||
|
||||
apollo-cache-control@^0.8.4:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.4.tgz#a3650d5e4173953e2a3af995bea62147f1ffe4d7"
|
||||
integrity sha512-IZ1d3AXZtkZhLYo0kWqTbZ6nqLFaeUvLdMESs+9orMadBZ7mvzcAfBwrhKyCWPGeAAZ/jKv8FtYHybpchHgFAg==
|
||||
dependencies:
|
||||
apollo-server-env "^2.4.3"
|
||||
graphql-extensions "^0.10.3"
|
||||
|
||||
apollo-cache-inmemory@~1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz#826861d20baca4abc45f7ca7a874105905b8525d"
|
||||
@ -1587,7 +1595,15 @@ apollo-datasource@0.6.2:
|
||||
apollo-server-caching "0.5.0"
|
||||
apollo-server-env "2.4.2"
|
||||
|
||||
apollo-engine-reporting-protobuf@0.4.0:
|
||||
apollo-datasource@^0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.6.3.tgz#b31e089e52adb92fabb536ab8501c502573ffe13"
|
||||
integrity sha512-gRYyFVpJgHE2hhS+VxMeOerxXQ/QYxWG7T6QddfugJWYAG9DRCl65e2b7txcGq2NP3r+O1iCm4GNwhRBDJbd8A==
|
||||
dependencies:
|
||||
apollo-server-caching "^0.5.0"
|
||||
apollo-server-env "^2.4.3"
|
||||
|
||||
apollo-engine-reporting-protobuf@0.4.0, apollo-engine-reporting-protobuf@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.0.tgz#e34c192d86493b33a73181fd6be75721559111ec"
|
||||
integrity sha512-cXHZSienkis8v4RhqB3YG3DkaksqLpcxApRLTpRMs7IXNozgV7CUPYGFyFBEra1ZFgUyHXx4G9MpelV+n2cCfA==
|
||||
@ -1607,6 +1623,19 @@ apollo-engine-reporting@1.4.4:
|
||||
async-retry "^1.2.1"
|
||||
graphql-extensions "0.10.1"
|
||||
|
||||
apollo-engine-reporting@^1.4.6:
|
||||
version "1.4.6"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.6.tgz#83af6689c4ab82d1c62c3f5dde7651975508114f"
|
||||
integrity sha512-acfb7oFnru/8YQdY4x6+7WJbZfzdVETI8Cl+9ImgUrvUnE8P+f2SsGTKXTC1RuUvve4c56PAvaPgE+z8X1a1Mw==
|
||||
dependencies:
|
||||
apollo-engine-reporting-protobuf "^0.4.0"
|
||||
apollo-graphql "^0.3.3"
|
||||
apollo-server-caching "^0.5.0"
|
||||
apollo-server-env "^2.4.3"
|
||||
apollo-server-types "^0.2.4"
|
||||
async-retry "^1.2.1"
|
||||
graphql-extensions "^0.10.3"
|
||||
|
||||
apollo-env@0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.5.1.tgz#b9b0195c16feadf0fe9fd5563edb0b9b7d9e97d3"
|
||||
@ -1668,7 +1697,7 @@ apollo-link@^1.0.0, apollo-link@^1.2.12, apollo-link@^1.2.3:
|
||||
tslib "^1.9.3"
|
||||
zen-observable-ts "^0.8.19"
|
||||
|
||||
apollo-server-caching@0.5.0:
|
||||
apollo-server-caching@0.5.0, apollo-server-caching@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.5.0.tgz#446a37ce2d4e24c81833e276638330a634f7bd46"
|
||||
integrity sha512-l7ieNCGxUaUAVAAp600HjbUJxVaxjJygtPV0tPTe1Q3HkPy6LEWoY6mNHV7T268g1hxtPTxcdRu7WLsJrg7ufw==
|
||||
@ -1702,6 +1731,33 @@ apollo-server-core@2.9.1:
|
||||
subscriptions-transport-ws "^0.9.11"
|
||||
ws "^6.0.0"
|
||||
|
||||
apollo-server-core@^2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.3.tgz#918f836c8215d371935c831c72d0840c7bf0250f"
|
||||
integrity sha512-KQpOM3nAXdMqKVE0HHcOkH/EVhyDqFEKLNFlsyGHGOn9ujpI6RsltX+YpXRyAdbfQHpTk11v/IAo6XksWN+g1Q==
|
||||
dependencies:
|
||||
"@apollographql/apollo-tools" "^0.4.0"
|
||||
"@apollographql/graphql-playground-html" "1.6.24"
|
||||
"@types/graphql-upload" "^8.0.0"
|
||||
"@types/ws" "^6.0.0"
|
||||
apollo-cache-control "^0.8.4"
|
||||
apollo-datasource "^0.6.3"
|
||||
apollo-engine-reporting "^1.4.6"
|
||||
apollo-server-caching "^0.5.0"
|
||||
apollo-server-env "^2.4.3"
|
||||
apollo-server-errors "^2.3.3"
|
||||
apollo-server-plugin-base "^0.6.4"
|
||||
apollo-server-types "^0.2.4"
|
||||
apollo-tracing "^0.8.4"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
graphql-extensions "^0.10.3"
|
||||
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-env@2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-2.4.2.tgz#8549caa7c8f57af88aadad5c2a0bb7adbcc5f76e"
|
||||
@ -1710,15 +1766,28 @@ apollo-server-env@2.4.2:
|
||||
node-fetch "^2.1.2"
|
||||
util.promisify "^1.0.0"
|
||||
|
||||
apollo-server-env@^2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-2.4.3.tgz#9bceedaae07eafb96becdfd478f8d92617d825d2"
|
||||
integrity sha512-23R5Xo9OMYX0iyTu2/qT0EUb+AULCBriA9w8HDfMoChB8M+lFClqUkYtaTTHDfp6eoARLW8kDBhPOBavsvKAjA==
|
||||
dependencies:
|
||||
node-fetch "^2.1.2"
|
||||
util.promisify "^1.0.0"
|
||||
|
||||
apollo-server-errors@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.2.tgz#86bbd1ff8f0b5f16bfdcbb1760398928f9fce539"
|
||||
integrity sha512-twVCP8tNHFzxOzU3jf84ppBFSvjvisZVWlgF82vwG+qEEUaAE5h5DVpeJbcI1vRW4VQPuFV+B+FIsnlweFKqtQ==
|
||||
|
||||
apollo-server-express@2.9.1, apollo-server-express@^2.9.0:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.1.tgz#9a8cb7fba579e68ddfa1953dfd066b751bca32f0"
|
||||
integrity sha512-3mmuojt9s9Gyqdf8fbdKtbw23UFYrtVQtTNASgVW8zCabZqs2WjYnijMRf1aL4u9VSl+BFMOZUPMYaeBX+u38w==
|
||||
apollo-server-errors@^2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.3.tgz#83763b00352c10dc68fbb0d41744ade66de549ff"
|
||||
integrity sha512-MO4oJ129vuCcbqwr5ZwgxqGGiLz3hCyowz0bstUF7MR+vNGe4oe3DWajC9lv4CxrhcqUHQOeOPViOdIo1IxE3g==
|
||||
|
||||
apollo-server-express@^2.9.0, apollo-server-express@^2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.3.tgz#67573404030c2676be49a7bf97d423b8462e295c"
|
||||
integrity sha512-Hkfs+ce6GqaoSzDOJs8Pj7W3YUjH0BzGglo5HMsOXOnjPZ0pJE9v8fmK76rlkITLw7GjvIq5GKlafymC31FMBw==
|
||||
dependencies:
|
||||
"@apollographql/graphql-playground-html" "1.6.24"
|
||||
"@types/accepts" "^1.3.5"
|
||||
@ -1726,10 +1795,11 @@ apollo-server-express@2.9.1, apollo-server-express@^2.9.0:
|
||||
"@types/cors" "^2.8.4"
|
||||
"@types/express" "4.17.1"
|
||||
accepts "^1.3.5"
|
||||
apollo-server-core "2.9.1"
|
||||
apollo-server-types "0.2.2"
|
||||
apollo-server-core "^2.9.3"
|
||||
apollo-server-types "^0.2.4"
|
||||
body-parser "^1.18.3"
|
||||
cors "^2.8.4"
|
||||
express "^4.17.1"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
graphql-tools "^4.0.0"
|
||||
parseurl "^1.3.2"
|
||||
@ -1743,6 +1813,13 @@ apollo-server-plugin-base@0.6.2:
|
||||
dependencies:
|
||||
apollo-server-types "0.2.2"
|
||||
|
||||
apollo-server-plugin-base@^0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.4.tgz#63ea4fd0bbb6c4510bc8d0d2ad0a0684c8d0da8c"
|
||||
integrity sha512-4rY+cBAIpQomGWYBtk8hHkLQWHrh5hgIBPQqmhXh00YFdcY+Ob1/cU2/2iqTcIzhtcaezsc8OZ63au6ahSBQqg==
|
||||
dependencies:
|
||||
apollo-server-types "^0.2.4"
|
||||
|
||||
apollo-server-testing@~2.9.1:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.1.tgz#29d2524e84722a1319d9c1524b4f9d44379d6a49"
|
||||
@ -1759,13 +1836,22 @@ apollo-server-types@0.2.2:
|
||||
apollo-server-caching "0.5.0"
|
||||
apollo-server-env "2.4.2"
|
||||
|
||||
apollo-server@~2.9.1:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.1.tgz#16ff443d43ea38f72fe20adea0803c46037b2b3b"
|
||||
integrity sha512-iCGoRBOvwTUkDz6Nq/rKguMyhDiQdL3VneF0GTjBGrelTIp3YTIxk/qBFkIr2Chtm9ZZYkS6o+ZldUnxYFKg7A==
|
||||
apollo-server-types@^0.2.4:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.2.4.tgz#28864900ffc7f9711a859297c143a833fdb6aa43"
|
||||
integrity sha512-G4FvBVgGQcTW6ZBS2+hvcDQkSfdOIKV+cHADduXA275v+5zl42g+bCaGd/hCCKTDRjmQvObLiMxH/BJ6pDMQgA==
|
||||
dependencies:
|
||||
apollo-server-core "2.9.1"
|
||||
apollo-server-express "2.9.1"
|
||||
apollo-engine-reporting-protobuf "^0.4.0"
|
||||
apollo-server-caching "^0.5.0"
|
||||
apollo-server-env "^2.4.3"
|
||||
|
||||
apollo-server@~2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.3.tgz#2a79fcee25da0b0673eb70d73839c40c3c4b8cca"
|
||||
integrity sha512-JQoeseSo3yOBu3WJzju0NTreoqYckNILybgXNUOhdurE55VFpZ8dsBEO6nMfdO2y1A70W14mnnVWCBEm+1rE8w==
|
||||
dependencies:
|
||||
apollo-server-core "^2.9.3"
|
||||
apollo-server-express "^2.9.3"
|
||||
express "^4.0.0"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
graphql-tools "^4.0.0"
|
||||
@ -1778,6 +1864,14 @@ apollo-tracing@0.8.2:
|
||||
apollo-server-env "2.4.2"
|
||||
graphql-extensions "0.10.1"
|
||||
|
||||
apollo-tracing@^0.8.4:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.4.tgz#0117820c3f0ad3aa6daf7bf13ddbb923cbefa6de"
|
||||
integrity sha512-DjbFW0IvHicSlTVG+vK+1WINfBMRCdPPHJSW/j65JMir9Oe56WGeqL8qz8hptdUUmLYEb+azvcyyGsJsiR3zpQ==
|
||||
dependencies:
|
||||
apollo-server-env "^2.4.3"
|
||||
graphql-extensions "^0.10.3"
|
||||
|
||||
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
|
||||
@ -3311,12 +3405,12 @@ eslint-module-utils@^2.4.0:
|
||||
debug "^2.6.8"
|
||||
pkg-dir "^2.0.0"
|
||||
|
||||
eslint-plugin-es@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz#475f65bb20c993fc10e8c8fe77d1d60068072da6"
|
||||
integrity sha512-XfFmgFdIUDgvaRAlaXUkxrRg5JSADoRC8IkKLc/cISeR3yHVMefFHQZpcyXXEUUPHfy5DwviBcrfqlyqEwlQVw==
|
||||
eslint-plugin-es@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998"
|
||||
integrity sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==
|
||||
dependencies:
|
||||
eslint-utils "^1.3.0"
|
||||
eslint-utils "^1.4.2"
|
||||
regexpp "^2.0.1"
|
||||
|
||||
eslint-plugin-import@~2.18.2:
|
||||
@ -3336,20 +3430,20 @@ eslint-plugin-import@~2.18.2:
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.11.0"
|
||||
|
||||
eslint-plugin-jest@~22.15.2:
|
||||
version "22.15.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.15.2.tgz#e3c10d9391f787744e31566f69ebb70c3a98e398"
|
||||
integrity sha512-p4NME9TgXIt+KgpxcXyNBvO30ZKxwFAO1dJZBc2OGfDnXVEtPwEyNs95GSr6RIE3xLHdjd8ngDdE2icRRXrbxg==
|
||||
eslint-plugin-jest@~22.16.0:
|
||||
version "22.16.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.16.0.tgz#30c4e0e9dc331beb2e7369b70dd1363690c1ce05"
|
||||
integrity sha512-eBtSCDhO1k7g3sULX/fuRK+upFQ7s548rrBtxDyM1fSoY7dTWp/wICjrJcDZKVsW7tsFfH22SG+ZaxG5BZodIg==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^1.13.0"
|
||||
|
||||
eslint-plugin-node@~9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-9.1.0.tgz#f2fd88509a31ec69db6e9606d76dabc5adc1b91a"
|
||||
integrity sha512-ZwQYGm6EoV2cfLpE1wxJWsfnKUIXfM/KM09/TlorkukgCAwmkgajEJnPCmyzoFPQQkmvo5DrW/nyKutNIw36Mw==
|
||||
eslint-plugin-node@~9.2.0:
|
||||
version "9.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-9.2.0.tgz#b1911f111002d366c5954a6d96d3cd5bf2a3036a"
|
||||
integrity sha512-2abNmzAH/JpxI4gEOwd6K8wZIodK3BmHbTxz4s79OIYwwIt2gkpEXlAouJXu4H1c9ySTnRso0tsuthSOZbUMlA==
|
||||
dependencies:
|
||||
eslint-plugin-es "^1.4.0"
|
||||
eslint-utils "^1.3.1"
|
||||
eslint-plugin-es "^1.4.1"
|
||||
eslint-utils "^1.4.2"
|
||||
ignore "^5.1.1"
|
||||
minimatch "^3.0.4"
|
||||
resolve "^1.10.1"
|
||||
@ -3388,7 +3482,7 @@ eslint-scope@^5.0.0:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-utils@^1.3.0, eslint-utils@^1.3.1, eslint-utils@^1.4.2:
|
||||
eslint-utils@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
|
||||
integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
|
||||
@ -3400,10 +3494,10 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
|
||||
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
|
||||
|
||||
eslint@~6.2.2:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.2.2.tgz#03298280e7750d81fcd31431f3d333e43d93f24f"
|
||||
integrity sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==
|
||||
eslint@~6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.3.0.tgz#1f1a902f67bfd4c354e7288b81e40654d927eb6a"
|
||||
integrity sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
@ -4086,6 +4180,15 @@ graphql-extensions@0.10.1:
|
||||
apollo-server-env "2.4.2"
|
||||
apollo-server-types "0.2.2"
|
||||
|
||||
graphql-extensions@^0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.3.tgz#9e37f3bd26309c40b03a0be0e63e02b3f99d52ea"
|
||||
integrity sha512-kwU0gUe+Qdfr8iZYT91qrPSwQNgPhB/ClF1m1LEPdxlptk5FhFmjpxAcbMZ8q7j0kjfnbp2IeV1OhRDCEPqz2w==
|
||||
dependencies:
|
||||
"@apollographql/apollo-tools" "^0.4.0"
|
||||
apollo-server-env "^2.4.3"
|
||||
apollo-server-types "^0.2.4"
|
||||
|
||||
graphql-import@0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.7.1.tgz#4add8d91a5f752d764b0a4a7a461fcd93136f223"
|
||||
@ -4180,10 +4283,10 @@ graphql-upload@^8.0.2:
|
||||
http-errors "^1.7.2"
|
||||
object-path "^0.11.4"
|
||||
|
||||
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==
|
||||
graphql@^14.2.1, graphql@^14.5.4:
|
||||
version "14.5.4"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.4.tgz#b33fe957854e90c10d4c07c7d26b6c8e9f159a13"
|
||||
integrity sha512-dPLvHoxy5m9FrkqWczPPRnH0X80CyvRE6e7Fa5AWEqEAzg9LpxHvKh24po/482E6VWHigOkAmb4xCp6P9yT9gw==
|
||||
dependencies:
|
||||
iterall "^1.2.2"
|
||||
|
||||
@ -6175,12 +6278,12 @@ neo-async@^2.6.0:
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||
|
||||
neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.5:
|
||||
version "1.7.5"
|
||||
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.5.tgz#c3fe3677f69c12f26944563d45e7e7d818a685e4"
|
||||
integrity sha512-xCD2F5+tp/SD9r5avX5bSoY8u8RH2o793xJ9Ikjz1s5qQy7cFxFbbj2c52uz3BVGhRAx/NmB57VjOquYmmxGtw==
|
||||
neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.6:
|
||||
version "1.7.6"
|
||||
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.6.tgz#eccb135a71eba9048c68717444593a6424cffc49"
|
||||
integrity sha512-6c3ALO3vYDfUqNoCy8OFzq+fQ7q/ab3LCuJrmm8P04M7RmyRCCnUtJ8IzSTGbiZvyhcehGK+azNDAEJhxPV/hA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@babel/runtime" "^7.5.5"
|
||||
text-encoding-utf-8 "^1.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
|
||||
@ -168,13 +168,13 @@ Given("we have the following posts in our database:", table => {
|
||||
};
|
||||
postAttributes.deleted = Boolean(postAttributes.deleted);
|
||||
const disabled = Boolean(postAttributes.disabled);
|
||||
postAttributes.categoryIds = [`cat${i}`];
|
||||
postAttributes.categoryIds = [`cat${i}${new Date()}`];
|
||||
postAttributes;
|
||||
cy.factory()
|
||||
.create("User", userAttributes)
|
||||
.authenticateAs(userAttributes)
|
||||
.create("Category", {
|
||||
id: `cat${i}`,
|
||||
id: `cat${i}${new Date()}`,
|
||||
name: "Just For Fun",
|
||||
slug: `just-for-fun-${i}`,
|
||||
icon: "smile"
|
||||
@ -357,7 +357,7 @@ When("mention {string} in the text", mention => {
|
||||
});
|
||||
|
||||
Then("the notification gets marked as read", () => {
|
||||
cy.get(".post.createdAt")
|
||||
cy.get(".notifications-menu-popover .notification")
|
||||
.first()
|
||||
.should("have.class", "read");
|
||||
});
|
||||
|
||||
@ -26,6 +26,9 @@ Feature: Block a User
|
||||
And nobody is following the user profile anymore
|
||||
|
||||
Scenario: Posts of blocked users are filtered from search results
|
||||
Given we have the following posts in our database:
|
||||
| Author | id | title | content |
|
||||
| Some unblocked user | im-not-blocked | Post that should be seen | cause I'm not blocked |
|
||||
Given "Spammy Spammer" wrote a post "Spam Spam Spam"
|
||||
When I search for "Spam"
|
||||
Then I should see the following posts in the select dropdown:
|
||||
@ -35,3 +38,7 @@ Feature: Block a User
|
||||
And I refresh the page
|
||||
And I search for "Spam"
|
||||
Then the search has no results
|
||||
But I search for "not blocked"
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| Post that should be seen |
|
||||
|
||||
@ -30,17 +30,6 @@
|
||||
memory: "1G"
|
||||
limits:
|
||||
memory: "2G"
|
||||
env:
|
||||
- name: NEO4J_apoc_import_file_enabled
|
||||
value: "true"
|
||||
- name: NEO4J_dbms_memory_pagecache_size
|
||||
value: "490M"
|
||||
- name: NEO4J_dbms_memory_heap_max__size
|
||||
value: "500M"
|
||||
- name: NEO4J_dbms_memory_heap_initial__size
|
||||
value: "500M"
|
||||
- name: NEO4J_dbms_security_procedures_unrestricted
|
||||
value: "algo.*,apoc.*"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: configmap
|
||||
|
||||
@ -9,6 +9,11 @@
|
||||
NEO4J_URI: "bolt://nitro-neo4j.human-connection:7687"
|
||||
NEO4J_AUTH: "none"
|
||||
CLIENT_URI: "https://nitro-staging.human-connection.org"
|
||||
NEO4J_apoc_import_file_enabled: "true"
|
||||
NEO4J_dbms_memory_pagecache_size: "490M"
|
||||
NEO4J_dbms_memory_heap_max__size: "500M"
|
||||
NEO4J_dbms_memory_heap_initial__size: "500M"
|
||||
NEO4J_dbms_security_procedures_unrestricted: "algo.*,apoc.*"
|
||||
SENTRY_DSN_WEBAPP: ""
|
||||
SENTRY_DSN_BACKEND: ""
|
||||
COMMIT: ""
|
||||
|
||||
@ -137,8 +137,8 @@ p.contentExcerpt = post.contentExcerpt,
|
||||
p.visibility = toLower(post.visibility),
|
||||
p.createdAt = post.createdAt.`$date`,
|
||||
p.updatedAt = post.updatedAt.`$date`,
|
||||
p.deleted = COALESCE(post.deleted,false),
|
||||
p.disabled = NOT post.isEnabled
|
||||
p.deleted = COALESCE(post.deleted, false),
|
||||
p.disabled = COALESCE(NOT post.isEnabled, false)
|
||||
WITH p, post
|
||||
MATCH (u:User {id: post.userId})
|
||||
MERGE (u)-[:WROTE]->(p)
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
"dotenv": "^8.1.0",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"graphql-request": "^1.8.2",
|
||||
"neo4j-driver": "^1.7.5",
|
||||
"neo4j-driver": "^1.7.6",
|
||||
"neode": "^0.3.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"slug": "^1.1.0"
|
||||
|
||||
@ -37,9 +37,9 @@ describe('Notification', () => {
|
||||
describe('given a notification about a comment on a post', () => {
|
||||
beforeEach(() => {
|
||||
propsData.notification = {
|
||||
reason: 'comment_on_post',
|
||||
post: null,
|
||||
comment: {
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-1',
|
||||
contentExcerpt:
|
||||
'<a href="/profile/u123" target="_blank">@dagobert-duck</a> is the best on this comment.',
|
||||
@ -56,7 +56,7 @@ describe('Notification', () => {
|
||||
it('renders reason', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.reason-text-for-test').text()).toEqual(
|
||||
'notifications.menu.comment_on_post',
|
||||
'notifications.menu.commented_on_post',
|
||||
)
|
||||
})
|
||||
it('renders title', () => {
|
||||
@ -92,14 +92,14 @@ describe('Notification', () => {
|
||||
beforeEach(() => {
|
||||
propsData.notification = {
|
||||
reason: 'mentioned_in_post',
|
||||
post: {
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
title: "It's a post title",
|
||||
id: 'post-1',
|
||||
slug: 'its-a-title',
|
||||
contentExcerpt:
|
||||
'<a href="/profile/u3" target="_blank">@jenny-rostock</a> is the best on this post.',
|
||||
},
|
||||
comment: null,
|
||||
}
|
||||
})
|
||||
|
||||
@ -138,8 +138,8 @@ describe('Notification', () => {
|
||||
beforeEach(() => {
|
||||
propsData.notification = {
|
||||
reason: 'mentioned_in_comment',
|
||||
post: null,
|
||||
comment: {
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'comment-1',
|
||||
contentExcerpt:
|
||||
'<a href="/profile/u123" target="_blank">@dagobert-duck</a> is the best on this comment.',
|
||||
|
||||
@ -1,14 +1,8 @@
|
||||
<template>
|
||||
<ds-space :class="[{ read: notification.read }, notification]" margin-bottom="x-small">
|
||||
<ds-space :class="{ read: notification.read, notification: true }" margin-bottom="x-small">
|
||||
<client-only>
|
||||
<ds-space margin-bottom="x-small">
|
||||
<hc-user
|
||||
v-if="resourceType == 'Post'"
|
||||
:user="post.author"
|
||||
:date-time="post.createdAt"
|
||||
:trunc="35"
|
||||
/>
|
||||
<hc-user v-else :user="comment.author" :date-time="comment.createdAt" :trunc="35" />
|
||||
<hc-user :user="from.author" :date-time="from.createdAt" :trunc="35" />
|
||||
</ds-space>
|
||||
<ds-text class="reason-text-for-test" color="soft">
|
||||
{{ $t(`notifications.menu.${notification.reason}`) }}
|
||||
@ -22,16 +16,15 @@
|
||||
>
|
||||
<ds-space margin-bottom="x-small">
|
||||
<ds-card
|
||||
:header="post.title || comment.post.title"
|
||||
:header="from.title || from.post.title"
|
||||
hover
|
||||
space="x-small"
|
||||
class="notifications-card"
|
||||
>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
<div v-if="resourceType == 'Post'">{{ post.contentExcerpt | removeHtml }}</div>
|
||||
<div v-else>
|
||||
<span class="comment-notification-header">Comment:</span>
|
||||
{{ comment.contentExcerpt | removeHtml }}
|
||||
<div>
|
||||
<span v-if="isComment" class="comment-notification-header">Comment:</span>
|
||||
{{ from.contentExcerpt | removeHtml }}
|
||||
</div>
|
||||
</ds-card>
|
||||
</ds-space>
|
||||
@ -54,23 +47,21 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
resourceType() {
|
||||
return this.post.id ? 'Post' : 'Comment'
|
||||
from() {
|
||||
return this.notification.from
|
||||
},
|
||||
post() {
|
||||
return this.notification.post || {}
|
||||
},
|
||||
comment() {
|
||||
return this.notification.comment || {}
|
||||
isComment() {
|
||||
return this.from.__typename === 'Comment'
|
||||
},
|
||||
params() {
|
||||
const post = this.isComment ? this.from.post : this.from
|
||||
return {
|
||||
id: this.post.id || this.comment.post.id,
|
||||
slug: this.post.slug || this.comment.post.slug,
|
||||
id: post.id,
|
||||
slug: post.slug,
|
||||
}
|
||||
},
|
||||
hashParam() {
|
||||
return this.post.id ? {} : { hash: `#commentId-${this.comment.id}` }
|
||||
return this.isComment ? { hash: `#commentId-${this.from.id}` } : {}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -40,9 +40,9 @@ describe('NotificationList.vue', () => {
|
||||
propsData = {
|
||||
notifications: [
|
||||
{
|
||||
id: 'notification-41',
|
||||
read: false,
|
||||
post: {
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post-1',
|
||||
title: 'some post title',
|
||||
slug: 'some-post-title',
|
||||
@ -55,9 +55,9 @@ describe('NotificationList.vue', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'notification-42',
|
||||
read: false,
|
||||
post: {
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'post-2',
|
||||
title: 'another post title',
|
||||
slug: 'another-post-title',
|
||||
@ -115,9 +115,9 @@ describe('NotificationList.vue', () => {
|
||||
.trigger('click')
|
||||
})
|
||||
|
||||
it("emits 'markAsRead' with the notificationId", () => {
|
||||
it("emits 'markAsRead' with the id of the notification source", () => {
|
||||
expect(wrapper.emitted('markAsRead')).toBeTruthy()
|
||||
expect(wrapper.emitted('markAsRead')[0]).toEqual(['notification-42'])
|
||||
expect(wrapper.emitted('markAsRead')[0]).toEqual(['post-2'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
:notification="notification"
|
||||
@read="markAsRead(notification.id)"
|
||||
@read="markAsRead(notification.from.id)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -24,8 +24,8 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
markAsRead(notificationId) {
|
||||
this.$emit('markAsRead', notificationId)
|
||||
markAsRead(notificationSourceId) {
|
||||
this.$emit('markAsRead', notificationSourceId)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
<script>
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import { currentUserNotificationsQuery, updateNotificationMutation } from '~/graphql/User'
|
||||
import { notificationQuery, markAsReadMutation } from '~/graphql/User'
|
||||
import NotificationList from '../NotificationList/NotificationList'
|
||||
|
||||
export default {
|
||||
@ -27,36 +27,41 @@ export default {
|
||||
NotificationList,
|
||||
Dropdown,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notifications: [],
|
||||
}
|
||||
},
|
||||
props: {
|
||||
placement: { type: String },
|
||||
},
|
||||
computed: {
|
||||
totalNotifications() {
|
||||
return (this.notifications || []).length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async markAsRead(notificationId) {
|
||||
const variables = { id: notificationId, read: true }
|
||||
async markAsRead(notificationSourceId) {
|
||||
const variables = { id: notificationSourceId }
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: updateNotificationMutation(),
|
||||
const {
|
||||
data: { markAsRead },
|
||||
} = await this.$apollo.mutate({
|
||||
mutation: markAsReadMutation(),
|
||||
variables,
|
||||
})
|
||||
if (!(markAsRead && markAsRead.read === true)) return
|
||||
this.notifications = this.notifications.map(n => {
|
||||
return n.from.id === markAsRead.from.id ? markAsRead : n
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
totalNotifications() {
|
||||
return this.notifications.length
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
notifications: {
|
||||
query: currentUserNotificationsQuery(),
|
||||
update: data => {
|
||||
const {
|
||||
currentUser: { notifications },
|
||||
} = data
|
||||
return notifications
|
||||
},
|
||||
query: notificationQuery(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,5 +1,58 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const fragments = gql`
|
||||
fragment post on Post {
|
||||
id
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
title
|
||||
contentExcerpt
|
||||
slug
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
avatar
|
||||
}
|
||||
}
|
||||
|
||||
fragment comment on Comment {
|
||||
id
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
contentExcerpt
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
avatar
|
||||
}
|
||||
post {
|
||||
id
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
title
|
||||
contentExcerpt
|
||||
slug
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
@ -76,77 +129,37 @@ export default i18n => {
|
||||
`
|
||||
}
|
||||
|
||||
export const currentUserNotificationsQuery = () => {
|
||||
export const notificationQuery = () => {
|
||||
return gql`
|
||||
${fragments}
|
||||
query {
|
||||
currentUser {
|
||||
id
|
||||
notifications(read: false, orderBy: createdAt_desc) {
|
||||
id
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
post {
|
||||
id
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
title
|
||||
contentExcerpt
|
||||
slug
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
avatar
|
||||
}
|
||||
}
|
||||
comment {
|
||||
id
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
contentExcerpt
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
avatar
|
||||
}
|
||||
post {
|
||||
id
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
title
|
||||
contentExcerpt
|
||||
slug
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
notifications(read: false, orderBy: createdAt_desc) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
from {
|
||||
__typename
|
||||
...post
|
||||
...comment
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const updateNotificationMutation = () => {
|
||||
export const markAsReadMutation = () => {
|
||||
return gql`
|
||||
mutation($id: ID!, $read: Boolean!) {
|
||||
UpdateNotification(id: $id, read: $read) {
|
||||
id
|
||||
${fragments}
|
||||
mutation($id: ID!) {
|
||||
markAsRead(id: $id) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
from {
|
||||
__typename
|
||||
...post
|
||||
...comment
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -134,7 +134,7 @@
|
||||
"menu": {
|
||||
"mentioned_in_post": "Hat dich in einem Beitrag erwähnt …",
|
||||
"mentioned_in_comment": "Hat dich in einem Kommentar erwähnt …",
|
||||
"comment_on_post": "Hat deinen Beitrag kommentiert …"
|
||||
"commented_on_post": "Hat deinen Beitrag kommentiert …"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
|
||||
@ -134,7 +134,7 @@
|
||||
"menu": {
|
||||
"mentioned_in_post": "Mentioned you in a post …",
|
||||
"mentioned_in_comment": "Mentioned you in a comment …",
|
||||
"comment_on_post": "Commented on your post …"
|
||||
"commented_on_post": "Commented on your post …"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.1",
|
||||
"express": "~4.17.1",
|
||||
"graphql": "~14.5.3",
|
||||
"graphql": "~14.5.4",
|
||||
"isemail": "^3.2.0",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkify-it": "~2.2.0",
|
||||
@ -78,7 +78,7 @@
|
||||
"v-tooltip": "~2.0.2",
|
||||
"vue-count-to": "~1.0.13",
|
||||
"vue-infinite-scroll": "^2.0.2",
|
||||
"vue-izitoast": "^1.2.0",
|
||||
"vue-izitoast": "^1.2.1",
|
||||
"vue-sweetalert-icons": "~4.2.0",
|
||||
"vuex-i18n": "~1.13.1",
|
||||
"xregexp": "^4.2.4",
|
||||
@ -107,8 +107,8 @@
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-loader": "~3.0.0",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
"eslint-plugin-jest": "~22.15.2",
|
||||
"eslint-plugin-node": "~9.1.0",
|
||||
"eslint-plugin-jest": "~22.16.0",
|
||||
"eslint-plugin-node": "~9.2.0",
|
||||
"eslint-plugin-prettier": "~3.1.0",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
"eslint-plugin-standard": "~4.0.1",
|
||||
@ -120,7 +120,7 @@
|
||||
"node-sass": "~4.12.0",
|
||||
"nodemon": "~1.19.1",
|
||||
"prettier": "~1.18.2",
|
||||
"sass-loader": "~7.3.1",
|
||||
"sass-loader": "~8.0.0",
|
||||
"style-loader": "~0.23.1",
|
||||
"style-resources-loader": "~1.2.1",
|
||||
"tippy.js": "^4.3.5",
|
||||
|
||||
@ -185,9 +185,6 @@ export default {
|
||||
return result
|
||||
},
|
||||
update({ Post }) {
|
||||
// TODO: find out why `update` gets called twice initially.
|
||||
// We have to filter for uniq posts only because we get the same
|
||||
// result set twice.
|
||||
this.hasMore = Post.length >= this.pageSize
|
||||
const posts = uniqBy([...this.posts, ...Post], 'id')
|
||||
this.posts = posts
|
||||
|
||||
@ -104,9 +104,7 @@ describe('ProfileSlug', () => {
|
||||
|
||||
describe('currently no posts available (e.g. after tab switching)', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setData({
|
||||
Post: null,
|
||||
})
|
||||
wrapper.setData({ posts: [], hasMore: false })
|
||||
})
|
||||
|
||||
it('displays no "load more" button', () => {
|
||||
@ -137,9 +135,7 @@ describe('ProfileSlug', () => {
|
||||
}
|
||||
})
|
||||
|
||||
wrapper.setData({
|
||||
Post: posts,
|
||||
})
|
||||
wrapper.setData({ posts, hasMore: true })
|
||||
})
|
||||
|
||||
it('displays a "load more" button', () => {
|
||||
@ -170,9 +166,7 @@ describe('ProfileSlug', () => {
|
||||
}
|
||||
})
|
||||
|
||||
wrapper.setData({
|
||||
Post: posts,
|
||||
})
|
||||
wrapper.setData({ posts, hasMore: false })
|
||||
})
|
||||
|
||||
it('displays no "load more" button', () => {
|
||||
|
||||
@ -219,8 +219,8 @@
|
||||
</ds-space>
|
||||
</ds-grid-item>
|
||||
|
||||
<template v-if="activePosts.length">
|
||||
<masonry-grid-item v-for="(post, index) in activePosts" :key="post.id">
|
||||
<template v-if="posts.length">
|
||||
<masonry-grid-item v-for="(post, index) in posts" :key="post.id">
|
||||
<hc-post-card
|
||||
:post="post"
|
||||
:width="{ base: '100%', md: '100%', xl: '50%' }"
|
||||
@ -229,10 +229,10 @@
|
||||
</masonry-grid-item>
|
||||
</template>
|
||||
<template v-else-if="$apollo.loading">
|
||||
<ds-grid-item>
|
||||
<ds-section centered>
|
||||
<ds-grid-item column-span="fullWidth">
|
||||
<ds-space centered>
|
||||
<ds-spinner size="base"></ds-spinner>
|
||||
</ds-section>
|
||||
</ds-space>
|
||||
</ds-grid-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
@ -306,33 +306,21 @@ export default {
|
||||
const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id })
|
||||
return {
|
||||
User: [],
|
||||
Post: [],
|
||||
activePosts: [],
|
||||
voted: false,
|
||||
page: 1,
|
||||
posts: [],
|
||||
hasMore: false,
|
||||
offset: 0,
|
||||
pageSize: 6,
|
||||
tabActive: 'post',
|
||||
filter,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasMore() {
|
||||
const total = {
|
||||
post: this.user.contributionsCount,
|
||||
shout: this.user.shoutedCount,
|
||||
comment: this.user.commentedCount,
|
||||
}[this.tabActive]
|
||||
return this.Post && this.Post.length < total
|
||||
},
|
||||
myProfile() {
|
||||
return this.$route.params.id === this.$store.getters['auth/user'].id
|
||||
},
|
||||
user() {
|
||||
return this.User ? this.User[0] : {}
|
||||
},
|
||||
offset() {
|
||||
return (this.page - 1) * this.pageSize
|
||||
},
|
||||
socialMediaLinks() {
|
||||
const { socialMedia = [] } = this.user
|
||||
return socialMedia.map(socialMedia => {
|
||||
@ -355,19 +343,15 @@ export default {
|
||||
throw new Error('User not found!')
|
||||
}
|
||||
},
|
||||
Post(val) {
|
||||
this.activePosts = this.setActivePosts()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removePostFromList(index) {
|
||||
this.activePosts.splice(index, 1)
|
||||
this.$apollo.queries.User.refetch()
|
||||
this.posts.splice(index, 1)
|
||||
},
|
||||
handleTab(tab) {
|
||||
this.tabActive = tab
|
||||
this.Post = null
|
||||
this.filter = tabToFilterMapping({ tab, id: this.$route.params.id })
|
||||
this.resetPostList()
|
||||
},
|
||||
uniq(items, field = 'id') {
|
||||
return uniqBy(items, field)
|
||||
@ -377,38 +361,23 @@ export default {
|
||||
this.$apollo.queries.User.refetch()
|
||||
},
|
||||
showMoreContributions() {
|
||||
// this.page++
|
||||
// Fetch more data and transform the original result
|
||||
this.page++
|
||||
this.$apollo.queries.Post.fetchMore({
|
||||
variables: {
|
||||
filter: this.filter,
|
||||
first: this.pageSize,
|
||||
offset: this.offset,
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
let output = { Post: this.Post }
|
||||
output.Post = [...previousResult.Post, ...fetchMoreResult.Post]
|
||||
return output
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
})
|
||||
this.offset += this.pageSize
|
||||
},
|
||||
setActivePosts() {
|
||||
if (!this.Post) {
|
||||
return []
|
||||
}
|
||||
return this.uniq(this.Post.filter(post => !post.deleted))
|
||||
resetPostList() {
|
||||
this.offset = 0
|
||||
this.posts = []
|
||||
this.hasMore = false
|
||||
},
|
||||
async block(user) {
|
||||
await this.$apollo.mutate({ mutation: Block(), variables: { id: user.id } })
|
||||
this.$apollo.queries.User.refetch()
|
||||
this.resetPostList()
|
||||
this.$apollo.queries.Post.refetch()
|
||||
},
|
||||
async unblock(user) {
|
||||
await this.$apollo.mutate({ mutation: Unblock(), variables: { id: user.id } })
|
||||
this.$apollo.queries.User.refetch()
|
||||
this.resetPostList()
|
||||
this.$apollo.queries.Post.refetch()
|
||||
},
|
||||
},
|
||||
@ -421,10 +390,19 @@ export default {
|
||||
return {
|
||||
filter: this.filter,
|
||||
first: this.pageSize,
|
||||
offset: 0,
|
||||
offset: this.offset,
|
||||
orderBy: 'createdAt_desc',
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
update({ Post }) {
|
||||
if (!Post) return
|
||||
// TODO: find out why `update` gets called twice initially.
|
||||
// We have to filter for uniq posts only because we get the same
|
||||
// result set twice.
|
||||
this.hasMore = Post.length >= this.pageSize
|
||||
this.posts = this.uniq([...this.posts, ...Post])
|
||||
},
|
||||
},
|
||||
User: {
|
||||
query() {
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
|
||||
import introspectionQueryResultData from './apollo-config/fragmentTypes.json'
|
||||
|
||||
const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
})
|
||||
|
||||
export default ({ app }) => {
|
||||
const backendUrl = process.env.GRAPHQL_URI || 'http://localhost:4000'
|
||||
|
||||
@ -10,5 +17,6 @@ export default ({ app }) => {
|
||||
tokenName: 'human-connection-token',
|
||||
persisting: false,
|
||||
websocketsOnly: false,
|
||||
cache: new InMemoryCache({ fragmentMatcher }),
|
||||
}
|
||||
}
|
||||
|
||||
18
webapp/plugins/apollo-config/fragmentTypes.json
Normal file
18
webapp/plugins/apollo-config/fragmentTypes.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"__schema": {
|
||||
"types": [
|
||||
{
|
||||
"kind": "UNION",
|
||||
"name": "NotificationSource",
|
||||
"possibleTypes": [
|
||||
{
|
||||
"name": "Post"
|
||||
},
|
||||
{
|
||||
"name": "Comment"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -86,23 +86,6 @@ export const actions = {
|
||||
id
|
||||
url
|
||||
}
|
||||
notifications(read: false, orderBy: createdAt_desc) {
|
||||
id
|
||||
read
|
||||
createdAt
|
||||
post {
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
title
|
||||
contentExcerpt
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@ -1,99 +0,0 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const state = () => {
|
||||
return {
|
||||
notifications: null,
|
||||
pending: false,
|
||||
}
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
SET_NOTIFICATIONS(state, notifications) {
|
||||
state.notifications = notifications
|
||||
},
|
||||
SET_PENDING(state, pending) {
|
||||
state.pending = pending
|
||||
},
|
||||
UPDATE_NOTIFICATIONS(state, notification) {
|
||||
const notifications = state.notifications
|
||||
const toBeUpdated = notifications.find(n => {
|
||||
return n.id === notification.id
|
||||
})
|
||||
state.notifications = {
|
||||
...toBeUpdated,
|
||||
...notification,
|
||||
}
|
||||
},
|
||||
}
|
||||
export const getters = {
|
||||
notifications(state) {
|
||||
return !!state.notifications
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
async init({ getters, commit }) {
|
||||
if (getters.notifications) return
|
||||
commit('SET_PENDING', true)
|
||||
const client = this.app.apolloProvider.defaultClient
|
||||
let notifications
|
||||
try {
|
||||
const {
|
||||
data: { currentUser },
|
||||
} = await client.query({
|
||||
query: gql`
|
||||
{
|
||||
currentUser {
|
||||
id
|
||||
notifications(orderBy: createdAt_desc) {
|
||||
id
|
||||
read
|
||||
createdAt
|
||||
post {
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
title
|
||||
contentExcerpt
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
notifications = currentUser.notifications
|
||||
commit('SET_NOTIFICATIONS', notifications)
|
||||
} finally {
|
||||
commit('SET_PENDING', false)
|
||||
}
|
||||
return notifications
|
||||
},
|
||||
|
||||
async markAsRead({ commit, rootGetters }, notificationId) {
|
||||
const client = this.app.apolloProvider.defaultClient
|
||||
const mutation = gql`
|
||||
mutation($id: ID!, $read: Boolean!) {
|
||||
UpdateNotification(id: $id, read: $read) {
|
||||
id
|
||||
read
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = {
|
||||
id: notificationId,
|
||||
read: true,
|
||||
}
|
||||
const {
|
||||
data: { UpdateNotification },
|
||||
} = await client.mutate({
|
||||
mutation,
|
||||
variables,
|
||||
})
|
||||
commit('UPDATE_NOTIFICATIONS', UpdateNotification)
|
||||
},
|
||||
}
|
||||
@ -46,8 +46,8 @@ export const actions = {
|
||||
await this.app.apolloProvider.defaultClient
|
||||
.query({
|
||||
query: gql`
|
||||
query findPosts($query: String!) {
|
||||
findPosts(query: $query, limit: 10) {
|
||||
query findPosts($query: String!, $filter: _PostFilter) {
|
||||
findPosts(query: $query, limit: 10, filter: $filter) {
|
||||
id
|
||||
slug
|
||||
label: title
|
||||
@ -64,6 +64,7 @@ export const actions = {
|
||||
`,
|
||||
variables: {
|
||||
query: value.replace(/\s/g, '~ ') + '~',
|
||||
filter: {},
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
|
||||
@ -6387,12 +6387,12 @@ eslint-module-utils@^2.4.0:
|
||||
debug "^2.6.8"
|
||||
pkg-dir "^2.0.0"
|
||||
|
||||
eslint-plugin-es@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz#475f65bb20c993fc10e8c8fe77d1d60068072da6"
|
||||
integrity sha512-XfFmgFdIUDgvaRAlaXUkxrRg5JSADoRC8IkKLc/cISeR3yHVMefFHQZpcyXXEUUPHfy5DwviBcrfqlyqEwlQVw==
|
||||
eslint-plugin-es@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998"
|
||||
integrity sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==
|
||||
dependencies:
|
||||
eslint-utils "^1.3.0"
|
||||
eslint-utils "^1.4.2"
|
||||
regexpp "^2.0.1"
|
||||
|
||||
eslint-plugin-import@~2.18.2:
|
||||
@ -6412,20 +6412,20 @@ eslint-plugin-import@~2.18.2:
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.11.0"
|
||||
|
||||
eslint-plugin-jest@~22.15.2:
|
||||
version "22.15.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.15.2.tgz#e3c10d9391f787744e31566f69ebb70c3a98e398"
|
||||
integrity sha512-p4NME9TgXIt+KgpxcXyNBvO30ZKxwFAO1dJZBc2OGfDnXVEtPwEyNs95GSr6RIE3xLHdjd8ngDdE2icRRXrbxg==
|
||||
eslint-plugin-jest@~22.16.0:
|
||||
version "22.16.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.16.0.tgz#30c4e0e9dc331beb2e7369b70dd1363690c1ce05"
|
||||
integrity sha512-eBtSCDhO1k7g3sULX/fuRK+upFQ7s548rrBtxDyM1fSoY7dTWp/wICjrJcDZKVsW7tsFfH22SG+ZaxG5BZodIg==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^1.13.0"
|
||||
|
||||
eslint-plugin-node@~9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-9.1.0.tgz#f2fd88509a31ec69db6e9606d76dabc5adc1b91a"
|
||||
integrity sha512-ZwQYGm6EoV2cfLpE1wxJWsfnKUIXfM/KM09/TlorkukgCAwmkgajEJnPCmyzoFPQQkmvo5DrW/nyKutNIw36Mw==
|
||||
eslint-plugin-node@~9.2.0:
|
||||
version "9.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-9.2.0.tgz#b1911f111002d366c5954a6d96d3cd5bf2a3036a"
|
||||
integrity sha512-2abNmzAH/JpxI4gEOwd6K8wZIodK3BmHbTxz4s79OIYwwIt2gkpEXlAouJXu4H1c9ySTnRso0tsuthSOZbUMlA==
|
||||
dependencies:
|
||||
eslint-plugin-es "^1.4.0"
|
||||
eslint-utils "^1.3.1"
|
||||
eslint-plugin-es "^1.4.1"
|
||||
eslint-utils "^1.4.2"
|
||||
ignore "^5.1.1"
|
||||
minimatch "^3.0.4"
|
||||
resolve "^1.10.1"
|
||||
@ -6463,7 +6463,7 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-utils@^1.3.0, eslint-utils@^1.3.1:
|
||||
eslint-utils@^1.3.1, eslint-utils@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
|
||||
integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
|
||||
@ -7635,10 +7635,10 @@ graphql-upload@^8.0.2:
|
||||
http-errors "^1.7.2"
|
||||
object-path "^0.11.4"
|
||||
|
||||
"graphql@14.0.2 - 14.2.0 || ^14.3.1", graphql@^14.4.0, 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==
|
||||
"graphql@14.0.2 - 14.2.0 || ^14.3.1", graphql@^14.4.0, graphql@~14.5.4:
|
||||
version "14.5.4"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.4.tgz#b33fe957854e90c10d4c07c7d26b6c8e9f159a13"
|
||||
integrity sha512-dPLvHoxy5m9FrkqWczPPRnH0X80CyvRE6e7Fa5AWEqEAzg9LpxHvKh24po/482E6VWHigOkAmb4xCp6P9yT9gw==
|
||||
dependencies:
|
||||
iterall "^1.2.2"
|
||||
|
||||
@ -8162,12 +8162,7 @@ ignore@^4.0.6:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
||||
|
||||
ignore@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.1.tgz#2fc6b8f518aff48fef65a7f348ed85632448e4a5"
|
||||
integrity sha512-DWjnQIFLenVrwyRCKZT+7a7/U4Cqgar4WG8V++K3hw+lrW1hc/SIwdiGmtxKCVACmHULTuGeBbHJmbwW7/sAvA==
|
||||
|
||||
ignore@^5.1.4:
|
||||
ignore@^5.1.1, ignore@^5.1.4:
|
||||
version "5.1.4"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
|
||||
integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
|
||||
@ -13345,7 +13340,7 @@ sass-graph@^2.2.4:
|
||||
scss-tokenizer "^0.2.3"
|
||||
yargs "^7.0.0"
|
||||
|
||||
sass-loader@^7.1.0, sass-loader@~7.3.1:
|
||||
sass-loader@^7.1.0:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f"
|
||||
integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==
|
||||
@ -13356,6 +13351,17 @@ sass-loader@^7.1.0, sass-loader@~7.3.1:
|
||||
pify "^4.0.1"
|
||||
semver "^6.3.0"
|
||||
|
||||
sass-loader@~8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.0.tgz#e7b07a3e357f965e6b03dd45b016b0a9746af797"
|
||||
integrity sha512-+qeMu563PN7rPdit2+n5uuYVR0SSVwm0JsOUsaJXzgYcClWSlmX0iHDnmeOobPkf5kUglVot3QS6SyLyaQoJ4w==
|
||||
dependencies:
|
||||
clone-deep "^4.0.1"
|
||||
loader-utils "^1.2.3"
|
||||
neo-async "^2.6.1"
|
||||
schema-utils "^2.1.0"
|
||||
semver "^6.3.0"
|
||||
|
||||
sass-resources-loader@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-resources-loader/-/sass-resources-loader-2.0.0.tgz#88569c542fbf1f18f33a6578b77cc5b36c56911d"
|
||||
@ -15173,10 +15179,10 @@ vue-infinite-scroll@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/vue-infinite-scroll/-/vue-infinite-scroll-2.0.2.tgz#ca37a91fe92ee0ad3b74acf8682c00917144b711"
|
||||
integrity sha512-n+YghR059YmciANGJh9SsNWRi1YZEBVlODtmnb/12zI+4R72QZSWd+EuZ5mW6auEo/yaJXgxzwsuhvALVnm73A==
|
||||
|
||||
vue-izitoast@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-izitoast/-/vue-izitoast-1.2.0.tgz#55b7434a391c6eb64dd10c0de211e99ba7e486e2"
|
||||
integrity sha512-Jqxfid12SUBIySJxgyPpu6gZ1ssMcbKtCvu9uMQPNM8RUnd3RKC4nyxkncdYe5L6XPU+SaznjYRudnvtclY4wA==
|
||||
vue-izitoast@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-izitoast/-/vue-izitoast-1.2.1.tgz#cd2cbfbd96ea438dede8fb00f2c328364cb7141d"
|
||||
integrity sha512-5krrKyAftSR3TnnO3zhMihYCSt0Lay4SBO1AWWKD3jhTErJrR+q9kOKyuAYhn1SttNER87hpnRKqdvLjzjHWQQ==
|
||||
dependencies:
|
||||
izitoast "^1.4.0"
|
||||
|
||||
|
||||
18
yarn.lock
18
yarn.lock
@ -740,10 +740,10 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@^7.4.4":
|
||||
version "7.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
|
||||
integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
|
||||
"@babel/runtime@^7.5.5":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
||||
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
@ -3463,12 +3463,12 @@ needle@^2.2.1:
|
||||
iconv-lite "^0.4.4"
|
||||
sax "^1.2.4"
|
||||
|
||||
neo4j-driver@^1.7.5:
|
||||
version "1.7.5"
|
||||
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.5.tgz#c3fe3677f69c12f26944563d45e7e7d818a685e4"
|
||||
integrity sha512-xCD2F5+tp/SD9r5avX5bSoY8u8RH2o793xJ9Ikjz1s5qQy7cFxFbbj2c52uz3BVGhRAx/NmB57VjOquYmmxGtw==
|
||||
neo4j-driver@^1.7.5, neo4j-driver@^1.7.6:
|
||||
version "1.7.6"
|
||||
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.6.tgz#eccb135a71eba9048c68717444593a6424cffc49"
|
||||
integrity sha512-6c3ALO3vYDfUqNoCy8OFzq+fQ7q/ab3LCuJrmm8P04M7RmyRCCnUtJ8IzSTGbiZvyhcehGK+azNDAEJhxPV/hA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@babel/runtime" "^7.5.5"
|
||||
text-encoding-utf-8 "^1.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user