mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #1705 from Human-Connection/1703-add-vue-apollo-subsriptions
feat: 🍰 Set up Vue-Apollo Subscriptions
This commit is contained in:
commit
50ec01d718
@ -60,9 +60,11 @@
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~4.0.2",
|
||||
"graphql-middleware-sentry": "^3.2.1",
|
||||
"graphql-redis-subscriptions": "^2.1.2",
|
||||
"graphql-shield": "~7.0.11",
|
||||
"graphql-tag": "~2.10.3",
|
||||
"helmet": "~3.21.2",
|
||||
"ioredis": "^4.14.1",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkifyjs": "~2.1.8",
|
||||
"lodash": "~4.17.14",
|
||||
@ -96,6 +98,7 @@
|
||||
"request": "~2.88.2",
|
||||
"sanitize-html": "~1.21.1",
|
||||
"slug": "~2.1.1",
|
||||
"subscriptions-transport-ws": "^0.9.16",
|
||||
"trunc-html": "~1.1.2",
|
||||
"uuid": "~3.4.0",
|
||||
"validator": "^12.2.0",
|
||||
|
||||
@ -23,6 +23,9 @@ const {
|
||||
NEO4J_PASSWORD = 'neo4j',
|
||||
CLIENT_URI = 'http://localhost:3000',
|
||||
GRAPHQL_URI = 'http://localhost:4000',
|
||||
REDIS_DOMAIN,
|
||||
REDIS_PORT,
|
||||
REDIS_PASSWORD,
|
||||
} = env
|
||||
|
||||
export const requiredConfigs = {
|
||||
@ -61,7 +64,7 @@ export const developmentConfigs = {
|
||||
}
|
||||
|
||||
export const sentryConfigs = { SENTRY_DSN_BACKEND, COMMIT }
|
||||
|
||||
export const redisConfiig = { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD }
|
||||
export default {
|
||||
...requiredConfigs,
|
||||
...smtpConfigs,
|
||||
@ -69,4 +72,5 @@ export default {
|
||||
...serverConfigs,
|
||||
...developmentConfigs,
|
||||
...sentryConfigs,
|
||||
...redisConfiig,
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import createServer from './server'
|
||||
import CONFIG from './config'
|
||||
|
||||
const { app } = createServer()
|
||||
const { server, httpServer } = createServer()
|
||||
const url = new URL(CONFIG.GRAPHQL_URI)
|
||||
app.listen({ port: url.port }, () => {
|
||||
httpServer.listen({ port: url.port }, () => {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.log(`GraphQLServer ready at ${CONFIG.GRAPHQL_URI} 🚀`)
|
||||
console.log(`🚀 Server ready at http://localhost:${url.port}${server.graphqlPath}`)
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.log(`🚀 Subscriptions ready at ws://localhost:${url.port}${server.subscriptionsPath}`)
|
||||
})
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
||||
import { validateNotifyUsers } from '../validation/validationMiddleware'
|
||||
import { pubsub, NOTIFICATION_ADDED } from '../../server'
|
||||
|
||||
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
||||
const idsOfUsers = extractMentionedUsers(args.content)
|
||||
@ -52,34 +53,48 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)-[:BLOCKED]-(author)
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
WITH post AS resource, notification, user
|
||||
`
|
||||
break
|
||||
}
|
||||
case 'mentioned_in_comment': {
|
||||
mentionedCypher = `
|
||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(commenter: User)
|
||||
MATCH (user: User)
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)-[:BLOCKED]-(author)
|
||||
AND NOT (user)-[:BLOCKED]-(commenter)
|
||||
AND NOT (user)-[:BLOCKED]-(postAuthor)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
WITH comment AS resource, notification, user
|
||||
`
|
||||
break
|
||||
}
|
||||
}
|
||||
mentionedCypher += `
|
||||
WITH notification, user, resource,
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
|
||||
WITH resource, user, notification, authors, posts,
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
|
||||
SET notification.read = FALSE
|
||||
SET (
|
||||
CASE
|
||||
WHEN notification.createdAt IS NULL
|
||||
THEN notification END ).createdAt = toString(datetime())
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
RETURN notification {.*, from: finalResource, to: properties(user)}
|
||||
`
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
await session.writeTransaction(transaction => {
|
||||
return transaction.run(mentionedCypher, { id, idsOfUsers, reason })
|
||||
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||
const notificationTransactionResponse = await transaction.run(mentionedCypher, {
|
||||
id,
|
||||
idsOfUsers,
|
||||
reason,
|
||||
})
|
||||
return notificationTransactionResponse.records.map(record => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
const [notification] = await writeTxResultPromise
|
||||
return pubsub.publish(NOTIFICATION_ADDED, { notificationAdded: notification })
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
@ -88,24 +103,26 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
|
||||
await validateNotifyUsers(label, reason)
|
||||
const session = context.driver.session()
|
||||
|
||||
const writeTxResultPromise = await session.writeTransaction(async transaction => {
|
||||
const notificationTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||
WHERE NOT (postAuthor)-[:BLOCKED]-(commenter)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, postAuthor, post,
|
||||
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
|
||||
RETURN notification {.*, from: finalResource, to: properties(postAuthor)}
|
||||
`,
|
||||
{ commentId, postAuthorId, reason },
|
||||
)
|
||||
return notificationTransactionResponse.records.map(record => record.get('notification'))
|
||||
})
|
||||
try {
|
||||
await session.writeTransaction(async transaction => {
|
||||
await transaction.run(
|
||||
`
|
||||
MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||
WHERE NOT (postAuthor)-[:BLOCKED]-(commenter)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor)
|
||||
SET notification.read = FALSE
|
||||
SET (
|
||||
CASE
|
||||
WHEN notification.createdAt IS NULL
|
||||
THEN notification END ).createdAt = toString(datetime())
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
`,
|
||||
{ commentId, postAuthorId, reason },
|
||||
)
|
||||
})
|
||||
const [notification] = await writeTxResultPromise
|
||||
return pubsub.publish(NOTIFICATION_ADDED, { notificationAdded: notification })
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
|
||||
@ -1,21 +1,18 @@
|
||||
import log from './helpers/databaseLogger'
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
import { withFilter } from 'graphql-subscriptions'
|
||||
import { pubsub, NOTIFICATION_ADDED } from '../../server'
|
||||
|
||||
export default {
|
||||
Subscription: {
|
||||
notificationAdded: {
|
||||
subscribe: withFilter(
|
||||
() => pubsub.asyncIterator(NOTIFICATION_ADDED),
|
||||
(payload, variables) => {
|
||||
return payload.notificationAdded.to.id === variables.userId
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
notifications: async (_parent, args, context, _resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
@ -51,10 +48,10 @@ export default {
|
||||
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
|
||||
${whereClause}
|
||||
WITH user, notification, resource,
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*}] as authors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
|
||||
WITH resource, user, notification, authors, posts,
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} as finalResource
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
|
||||
RETURN notification {.*, from: finalResource, to: properties(user)}
|
||||
${orderByClause}
|
||||
${offset} ${limit}
|
||||
@ -81,12 +78,19 @@ export default {
|
||||
`
|
||||
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
|
||||
SET notification.read = TRUE
|
||||
RETURN resource, notification, user
|
||||
WITH user, notification, resource,
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
|
||||
WITH resource, user, notification, authors, posts,
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
|
||||
RETURN notification {.*, from: finalResource, to: properties(user)}
|
||||
`,
|
||||
{ resourceId: args.id, id: currentUser.id },
|
||||
)
|
||||
log(markNotificationAsReadTransactionResponse)
|
||||
return markNotificationAsReadTransactionResponse.records.map(transformReturnType)
|
||||
return markNotificationAsReadTransactionResponse.records.map(record =>
|
||||
record.get('notification'),
|
||||
)
|
||||
})
|
||||
try {
|
||||
const [notifications] = await writeTxResultPromise
|
||||
|
||||
@ -30,3 +30,7 @@ type Query {
|
||||
type Mutation {
|
||||
markAsRead(id: ID!): NOTIFIED
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
notificationAdded(userId: ID!): NOTIFIED
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import express from 'express'
|
||||
import http from 'http'
|
||||
import helmet from 'helmet'
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import CONFIG from './config'
|
||||
@ -7,12 +8,35 @@ import { getNeode, getDriver } from './db/neo4j'
|
||||
import decode from './jwt/decode'
|
||||
import schema from './schema'
|
||||
import webfinger from './activitypub/routes/webfinger'
|
||||
import { RedisPubSub } from 'graphql-redis-subscriptions'
|
||||
import { PubSub } from 'graphql-subscriptions'
|
||||
import Redis from 'ioredis'
|
||||
import bodyParser from 'body-parser'
|
||||
|
||||
export const NOTIFICATION_ADDED = 'NOTIFICATION_ADDED'
|
||||
const { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD } = CONFIG
|
||||
let prodPubsub, devPubsub
|
||||
const options = {
|
||||
host: REDIS_DOMAIN,
|
||||
port: REDIS_PORT,
|
||||
password: REDIS_PASSWORD,
|
||||
retryStrategy: times => {
|
||||
return Math.min(times * 50, 2000)
|
||||
},
|
||||
}
|
||||
if (options.host && options.port && options.password) {
|
||||
prodPubsub = new RedisPubSub({
|
||||
publisher: new Redis(options),
|
||||
subscriber: new Redis(options),
|
||||
})
|
||||
} else {
|
||||
devPubsub = new PubSub()
|
||||
}
|
||||
export const pubsub = prodPubsub || devPubsub
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
export const context = async ({ req }) => {
|
||||
const getContext = async req => {
|
||||
const user = await decode(driver, req.headers.authorization)
|
||||
return {
|
||||
driver,
|
||||
@ -24,11 +48,24 @@ export const context = async ({ req }) => {
|
||||
},
|
||||
}
|
||||
}
|
||||
export const context = async options => {
|
||||
const { connection, req } = options
|
||||
if (connection) {
|
||||
return connection.context
|
||||
} else {
|
||||
return getContext(req)
|
||||
}
|
||||
}
|
||||
|
||||
const createServer = options => {
|
||||
const defaults = {
|
||||
context,
|
||||
schema: middleware(schema),
|
||||
subscriptions: {
|
||||
onConnect: (connectionParams, webSocket) => {
|
||||
return getContext(connectionParams)
|
||||
},
|
||||
},
|
||||
debug: !!CONFIG.DEBUG,
|
||||
tracing: !!CONFIG.DEBUG,
|
||||
formatError: error => {
|
||||
@ -49,8 +86,10 @@ const createServer = options => {
|
||||
app.use(bodyParser.json({ limit: '10mb' }))
|
||||
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }))
|
||||
server.applyMiddleware({ app, path: '/' })
|
||||
const httpServer = http.createServer(app)
|
||||
server.installSubscriptionHandlers(httpServer)
|
||||
|
||||
return { server, app }
|
||||
return { server, httpServer, app }
|
||||
}
|
||||
|
||||
export default createServer
|
||||
|
||||
@ -2774,6 +2774,11 @@ clone-response@^1.0.2:
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
cluster-key-slot@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
@ -3254,6 +3259,11 @@ delegates@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||
|
||||
denque@^1.1.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
|
||||
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
|
||||
|
||||
depd@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||
@ -4476,6 +4486,15 @@ graphql-middleware@~4.0.2:
|
||||
dependencies:
|
||||
graphql-tools "^4.0.5"
|
||||
|
||||
graphql-redis-subscriptions@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.1.2.tgz#9c1b744bace0c6ba99dd0ebafe0148cad1df3301"
|
||||
integrity sha512-l69KbGxyYfVHxvE+Dzv9/hXg/q+Xnjfx1JsrJD6ikePuSsNaCSNxr+MubSTNF3Gt3C/+JZs4FaWImFeK/+X2og==
|
||||
dependencies:
|
||||
iterall "^1.2.2"
|
||||
optionalDependencies:
|
||||
ioredis "^4.6.3"
|
||||
|
||||
graphql-shield@~7.0.11:
|
||||
version "7.0.11"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.0.11.tgz#78d49f346326be71090d35d8f5843da9ee8136e2"
|
||||
@ -4920,6 +4939,21 @@ invariant@^2.2.2, invariant@^2.2.4:
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
ioredis@^4.14.1, ioredis@^4.6.3:
|
||||
version "4.14.1"
|
||||
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.14.1.tgz#b73ded95fcf220f106d33125a92ef6213aa31318"
|
||||
integrity sha512-94W+X//GHM+1GJvDk6JPc+8qlM7Dul+9K+lg3/aHixPN7ZGkW6qlvX0DG6At9hWtH2v3B32myfZqWoANUJYGJA==
|
||||
dependencies:
|
||||
cluster-key-slot "^1.1.0"
|
||||
debug "^4.1.1"
|
||||
denque "^1.1.0"
|
||||
lodash.defaults "^4.2.0"
|
||||
lodash.flatten "^4.4.0"
|
||||
redis-commands "1.5.0"
|
||||
redis-errors "^1.2.0"
|
||||
redis-parser "^3.0.0"
|
||||
standard-as-callback "^2.0.1"
|
||||
|
||||
ip-regex@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd"
|
||||
@ -5960,11 +5994,21 @@ lodash.clonedeep@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||
|
||||
lodash.defaults@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
|
||||
|
||||
lodash.escaperegexp@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
||||
integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
|
||||
|
||||
lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
@ -7489,6 +7533,23 @@ realpath-native@^1.1.0:
|
||||
dependencies:
|
||||
util.promisify "^1.0.0"
|
||||
|
||||
redis-commands@1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
|
||||
integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
|
||||
|
||||
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
|
||||
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
|
||||
|
||||
redis-parser@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
|
||||
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
|
||||
dependencies:
|
||||
redis-errors "^1.0.0"
|
||||
|
||||
referrer-policy@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e"
|
||||
@ -8196,6 +8257,11 @@ stacktrace-js@^2.0.0:
|
||||
stack-generator "^2.0.1"
|
||||
stacktrace-gps "^3.0.1"
|
||||
|
||||
standard-as-callback@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126"
|
||||
integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||
|
||||
@ -22,10 +22,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NOTIFICATIONS_POLL_INTERVAL } from '~/constants/notifications'
|
||||
import { notificationQuery, markAsReadMutation } from '~/graphql/User'
|
||||
import { mapGetters } from 'vuex'
|
||||
import unionBy from 'lodash/unionBy'
|
||||
|
||||
import { notificationQuery, markAsReadMutation, notificationAdded } from '~/graphql/User'
|
||||
import CounterIcon from '~/components/_new/generic/CounterIcon/CounterIcon'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import NotificationList from '../NotificationList/NotificationList'
|
||||
@ -59,6 +58,9 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
user: 'auth/user',
|
||||
}),
|
||||
unreadNotificationsCount() {
|
||||
const result = this.notifications.reduce((count, notification) => {
|
||||
return notification.read ? count : count + 1
|
||||
@ -77,11 +79,25 @@ export default {
|
||||
orderBy: 'updatedAt_desc',
|
||||
}
|
||||
},
|
||||
pollInterval: NOTIFICATIONS_POLL_INTERVAL,
|
||||
update({ notifications }) {
|
||||
return unionBy(notifications, this.notifications, notification => notification.id).sort(
|
||||
(a, b) => new Date(b.createdAt) - new Date(a.createdAt),
|
||||
)
|
||||
subscribeToMore: {
|
||||
document: notificationAdded(),
|
||||
variables() {
|
||||
return {
|
||||
userId: this.user.id,
|
||||
}
|
||||
},
|
||||
updateQuery: (previousResult, { subscriptionData }) => {
|
||||
const {
|
||||
data: { notificationAdded: newNotification },
|
||||
} = subscriptionData
|
||||
return {
|
||||
notifications: unionBy(
|
||||
[newNotification],
|
||||
previousResult.notifications,
|
||||
notification => notification.id,
|
||||
).sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)),
|
||||
}
|
||||
},
|
||||
},
|
||||
error(error) {
|
||||
this.$toast.error(error.message)
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export const NOTIFICATIONS_POLL_INTERVAL = 60000
|
||||
@ -71,6 +71,7 @@ export const notificationQuery = i18n => {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
updatedAt
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
@ -109,6 +110,7 @@ export const markAsReadMutation = i18n => {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
updatedAt
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
@ -132,6 +134,44 @@ export const markAsReadMutation = i18n => {
|
||||
`
|
||||
}
|
||||
|
||||
export const notificationAdded = () => {
|
||||
return gql`
|
||||
${userFragment}
|
||||
${commentFragment}
|
||||
${postFragment}
|
||||
|
||||
subscription notifications($userId: ID!) {
|
||||
notificationAdded(userId: $userId) {
|
||||
id
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
updatedAt
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
...post
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
... on Comment {
|
||||
...comment
|
||||
author {
|
||||
...user
|
||||
}
|
||||
post {
|
||||
...post
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
export const followUserMutation = i18n => {
|
||||
return gql`
|
||||
${userFragment}
|
||||
|
||||
@ -9,6 +9,7 @@ export default ({ app }) => {
|
||||
const backendUrl = process.env.GRAPHQL_URI || 'http://localhost:4000'
|
||||
|
||||
return {
|
||||
wsEndpoint: process.env.WEBSOCKETS_URI || 'ws://localhost:4000/graphql',
|
||||
httpEndpoint: process.server ? backendUrl : '/api',
|
||||
httpLinkOptions: {
|
||||
credentials: 'same-origin',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user