From 3be5dee08b3a1853d86b7c01f768bf4920784be9 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Fri, 24 Jan 2020 17:25:45 +0100 Subject: [PATCH] Draft: Setup subsriptions for backend+frontend Please clean up this commit and squash it later on. --- backend/package.json | 1 + backend/src/config/index.js | 1 + backend/src/index.js | 7 +++-- backend/src/schema/resolvers/posts.js | 12 ++++++++ backend/src/schema/types/schema.gql | 4 +++ backend/src/server.js | 43 ++++++++++++++++++++------- backend/yarn.lock | 7 ++++- webapp/pages/index.vue | 40 +++++++++++++++++++++++++ webapp/plugins/apollo-config.js | 3 +- 9 files changed, 102 insertions(+), 16 deletions(-) diff --git a/backend/package.json b/backend/package.json index 2a83c7f01..d33f9e14a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -96,6 +96,7 @@ "request": "~2.88.0", "sanitize-html": "~1.21.1", "slug": "~2.1.0", + "subscriptions-transport-ws": "^0.9.16", "trunc-html": "~1.1.2", "uuid": "~3.4.0", "validator": "^12.1.0", diff --git a/backend/src/config/index.js b/backend/src/config/index.js index 2f8d0ed22..59ab20af7 100644 --- a/backend/src/config/index.js +++ b/backend/src/config/index.js @@ -49,6 +49,7 @@ export const serverConfigs = { CLIENT_URI, GRAPHQL_URI, PUBLIC_REGISTRATION: process.env.PUBLIC_REGISTRATION === 'true', + SUBSCRIPTIONS_PATH: '/subscriptions', } export const developmentConfigs = { diff --git a/backend/src/index.js b/backend/src/index.js index 98354dc1f..0eb4f51b6 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,9 +1,10 @@ import createServer from './server' import CONFIG from './config' -const { app } = createServer() +const { app, 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}`) + console.log(`🚀 Subscriptions ready at ws://localhost:${url.port}${server.subscriptionsPath}`) }) diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index af8165997..f8609f7a6 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -5,6 +5,11 @@ import { UserInputError } from 'apollo-server' import fileUpload from './fileUpload' import Resolver from './helpers/Resolver' import { filterForMutedUsers } from './helpers/filterForMutedUsers' +import { PubSub } from 'apollo-server' + +const pubsub = new PubSub(); +const POST_ADDED = 'POST_ADDED'; + const maintainPinnedPosts = params => { const pinnedPostFilter = { pinned: true } @@ -17,6 +22,12 @@ const maintainPinnedPosts = params => { } export default { + Subscription: { + postAdded: { + // Additional event labels can be passed to asyncIterator creation + subscribe: () => pubsub.asyncIterator([POST_ADDED]), + }, + }, Query: { Post: async (object, params, context, resolveInfo) => { params = await filterForMutedUsers(params, context) @@ -102,6 +113,7 @@ export default { }) try { const [post] = await writeTxResultPromise + pubsub.publish(POST_ADDED, { postAdded: post }); return post } catch (e) { if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 23c2ded4d..b3df61af2 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -1,3 +1,7 @@ +type Subscription { + postAdded: Post +} + type Mutation { # Get a JWT Token for the given Email and password login(email: String!, password: String!): String! diff --git a/backend/src/server.js b/backend/src/server.js index 02e166b71..22431bd7f 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -1,6 +1,9 @@ import express from 'express' +import http from 'http' import helmet from 'helmet' import { ApolloServer } from 'apollo-server-express' + + import CONFIG from './config' import middleware from './middleware' import { getNeode, getDriver } from './db/neo4j' @@ -8,19 +11,28 @@ import decode from './jwt/decode' import schema from './schema' import webfinger from './activitypub/routes/webfinger' + const driver = getDriver() const neode = getNeode() -export const context = async ({ req }) => { - const user = await decode(driver, req.headers.authorization) - return { - driver, - neode, - user, - req, - cypherParams: { - currentUserId: user ? user.id : null, - }, +const getContext = async (req) => { + const user = await decode(driver, req.headers.authorization) + return { + driver, + neode, + user, + req, + cypherParams: { + currentUserId: user ? user.id : null, + }, + } +} +export const context = async (options) => { + const { connection, req } = options + if (connection) { + return connection.context + } else { + return getContext(req) } } @@ -28,6 +40,12 @@ const createServer = options => { const defaults = { context, schema: middleware(schema), + subscriptions: { + onConnect: (connectionParams, webSocket) => { + console.log('connectionParams', connectionParams) + return getContext(connectionParams) + }, + }, debug: !!CONFIG.DEBUG, tracing: !!CONFIG.DEBUG, formatError: error => { @@ -46,8 +64,11 @@ const createServer = options => { app.use('/.well-known/', webfinger()) app.use(express.static('public')) server.applyMiddleware({ app, path: '/' }) + const httpServer = http.createServer(app); + server.installSubscriptionHandlers(httpServer); - return { server, app } + + return { server, httpServer, app } } export default createServer diff --git a/backend/yarn.lock b/backend/yarn.lock index caf302820..9d3e0e571 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1175,7 +1175,7 @@ url-regex "~4.1.1" video-extensions "~1.1.0" -"@metascraper/helpers@^5.10.5", "@metascraper/helpers@^5.10.6": +"@metascraper/helpers@^5.10.6": version "5.10.6" resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.10.6.tgz#0b786607212925a577926fd0cd0313a49de3499c" integrity sha512-/jvhlM3RKGYMoUK8D8S1r3tN03/EYizCqWF7zDx0aBMC8Ihp33DRGs9oNdsgkgwzVF7O/YpDm55l9K+qVJlsyQ== @@ -7918,6 +7918,11 @@ serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue index 609c3d800..f0089979d 100644 --- a/webapp/pages/index.vue +++ b/webapp/pages/index.vue @@ -74,6 +74,16 @@ import { mapGetters, mapMutations } from 'vuex' import { filterPosts } from '~/graphql/PostQuery.js' import PostMutations from '~/graphql/PostMutations' import UpdateQuery from '~/components/utils/UpdateQuery' +import gql from 'graphql-tag' +import { + userFragment, + postFragment, + commentFragment, + postCountsFragment, + userCountsFragment, + locationAndBadgesFragment, + tagsCategoriesAndPinnedFragment, +} from '~/graphql/Fragments' export default { components: { @@ -213,6 +223,36 @@ export default { this.posts = Post }, fetchPolicy: 'cache-and-network', + subscribeToMore: { + document: gql` + ${userFragment} + ${userCountsFragment} + ${locationAndBadgesFragment('EN')} + ${postFragment} + ${postCountsFragment} + ${tagsCategoriesAndPinnedFragment} + + subscription Post { + postAdded { + ...post + ...postCounts + ...tagsCategoriesAndPinned + author { + ...user + ...userCounts + ...locationAndBadges + } + } + }`, + // Mutate the previous result + updateQuery: (previousResult, { subscriptionData }) => { + console.log('previousResult', previousResult) + console.log('subscriptionData', subscriptionData) + const { data: { postAdded: newPost } } = subscriptionData + return { Post: [newPost, ...previousResult.Post] } + // Here, return the new result from the previous with the new data + }, + } }, }, } diff --git a/webapp/plugins/apollo-config.js b/webapp/plugins/apollo-config.js index 4bf05f178..a4487c0bb 100644 --- a/webapp/plugins/apollo-config.js +++ b/webapp/plugins/apollo-config.js @@ -9,6 +9,7 @@ export default ({ app }) => { const backendUrl = process.env.GRAPHQL_URI || 'http://localhost:4000' return { + wsEndpoint: 'ws://localhost:4000/graphql', // optional httpEndpoint: process.server ? backendUrl : '/api', httpLinkOptions: { credentials: 'same-origin', @@ -16,7 +17,7 @@ export default ({ app }) => { credentials: true, tokenName: 'human-connection-token', persisting: false, - websocketsOnly: false, + websocketsOnly: true, cache: new InMemoryCache({ fragmentMatcher }), } }