mirror of
https://github.com/wassname/talk.git
synced 2026-06-27 16:32:15 +08:00
initial subscription support
This commit is contained in:
+2
-1
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
*.js
|
||||
*.js
|
||||
yarn.lock
|
||||
Generated
+95
-6
@@ -173,6 +173,16 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz",
|
||||
"integrity": "sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/events": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@@ -226,9 +236,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "9.6.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.21.tgz",
|
||||
"integrity": "sha512-zQS6mHzxEstR8Vvnpc3JDUCDGWnHFzMTcBu9UCZoVLuj1Uvkkk0qFKJQEhlvbsX34m3xt12ejV09eO/ljZcn7A=="
|
||||
"version": "9.6.22",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.22.tgz",
|
||||
"integrity": "sha512-RIg9EkxzVMkNH0M4sLRngK23f5QiigJC0iODQmu4nopzstt8AjegYund3r82iMrd2BNCjcZVnklaItvKHaGfBA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -265,9 +275,12 @@
|
||||
}
|
||||
},
|
||||
"apollo-utilities": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.13.tgz",
|
||||
"integrity": "sha512-WIbKDsFsLXMgPPGmlB2pL2noHT13TOLU2eRSyPjQMQwcz9lO9UKRkwK8pRJ8b4Zo/9qQHm30F/xlfP/OSr2ZSw=="
|
||||
"version": "1.0.16",
|
||||
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.16.tgz",
|
||||
"integrity": "sha512-5oKnElKqkV920KRbitiyISLeG63tUGAyNdotg58bQSX9Omr+smoNDTIRMRLbyIdKOYLaw3LpDaRepOPqljj0NQ==",
|
||||
"requires": {
|
||||
"fast-json-stable-stringify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
@@ -324,12 +337,22 @@
|
||||
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
|
||||
"dev": true
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz",
|
||||
"integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=",
|
||||
"dev": true
|
||||
},
|
||||
"backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@@ -941,6 +964,11 @@
|
||||
"through": "~2.3.1"
|
||||
}
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
|
||||
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA=="
|
||||
},
|
||||
"execa": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
|
||||
@@ -1122,6 +1150,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
@@ -1874,6 +1907,16 @@
|
||||
"graphql-playground-html": "1.6.0"
|
||||
}
|
||||
},
|
||||
"graphql-redis-subscriptions": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql-redis-subscriptions/-/graphql-redis-subscriptions-1.5.0.tgz",
|
||||
"integrity": "sha512-4R/rv3qg61/UuB/9enCdWJM9s4x6TRwXYubjAlPWXJuNhGcZXn6oELu9mrhm+8QuA924/GvOo8Z7hCqE617SeQ==",
|
||||
"requires": {
|
||||
"graphql-subscriptions": "^0.5.6",
|
||||
"ioredis": "^3.1.2",
|
||||
"iterall": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"graphql-request": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.6.0.tgz",
|
||||
@@ -1882,6 +1925,14 @@
|
||||
"cross-fetch": "2.0.0"
|
||||
}
|
||||
},
|
||||
"graphql-subscriptions": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-0.5.8.tgz",
|
||||
"integrity": "sha512-0CaZnXKBw2pwnIbvmVckby5Ge5e2ecmjofhYCdyeACbCly2j3WXDP/pl+s+Dqd2GQFC7y99NB+53jrt55CKxYQ==",
|
||||
"requires": {
|
||||
"iterall": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"graphql-tools": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-3.0.2.tgz",
|
||||
@@ -2355,6 +2406,16 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||
"integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4="
|
||||
},
|
||||
"lodash.isobject": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
|
||||
"integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0="
|
||||
},
|
||||
"lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
|
||||
},
|
||||
"lodash.keys": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz",
|
||||
@@ -3460,6 +3521,26 @@
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||
"dev": true
|
||||
},
|
||||
"subscriptions-transport-ws": {
|
||||
"version": "0.9.11",
|
||||
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.11.tgz",
|
||||
"integrity": "sha512-B8fwTIJy2buUcBXM6Ffbax30XcEqvCqL8RXwbivBAiB3X9ezrTcF5nYMmNGZ47sxrDYA1XfQ5W3aTgJEm8BFJA==",
|
||||
"requires": {
|
||||
"backo2": "^1.0.2",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"iterall": "^1.2.1",
|
||||
"lodash.assign": "^4.2.0",
|
||||
"lodash.isobject": "^3.0.2",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"symbol-observable": "^1.0.4",
|
||||
"ws": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
|
||||
},
|
||||
"term-size": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
|
||||
@@ -3836,6 +3917,14 @@
|
||||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.0.tgz",
|
||||
"integrity": "sha512-c18dMeW+PEQdDFzkhDsnBAlS4Z8KGStBQQUcQ5mf7Nf689jyGk0594L+i9RaQuf4gog6SvWLJorz2NfSaqxZ7w==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"xdg-basedir": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
|
||||
|
||||
+3
-1
@@ -21,6 +21,7 @@
|
||||
"express-static-gzip": "^0.3.2",
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-config": "^2.0.1",
|
||||
"graphql-redis-subscriptions": "^1.5.0",
|
||||
"graphql-tools": "^3.0.2",
|
||||
"ioredis": "^3.2.2",
|
||||
"joi": "^13.4.0",
|
||||
@@ -28,6 +29,7 @@
|
||||
"luxon": "^1.2.1",
|
||||
"mongodb": "^3.0.10",
|
||||
"performance-now": "^2.1.0",
|
||||
"subscriptions-transport-ws": "^0.9.11",
|
||||
"uuid": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -42,7 +44,7 @@
|
||||
"@types/luxon": "^0.5.3",
|
||||
"@types/mongodb": "^3.0.19",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"graphql-playground-html": "^1.6.0",
|
||||
"@types/ws": "^5.1.2",
|
||||
"graphql-playground-middleware-express": "^1.7.0",
|
||||
"nodemon": "^1.17.5",
|
||||
"prettier": "^1.13.4",
|
||||
|
||||
@@ -1,99 +1,34 @@
|
||||
import express, { Express, Router } from 'express';
|
||||
import { Express } from 'express';
|
||||
import { Db } from 'mongodb';
|
||||
import http from 'http';
|
||||
import { Redis } from 'ioredis';
|
||||
|
||||
import { Config } from 'talk-server/config';
|
||||
import { create } from 'talk-server/services/mongodb';
|
||||
import tenantGraphMiddleware from 'talk-server/graph/tenant/middleware';
|
||||
import managementGraphMiddleware from 'talk-server/graph/management/middleware';
|
||||
import { Schemas } from 'talk-server/graph/schemas';
|
||||
import { handleSubscriptions } from 'talk-server/graph/common/subscriptions/middleware';
|
||||
|
||||
import { createRouter } from './router';
|
||||
import serveStatic from './middleware/serveStatic';
|
||||
import playground from './middleware/playground';
|
||||
import {
|
||||
access as accessLogger,
|
||||
error as errorLogger,
|
||||
} from './middleware/logging';
|
||||
import { Redis } from 'ioredis';
|
||||
|
||||
export interface AppOptions {
|
||||
parent: Express;
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
redis: Redis;
|
||||
}
|
||||
|
||||
async function createManagementRouter(opts: AppOptions): Promise<Router> {
|
||||
const router = express.Router();
|
||||
|
||||
if (opts.config.get('env') === 'development') {
|
||||
// GraphiQL
|
||||
router.get(
|
||||
'/graphiql',
|
||||
playground({
|
||||
endpoint: '/api/management/graphql',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Management API
|
||||
router.use(
|
||||
'/graphql',
|
||||
express.json(),
|
||||
managementGraphMiddleware(opts.mongo)
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async function createTenantRouter(opts: AppOptions): Promise<Router> {
|
||||
const router = express.Router();
|
||||
|
||||
if (opts.config.get('env') === 'development') {
|
||||
// GraphiQL
|
||||
router.get(
|
||||
'/graphiql',
|
||||
playground({
|
||||
endpoint: '/api/tenant/graphql',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Tenant API
|
||||
router.use('/graphql', express.json(), tenantGraphMiddleware(opts.mongo));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async function createAPIRouter(opts: AppOptions): Promise<Router> {
|
||||
// Create a router.
|
||||
const router = express.Router();
|
||||
|
||||
// Configure the tenant routes.
|
||||
router.use('/tenant', await createTenantRouter(opts));
|
||||
|
||||
// Configure the management routes.
|
||||
router.use('/management', await createManagementRouter(opts));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async function createRouter(opts: AppOptions): Promise<Router> {
|
||||
// Create a router.
|
||||
const router = express.Router();
|
||||
|
||||
router.use('/api', await createAPIRouter(opts));
|
||||
|
||||
return router;
|
||||
schemas: Schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* createApp will create a Talk Express app that can be used to handle requests.
|
||||
*
|
||||
* @param parent the root application to attach the Talk routes/middleware to.
|
||||
*/
|
||||
export async function createApp(
|
||||
parent: Express,
|
||||
options: AppOptions
|
||||
): Promise<Express> {
|
||||
export async function createApp(options: AppOptions): Promise<Express> {
|
||||
// Pull the parent out of the options.
|
||||
const { parent } = options;
|
||||
|
||||
// Logging
|
||||
parent.use(accessLogger);
|
||||
|
||||
@@ -110,13 +45,40 @@ export async function createApp(
|
||||
}
|
||||
|
||||
/**
|
||||
* startApp will start the given express application.
|
||||
* listenAndServe will start the given express application.
|
||||
*
|
||||
* @param port the port to listen on
|
||||
* @param app the express application to start
|
||||
* @param port the port to listen on
|
||||
*/
|
||||
export const startApp = (port: number, app: Express): Promise<http.Server> =>
|
||||
export const listenAndServe = (
|
||||
app: Express,
|
||||
port: number
|
||||
): Promise<http.Server> =>
|
||||
new Promise(async resolve => {
|
||||
// Listen on the designated port.
|
||||
const httpServer = app.listen(port, () => resolve(httpServer));
|
||||
});
|
||||
|
||||
/**
|
||||
* attachSubscriptionHandlers attaches all the handlers to the http.Server to
|
||||
* handle websocket traffic by upgrading their http connections to websocket.
|
||||
*
|
||||
* @param schemas schemas for every schema this application handles
|
||||
* @param server the http.Server to attach the websocket upgraders to
|
||||
*/
|
||||
export async function attachSubscriptionHandlers(
|
||||
schemas: Schemas,
|
||||
server: http.Server
|
||||
) {
|
||||
// Setup the Management Subscription endpoint.
|
||||
handleSubscriptions(server, {
|
||||
schema: schemas.management,
|
||||
path: '/api/management/live',
|
||||
});
|
||||
|
||||
// Setup the Tenant Subscription endpoint.
|
||||
handleSubscriptions(server, {
|
||||
schema: schemas.tenant,
|
||||
path: '/api/tenant/live',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import express, { Router } from 'express';
|
||||
|
||||
import tenantGraphMiddleware from 'talk-server/graph/tenant/middleware';
|
||||
import managementGraphMiddleware from 'talk-server/graph/management/middleware';
|
||||
|
||||
import { AppOptions } from './index';
|
||||
import playground from './middleware/playground';
|
||||
|
||||
async function createManagementRouter(opts: AppOptions) {
|
||||
const router = express.Router();
|
||||
|
||||
if (opts.config.get('env') === 'development') {
|
||||
// GraphiQL
|
||||
router.get(
|
||||
'/graphiql',
|
||||
playground({
|
||||
endpoint: '/api/management/graphql',
|
||||
subscriptionEndpoint: '/api/management/live',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Management API
|
||||
router.use(
|
||||
'/graphql',
|
||||
express.json(),
|
||||
await managementGraphMiddleware(
|
||||
opts.schemas.management,
|
||||
opts.config,
|
||||
opts.mongo
|
||||
)
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async function createTenantRouter(opts: AppOptions) {
|
||||
const router = express.Router();
|
||||
|
||||
if (opts.config.get('env') === 'development') {
|
||||
// GraphiQL
|
||||
router.get(
|
||||
'/graphiql',
|
||||
playground({
|
||||
endpoint: '/api/tenant/graphql',
|
||||
subscriptionEndpoint: '/api/tenant/live',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Tenant API
|
||||
router.use(
|
||||
'/graphql',
|
||||
express.json(),
|
||||
await tenantGraphMiddleware(
|
||||
opts.schemas.tenant,
|
||||
opts.config,
|
||||
opts.mongo
|
||||
)
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async function createAPIRouter(opts: AppOptions) {
|
||||
// Create a router.
|
||||
const router = express.Router();
|
||||
|
||||
// Configure the tenant routes.
|
||||
router.use('/tenant', await createTenantRouter(opts));
|
||||
|
||||
// Configure the management routes.
|
||||
router.use('/management', await createManagementRouter(opts));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
export async function createRouter(opts: AppOptions) {
|
||||
// Create a router.
|
||||
const router = express.Router();
|
||||
|
||||
router.use('/api', await createAPIRouter(opts));
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ convict.addFormat({
|
||||
},
|
||||
});
|
||||
|
||||
export const config = convict({
|
||||
const config = convict({
|
||||
env: {
|
||||
doc: 'The application environment.',
|
||||
format: ['production', 'development', 'test'],
|
||||
@@ -67,6 +67,13 @@ export const config = convict({
|
||||
env: 'SECRET',
|
||||
arg: 'secret',
|
||||
},
|
||||
logging_level: {
|
||||
doc: 'The logging level to print to the console',
|
||||
format: ['fatal', 'error', 'warn', 'info', 'debug', 'trace'],
|
||||
default: 'info',
|
||||
env: 'LOGGING_LEVEL',
|
||||
arg: 'logging',
|
||||
},
|
||||
});
|
||||
|
||||
export type Config = typeof config;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
graphqlExpress,
|
||||
ExpressGraphQLOptionsFunction,
|
||||
GraphQLOptions,
|
||||
} from 'apollo-server-express';
|
||||
import { GraphQLError, FieldDefinitionNode, ValidationContext } from 'graphql';
|
||||
import { resolveGraphqlOptions } from 'apollo-server-core';
|
||||
import { Config } from 'talk-server/config';
|
||||
|
||||
// Sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L46-L57
|
||||
const NoIntrospection = (context: ValidationContext) => ({
|
||||
Field(node: FieldDefinitionNode) {
|
||||
if (node.name.value === '__schema' || node.name.value === '__type') {
|
||||
context.reportError(
|
||||
new GraphQLError(
|
||||
'GraphQL introspection is not allowed in production, but the query contained __schema or __type.',
|
||||
[node]
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const graphqlMiddleware = (
|
||||
config: Config,
|
||||
baseOptions: GraphQLOptions | ExpressGraphQLOptionsFunction
|
||||
) => {
|
||||
// Generate the validation rules.
|
||||
const validationRules: Array<(context: ValidationContext) => any> = [];
|
||||
|
||||
if (config.get('env') !== 'production') {
|
||||
// Disable introspection in production.
|
||||
validationRules.push(NoIntrospection);
|
||||
}
|
||||
|
||||
// Generate the actual middleware.
|
||||
return graphqlExpress(async (req, res) => {
|
||||
// Resolve the base options.
|
||||
const requestOptions = await resolveGraphqlOptions(
|
||||
baseOptions,
|
||||
req,
|
||||
res
|
||||
);
|
||||
|
||||
// Apply the validators, sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L104-L107
|
||||
requestOptions.validationRules = requestOptions.validationRules
|
||||
? requestOptions.validationRules.concat(validationRules)
|
||||
: validationRules;
|
||||
|
||||
return requestOptions;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { addResolveFunctionsToSchema, IResolvers } from 'graphql-tools';
|
||||
import { getGraphQLProjectConfig } from 'graphql-config';
|
||||
|
||||
export default function loadSchema(projectName: string, resolvers: IResolvers) {
|
||||
// Load the configuration from the provided `.graphqlconfig` file.
|
||||
const config = getGraphQLProjectConfig(__dirname, projectName);
|
||||
|
||||
// Get the GraphQLSchema from the configuration.
|
||||
const schema = config.getSchema();
|
||||
|
||||
// Attach the resolvers to the schema.
|
||||
addResolveFunctionsToSchema({ schema, resolvers });
|
||||
|
||||
return schema;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import http from 'http';
|
||||
import { SubscriptionServer } from 'subscriptions-transport-ws';
|
||||
import { GraphQLSchema, execute, subscribe } from 'graphql';
|
||||
|
||||
export interface SubscriptionMiddlewareOptions {
|
||||
schema: GraphQLSchema;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export function handleSubscriptions(
|
||||
server: http.Server,
|
||||
{ schema, path }: SubscriptionMiddlewareOptions
|
||||
): SubscriptionServer {
|
||||
// Configure some options for the subscription system.
|
||||
const options = {
|
||||
schema,
|
||||
execute,
|
||||
subscribe,
|
||||
};
|
||||
|
||||
// Configure the socket options for the websocket server. It needs to handle
|
||||
// upgrade requests on that route.
|
||||
const socketOption = {
|
||||
server,
|
||||
path,
|
||||
};
|
||||
|
||||
return new SubscriptionServer(options, socketOption);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
||||
import { createRedisClient } from 'talk-server/services/redis';
|
||||
import { Config } from 'talk-server/config';
|
||||
|
||||
export async function createPubSub(config: Config): Promise<RedisPubSub> {
|
||||
// Create the Redis clients for the PubSub server.
|
||||
const publisher = await createRedisClient(config);
|
||||
const subscriber = await createRedisClient(config);
|
||||
|
||||
// Create the new PubSub manager.
|
||||
return new RedisPubSub({
|
||||
publisher,
|
||||
subscriber,
|
||||
});
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import { graphqlExpress } from 'apollo-server-express';
|
||||
import schema from './schema';
|
||||
import Context from './context';
|
||||
import { Db } from 'mongodb';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
|
||||
export default (db: Db) =>
|
||||
graphqlExpress(async req => {
|
||||
return {
|
||||
schema,
|
||||
context: new Context({ db }),
|
||||
};
|
||||
});
|
||||
import { graphqlMiddleware } from 'talk-server/graph/common/middleware';
|
||||
import { Config } from 'talk-server/config';
|
||||
|
||||
import Context from './context';
|
||||
|
||||
export default (schema: GraphQLSchema, config: Config, db: Db) =>
|
||||
graphqlMiddleware(config, async () => ({
|
||||
schema,
|
||||
context: new Context({ db }),
|
||||
}));
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { addResolveFunctionsToSchema } from 'graphql-tools';
|
||||
import { getGraphQLProjectConfig } from 'graphql-config';
|
||||
import loadSchema from 'talk-server/graph/common/schema';
|
||||
import resolvers from 'talk-server/graph/management/resolvers';
|
||||
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
// Load the configuration from the provided `.graphqlconfig` file.
|
||||
const config = getGraphQLProjectConfig(__dirname, 'management');
|
||||
|
||||
// Get the GraphQLSchema from the configuration.
|
||||
const schema = config.getSchema();
|
||||
|
||||
// Attach the resolvers to the schema.
|
||||
addResolveFunctionsToSchema({ schema, resolvers });
|
||||
|
||||
export default schema;
|
||||
export default function getManagementSchema() {
|
||||
return loadSchema('management', resolvers);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
|
||||
export interface Schemas {
|
||||
management: GraphQLSchema;
|
||||
tenant: GraphQLSchema;
|
||||
}
|
||||
@@ -1,13 +1,24 @@
|
||||
import { graphqlExpress } from 'apollo-server-express';
|
||||
import schema from './schema';
|
||||
import TenantContext from './context';
|
||||
import { Db } from 'mongodb';
|
||||
import { Tenant } from 'talk-server/models/tenant';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
|
||||
import { retrieveByDomain } from 'talk-server/models/tenant';
|
||||
import { createPubSub } from 'talk-server/graph/common/subscriptions/pubsub';
|
||||
import { Config } from 'talk-server/config';
|
||||
import { graphqlMiddleware } from 'talk-server/graph/common/middleware';
|
||||
|
||||
import TenantContext from './context';
|
||||
|
||||
export default async (schema: GraphQLSchema, config: Config, db: Db) => {
|
||||
// Configure the PubSub broker.
|
||||
const pubsub = await createPubSub(config);
|
||||
|
||||
return graphqlMiddleware(config, async req => {
|
||||
// TODO: replace with shared synced cache instead of direct db access.
|
||||
const tenant = await retrieveByDomain(db, req.hostname);
|
||||
|
||||
export default (db: Db) =>
|
||||
graphqlExpress(async req => {
|
||||
return {
|
||||
schema,
|
||||
context: new TenantContext({ db, tenant: { id: '1' } as Tenant }),
|
||||
context: new TenantContext({ db, tenant }),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Context from 'talk-server/graph/tenant/context';
|
||||
import TenantContext from 'talk-server/graph/tenant/context';
|
||||
import { Asset } from 'talk-server/models/asset';
|
||||
|
||||
export default {
|
||||
asset: async (
|
||||
_: any,
|
||||
{ id, url }: { id?: string; url: string },
|
||||
ctx: Context
|
||||
ctx: TenantContext
|
||||
): Promise<Asset> => {
|
||||
return ctx.loaders.Assets.asset.load(id);
|
||||
},
|
||||
settings: async (parent: any, args: any, ctx: TenantContext) => ctx.tenant,
|
||||
};
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { addResolveFunctionsToSchema } from 'graphql-tools';
|
||||
import { getGraphQLProjectConfig } from 'graphql-config';
|
||||
import loadSchema from 'talk-server/graph/common/schema';
|
||||
import resolvers from 'talk-server/graph/tenant/resolvers';
|
||||
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
// Load the configuration from the provided `.graphqlconfig` file.
|
||||
const config = getGraphQLProjectConfig(__dirname, 'tenant');
|
||||
|
||||
// Get the GraphQLSchema from the configuration.
|
||||
const schema = config.getSchema();
|
||||
|
||||
// Attach the resolvers to the schema.
|
||||
addResolveFunctionsToSchema({ schema, resolvers });
|
||||
|
||||
export default schema;
|
||||
export default function getTenantSchema() {
|
||||
return loadSchema('tenant', resolvers);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import config, { Config } from './config';
|
||||
import express, { Express } from 'express';
|
||||
import http from 'http';
|
||||
import { createApp, startApp } from './app';
|
||||
|
||||
import config, { Config } from './config';
|
||||
import { createApp, listenAndServe, attachSubscriptionHandlers } from './app';
|
||||
import logger from './logger';
|
||||
import { create as createMongoDB } from './services/mongodb';
|
||||
import { create as createRedis } from 'talk-server/services/redis';
|
||||
import { createMongoDB } from './services/mongodb';
|
||||
import { createRedisClient } from './services/redis';
|
||||
import getManagementSchema from 'talk-server/graph/management/schema';
|
||||
import getTenantSchema from 'talk-server/graph/tenant/schema';
|
||||
import { Schemas } from 'talk-server/graph/schemas';
|
||||
|
||||
export interface ServerOptions {
|
||||
config?: Config;
|
||||
@@ -17,6 +21,10 @@ class Server {
|
||||
// parentApp is the root application that the server will bind to.
|
||||
private parentApp: Express;
|
||||
|
||||
// schemas are the set of GraphQLSchema objects for each schema used by the
|
||||
// server.
|
||||
private schemas: Schemas;
|
||||
|
||||
// config exposes application specific configuration.
|
||||
public config: Config;
|
||||
|
||||
@@ -29,6 +37,12 @@ class Server {
|
||||
this.config = config
|
||||
.load(options.config || {})
|
||||
.validate({ allowed: 'strict' });
|
||||
|
||||
// Load the graph schemas.
|
||||
this.schemas = {
|
||||
management: getManagementSchema(),
|
||||
tenant: getTenantSchema(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,17 +61,22 @@ class Server {
|
||||
const mongo = await createMongoDB(config);
|
||||
|
||||
// Setup Redis.
|
||||
const redis = await createRedis(config);
|
||||
const redis = await createRedisClient(config);
|
||||
|
||||
// Create the Talk App, branching off from the parent app.
|
||||
const app = await createApp(parent, {
|
||||
const app: Express = await createApp({
|
||||
parent,
|
||||
mongo,
|
||||
redis,
|
||||
config: this.config,
|
||||
schemas: this.schemas,
|
||||
});
|
||||
|
||||
// Start the application.
|
||||
this.httpServer = await startApp(port, app);
|
||||
// Start the application and store the resulting http.Server.
|
||||
this.httpServer = await listenAndServe(app, port);
|
||||
|
||||
// Setup the websocket servers on the new http.Server.
|
||||
attachSubscriptionHandlers(this.schemas, this.httpServer);
|
||||
|
||||
logger.info({ port }, 'now listening');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Config } from 'talk-server/config';
|
||||
*
|
||||
* @param config application configuration.
|
||||
*/
|
||||
export async function create(config: Config): Promise<Db> {
|
||||
export async function createMongoDB(config: Config): Promise<Db> {
|
||||
// Connect and create a client for MongoDB.
|
||||
const client = await MongoClient.connect(config.get('mongodb'));
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@ import { Config } from 'talk-server/config';
|
||||
*
|
||||
* @param config application configuration.
|
||||
*/
|
||||
export async function create(config: Config): Promise<Redis> {
|
||||
export async function createRedisClient(config: Config): Promise<Redis> {
|
||||
return new RedisClient(config.get('redis'), {});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user