mirror of
https://github.com/wassname/talk.git
synced 2026-07-28 11:27:05 +08:00
Merge branch 'next' into next-static-uri
This commit is contained in:
@@ -4,7 +4,7 @@ import { noop } from "lodash";
|
||||
import { Child as PymChild } from "pym.js";
|
||||
import React, { Component, ComponentType } from "react";
|
||||
import { Formatter } from "react-timeago";
|
||||
import { Environment, Network, RecordSource, Store } from "relay-runtime";
|
||||
import { Environment, RecordSource, Store } from "relay-runtime";
|
||||
import uuid from "uuid/v4";
|
||||
|
||||
import { getBrowserInfo } from "talk-framework/lib/browserInfo";
|
||||
@@ -21,7 +21,7 @@ import { RestClient } from "talk-framework/lib/rest";
|
||||
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
|
||||
|
||||
import { generateBundles, LocalesData, negotiateLanguages } from "../i18n";
|
||||
import { createFetch, TokenGetter } from "../network";
|
||||
import { createNetwork, TokenGetter } from "../network";
|
||||
import { PostMessageService } from "../postMessage";
|
||||
import { TalkContext, TalkContextProvider } from "./TalkContext";
|
||||
|
||||
@@ -97,7 +97,7 @@ function createRelayEnvironment() {
|
||||
return "";
|
||||
};
|
||||
const environment = new Environment({
|
||||
network: Network.create(createFetch(tokenGetter)),
|
||||
network: createNetwork(tokenGetter),
|
||||
store: new Store(source),
|
||||
});
|
||||
return { environment, tokenGetter };
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export interface GraphQLErrorItem {
|
||||
message: string;
|
||||
locations: Array<{
|
||||
line: number;
|
||||
column: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Graphql wraps graphql errors at the network layer.
|
||||
*/
|
||||
export default class GraphQLError extends Error {
|
||||
// Original error.
|
||||
public readonly origin: GraphQLErrorItem[];
|
||||
|
||||
constructor(origin: GraphQLErrorItem[]) {
|
||||
super(origin.map(o => o.message).join(" "));
|
||||
|
||||
// Maintains proper stack trace for where our error was thrown.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, GraphQLError);
|
||||
}
|
||||
this.origin = origin;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,2 @@
|
||||
export { default as NetworkError } from "./networkError";
|
||||
export { default as UnknownServerError } from "./unknownServerError";
|
||||
export { default as BadUserInputError } from "./badUserInputError";
|
||||
export { default as GraphQLError } from "./graphqlError";
|
||||
|
||||
export * from "./graphqlError";
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* NetworkError wraps errors at the network layer.
|
||||
*/
|
||||
export default class NetworkError extends Error {
|
||||
// Original error.
|
||||
public readonly origin: Error;
|
||||
|
||||
constructor(origin: Error) {
|
||||
// Pass remaining arguments (including vendor specific ones) to parent constructor.
|
||||
super(origin.message);
|
||||
|
||||
// Maintains proper stack trace for where our error was thrown.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, NetworkError);
|
||||
}
|
||||
this.origin = origin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
authMiddleware,
|
||||
batchMiddleware,
|
||||
cacheMiddleware,
|
||||
RelayNetworkLayer,
|
||||
retryMiddleware,
|
||||
urlMiddleware,
|
||||
} from "react-relay-network-modern/es";
|
||||
|
||||
import customErrorMiddleware from "./customErrorMiddleware";
|
||||
|
||||
export type TokenGetter = () => string;
|
||||
|
||||
const graphqlURL = "/api/tenant/graphql";
|
||||
|
||||
export default function createNetwork(tokenGetter: TokenGetter) {
|
||||
return new RelayNetworkLayer([
|
||||
customErrorMiddleware,
|
||||
cacheMiddleware({
|
||||
size: 100, // max 100 requests
|
||||
ttl: 900000, // 15 minutes
|
||||
}),
|
||||
urlMiddleware({
|
||||
url: req => Promise.resolve(graphqlURL),
|
||||
}),
|
||||
batchMiddleware({
|
||||
batchUrl: (requestMap: any) => Promise.resolve(graphqlURL),
|
||||
batchTimeout: 10,
|
||||
}),
|
||||
retryMiddleware({
|
||||
fetchTimeout: 15000,
|
||||
retryDelays: (attempt: number) => Math.pow(2, attempt + 4) * 100,
|
||||
// or simple array [3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600],
|
||||
statusCodes: [500, 503, 504],
|
||||
}),
|
||||
authMiddleware({
|
||||
token: tokenGetter,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Middleware } from "react-relay-network-modern/es";
|
||||
import { BadUserInputError, UnknownServerError } from "../errors";
|
||||
|
||||
function getError(errors: Error[]): Error | null {
|
||||
if (errors.length > 1 || !(errors[0] as any).extensions) {
|
||||
// Multiple errors are GraphQL errors.
|
||||
// TODO: (cvle) Is this assumption correct?
|
||||
// No extensions == GraphQL error.
|
||||
// TODO: (cvle) harmonize with server.
|
||||
return null;
|
||||
}
|
||||
const err = errors[0];
|
||||
if ((err as any).code === "BAD_USER_INPUT") {
|
||||
return new BadUserInputError((err as any).extensions);
|
||||
}
|
||||
return new UnknownServerError(err.message, (err as any).extensions);
|
||||
}
|
||||
|
||||
const customErrorMiddleware: Middleware = next => async req => {
|
||||
const res = await next(req);
|
||||
if (req.isMutation() && res.errors) {
|
||||
// Extract custom error.
|
||||
const error = getError(res.errors);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
export default customErrorMiddleware;
|
||||
@@ -1,72 +0,0 @@
|
||||
import { FetchFunction } from "relay-runtime";
|
||||
|
||||
import {
|
||||
BadUserInputError,
|
||||
GraphQLError,
|
||||
NetworkError,
|
||||
UnknownServerError,
|
||||
} from "../errors";
|
||||
|
||||
// Normalize errors.
|
||||
function getError(errors: Error[]): Error {
|
||||
if (errors.length > 1) {
|
||||
// Multiple errors are GraphQL errors.
|
||||
// TODO: (cvle) Is this assumption correct?
|
||||
return new GraphQLError(errors as any);
|
||||
}
|
||||
const err = errors[0] as Error;
|
||||
if ((err as any).extensions) {
|
||||
if ((err as any).code === "BAD_USER_INPUT") {
|
||||
return new BadUserInputError((err as any).extensions);
|
||||
}
|
||||
return new UnknownServerError(err.message, (err as any).extensions);
|
||||
}
|
||||
// No extensions == GraphQL error.
|
||||
// TODO: (cvle) harmonize with server.
|
||||
return new GraphQLError(errors as any);
|
||||
}
|
||||
|
||||
export type TokenGetter = () => string;
|
||||
type CreateFetch = (token?: TokenGetter) => FetchFunction;
|
||||
|
||||
/**
|
||||
* createFetch returns a simple implementation of the `FetchFunction`
|
||||
* required by Relay. It'll return a `NetworkError` on failure.
|
||||
*/
|
||||
const createFetch: CreateFetch = tokenGetter => async (
|
||||
operation,
|
||||
variables
|
||||
) => {
|
||||
const token = tokenGetter && tokenGetter();
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
try {
|
||||
const response = await fetch("/api/tenant/graphql", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
query: operation.text,
|
||||
variables,
|
||||
}),
|
||||
});
|
||||
if (response.status >= 500) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data.errors) {
|
||||
throw getError(data.errors);
|
||||
}
|
||||
return data;
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
throw new NetworkError(err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export default createFetch;
|
||||
@@ -1 +1 @@
|
||||
export { default as createFetch, TokenGetter } from "./fetchQuery";
|
||||
export { default as createNetwork, TokenGetter } from "./createNetwork";
|
||||
|
||||
@@ -24,17 +24,19 @@ function sharedUpdater(
|
||||
store: RecordSourceSelectorProxy,
|
||||
input: CreateCommentInput
|
||||
) {
|
||||
updateAsset(store, input);
|
||||
if (input.local) {
|
||||
localUpdate(store, input);
|
||||
} else {
|
||||
update(store, input);
|
||||
}
|
||||
updateProfile(store, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* update integrates new comment into the CommentConnection.
|
||||
*/
|
||||
function update(store: RecordSourceSelectorProxy, input: CreateCommentInput) {
|
||||
function updateAsset(
|
||||
store: RecordSourceSelectorProxy,
|
||||
input: CreateCommentInput
|
||||
) {
|
||||
// Updating Comment Count
|
||||
const asset = store.get(input.assetID);
|
||||
if (asset) {
|
||||
@@ -45,7 +47,12 @@ function update(store: RecordSourceSelectorProxy, input: CreateCommentInput) {
|
||||
record.setValue(currentCount + 1, "totalVisible");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update integrates new comment into the CommentConnection.
|
||||
*/
|
||||
function update(store: RecordSourceSelectorProxy, input: CreateCommentInput) {
|
||||
// Get the payload returned from the server.
|
||||
const payload = store.getRootField("createComment")!;
|
||||
|
||||
@@ -109,6 +116,26 @@ function localUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updateProfile integrates new comment into the profile.
|
||||
*/
|
||||
function updateProfile(
|
||||
store: RecordSourceSelectorProxy,
|
||||
input: CreateCommentInput
|
||||
) {
|
||||
// Get the payload returned from the server.
|
||||
const payload = store.getRootField("createComment")!;
|
||||
|
||||
// Get the edge of the newly created comment.
|
||||
const newEdge = payload.getLinkedRecord("edge")!;
|
||||
const newComment = newEdge.getLinkedRecord("node");
|
||||
|
||||
// TODO: update profile comments connection after we
|
||||
// integrated pagination.
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
newComment;
|
||||
}
|
||||
|
||||
const mutation = graphql`
|
||||
mutation CreateCommentMutation($input: CreateCommentInput!) {
|
||||
createComment(input: $input) {
|
||||
|
||||
@@ -64,6 +64,10 @@ const ProfileQuery: StatelessComponent<InnerProps> = ({
|
||||
assetID,
|
||||
assetURL,
|
||||
}}
|
||||
cacheConfig={{
|
||||
// TODO: enable caching after mutations are adapted.
|
||||
force: true,
|
||||
}}
|
||||
render={render}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1003,7 +1003,7 @@ exports[`post a reply: optimistic response 1`] = `
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
1 Comment
|
||||
2 Comments
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
@@ -2072,7 +2072,7 @@ exports[`post a reply: server response 1`] = `
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
1 Comment
|
||||
2 Comments
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
|
||||
@@ -43,7 +43,7 @@ export const signupHandler = (options: SignupOptions): RequestHandler => async (
|
||||
// TODO: rate limit based on the IP address and user agent.
|
||||
|
||||
// Tenant is guaranteed at this point.
|
||||
const tenant = req.tenant!;
|
||||
const tenant = req.talk!.tenant!;
|
||||
|
||||
// Check to ensure that the local integration has been enabled.
|
||||
if (!tenant.auth.integrations.local.enabled) {
|
||||
@@ -95,7 +95,7 @@ export const logoutHandler = (options: LogoutOptions): RequestHandler => async (
|
||||
// TODO: rate limit based on the IP address and user agent.
|
||||
|
||||
// Tenant is guaranteed at this point.
|
||||
const tenant = req.tenant!;
|
||||
const tenant = req.talk!.tenant!;
|
||||
|
||||
// Check to ensure that the local integration has been enabled.
|
||||
if (!tenant.auth.integrations.local.enabled) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { RequestHandler } from "express-jwt";
|
||||
import { Redis } from "ioredis";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import TenantContext from "talk-server/graph/tenant/context";
|
||||
import { TaskQueue } from "talk-server/services/queue";
|
||||
import { Request } from "talk-server/types/express";
|
||||
|
||||
export interface TenantContextMiddlewareOptions {
|
||||
mongo: Db;
|
||||
redis: Redis;
|
||||
queue: TaskQueue;
|
||||
}
|
||||
|
||||
export const tenantContext = ({
|
||||
mongo,
|
||||
redis,
|
||||
queue,
|
||||
}: TenantContextMiddlewareOptions): RequestHandler => (
|
||||
req: Request,
|
||||
res,
|
||||
next
|
||||
) => {
|
||||
if (!req.talk) {
|
||||
return next(new Error("talk was not set"));
|
||||
}
|
||||
|
||||
const { tenant, cache } = req.talk;
|
||||
|
||||
if (!cache) {
|
||||
return next(new Error("cache was not set"));
|
||||
}
|
||||
|
||||
if (!tenant) {
|
||||
return next(new Error("tenant was not set"));
|
||||
}
|
||||
|
||||
req.talk.context = {
|
||||
tenant: new TenantContext({
|
||||
req,
|
||||
mongo,
|
||||
redis,
|
||||
tenant,
|
||||
user: req.user,
|
||||
tenantCache: cache.tenant,
|
||||
queue,
|
||||
}),
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
import { RequestHandler, Response } from "express";
|
||||
|
||||
import logger from "talk-server/logger";
|
||||
import { Request } from "talk-server/types/express";
|
||||
|
||||
function wrapResponse(req: Request, res: Response) {
|
||||
// If the request is not an array, or has no elements, we should skip it.
|
||||
if (!Array.isArray(req.body) || req.body.length === 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// If the request is an array, but it does not have an ID field, then we
|
||||
// should skip it.
|
||||
const needsUpgrade = Boolean(typeof req.body[0].id !== "undefined");
|
||||
if (!needsUpgrade) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Grab all the existing ID's.
|
||||
const ids: string[] = req.body.map(({ id }) => id);
|
||||
|
||||
// Save a reference to the old setHeader function.
|
||||
const setHeader = res.setHeader.bind(res);
|
||||
|
||||
// Capture all the headers that are sent to this, in case we need to use it.
|
||||
const setHeaders: Record<string, any> = {};
|
||||
res.setHeader = (name: string, value: any) => {
|
||||
setHeaders[name] = value;
|
||||
return res;
|
||||
};
|
||||
|
||||
// Save a reference to the old write function.
|
||||
const write = res.write.bind(res);
|
||||
|
||||
// Create a flush function that will be used to flush the response to the
|
||||
// underlying response.
|
||||
const flush = (chunk: any, headers: Record<string, any> = setHeaders) => {
|
||||
for (const name in headers) {
|
||||
if (!headers.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setHeader(name, headers[name]);
|
||||
}
|
||||
|
||||
return write(chunk);
|
||||
};
|
||||
|
||||
// Override the response writer to parse the response to determine if it needs
|
||||
// to be rewritten.
|
||||
res.write = (chunk: string) => {
|
||||
try {
|
||||
// If there is no response, forward it, or if we peek at the first
|
||||
// character and it's not an array opening, then skip it.
|
||||
if (chunk.length <= 0 || chunk[0] !== "[") {
|
||||
return flush(chunk);
|
||||
}
|
||||
|
||||
// Parse the responses, if it's not an array, then skip it.
|
||||
const responses: object[] | any = JSON.parse(chunk);
|
||||
if (!Array.isArray(responses) || responses.length === 0) {
|
||||
return flush(chunk);
|
||||
}
|
||||
|
||||
// If the length of responses do not equal the length of id's collected,
|
||||
// then skip it.
|
||||
if (responses.length !== ids.length) {
|
||||
return flush(chunk);
|
||||
}
|
||||
|
||||
// For each of the responses, zip up their id's into the objects, and
|
||||
// string concat them together to ensure we get the right request.
|
||||
const gqlResponse = responses.reduce((body: object[], payload, idx) => {
|
||||
const id = ids[idx];
|
||||
body.push({ id, payload });
|
||||
return body;
|
||||
}, []);
|
||||
|
||||
const response = JSON.stringify(gqlResponse);
|
||||
|
||||
return flush(response, {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(response, "utf8").toString(),
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error({ err }, "could not parse chunk as JSON");
|
||||
return flush(chunk);
|
||||
}
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export const graphqlBatchMiddleware = (
|
||||
graphqlRequestHandler: RequestHandler
|
||||
): RequestHandler => (req: Request, res, next) =>
|
||||
graphqlRequestHandler(req, wrapResponse(req, res), next);
|
||||
@@ -102,8 +102,8 @@ export async function handleSuccessfulLogin(
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
// Grab the tenant from the request.
|
||||
const { tenant } = req;
|
||||
// Talk is guaranteed at this point.
|
||||
const { tenant } = req.talk!;
|
||||
|
||||
const options: SigningTokenOptions = {};
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ export class JWTStrategy extends Strategy {
|
||||
return this.pass();
|
||||
}
|
||||
|
||||
const { tenant } = req;
|
||||
const { tenant } = req.talk!;
|
||||
if (!tenant) {
|
||||
// TODO: (wyattjoh) log this error, and return a better one?
|
||||
return this.error(new Error("tenant not found"));
|
||||
|
||||
@@ -18,7 +18,7 @@ const verifyFactory = (mongo: Db) => async (
|
||||
// TODO: rate limit based on the IP address and user agent.
|
||||
|
||||
// The tenant is guaranteed at this point.
|
||||
const tenant = req.tenant!;
|
||||
const tenant = req.talk!.tenant!;
|
||||
|
||||
// Get the user from the database.
|
||||
const user = await retrieveUserWithProfile(mongo, tenant.id, {
|
||||
|
||||
@@ -265,7 +265,7 @@ export default class OIDCStrategy extends Strategy {
|
||||
}
|
||||
|
||||
// Grab the tenant out of the request, as we need some more details.
|
||||
const { tenant } = req;
|
||||
const { tenant } = req.talk!;
|
||||
if (!tenant) {
|
||||
// TODO: return a better error.
|
||||
return done(new Error("tenant not found"));
|
||||
@@ -336,7 +336,7 @@ export default class OIDCStrategy extends Strategy {
|
||||
}
|
||||
|
||||
private async lookupStrategy(req: Request) {
|
||||
const { tenant } = req;
|
||||
const { tenant } = req.talk!;
|
||||
if (!tenant) {
|
||||
// TODO: return a better error.
|
||||
throw new Error("tenant not found");
|
||||
|
||||
@@ -7,14 +7,12 @@ export interface MiddlewareOptions {
|
||||
cache: TenantCache;
|
||||
}
|
||||
|
||||
export default (options: MiddlewareOptions): RequestHandler => async (
|
||||
export default ({ cache }: MiddlewareOptions): RequestHandler => async (
|
||||
req: Request,
|
||||
res,
|
||||
next
|
||||
) => {
|
||||
try {
|
||||
const { cache } = options;
|
||||
|
||||
// Attach the tenant to the request.
|
||||
const tenant = await cache.retrieveByDomain(req.hostname);
|
||||
if (!tenant) {
|
||||
@@ -22,11 +20,15 @@ export default (options: MiddlewareOptions): RequestHandler => async (
|
||||
return next(new Error("tenant not found"));
|
||||
}
|
||||
|
||||
// Attach the tenant cache to the request.
|
||||
req.tenantCache = cache;
|
||||
|
||||
// Attach the tenant to the request.
|
||||
req.tenant = tenant;
|
||||
// Set Talk on the request.
|
||||
req.talk = {
|
||||
cache: {
|
||||
// Attach the tenant cache to the request.
|
||||
tenant: cache,
|
||||
},
|
||||
// Attach the tenant to the request.
|
||||
tenant,
|
||||
};
|
||||
|
||||
// Attach the tenant to the view locals.
|
||||
res.locals.tenant = tenant;
|
||||
|
||||
@@ -6,6 +6,7 @@ import tenantMiddleware from "talk-server/app/middleware/tenant";
|
||||
import { RouterOptions } from "talk-server/app/router/types";
|
||||
import tenantGraphMiddleware from "talk-server/graph/tenant/middleware";
|
||||
|
||||
import { tenantContext } from "talk-server/app/middleware/context/tenant";
|
||||
import { createNewAuthRouter } from "./auth";
|
||||
|
||||
export async function createTenantRouter(
|
||||
@@ -41,12 +42,14 @@ export async function createTenantRouter(
|
||||
// Any users may submit their GraphQL requests with authentication, this
|
||||
// middleware will unpack their user into the request.
|
||||
options.passport.authenticate("jwt", { session: false }),
|
||||
await tenantGraphMiddleware({
|
||||
schema: app.schemas.tenant,
|
||||
config: app.config,
|
||||
tenantContext({
|
||||
mongo: app.mongo,
|
||||
redis: app.redis,
|
||||
queue: app.queue,
|
||||
}),
|
||||
await tenantGraphMiddleware({
|
||||
schema: app.schemas.tenant,
|
||||
config: app.config,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,45 +1,37 @@
|
||||
import { GraphQLSchema } from "graphql";
|
||||
import { Redis } from "ioredis";
|
||||
import { Db } from "mongodb";
|
||||
// import { graphqlBatchHTTPWrapper } from "react-relay-network-layer";
|
||||
|
||||
import { Config } from "talk-common/config";
|
||||
import { graphqlBatchMiddleware } from "talk-server/app/middleware/graphqlBatch";
|
||||
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
|
||||
import { TaskQueue } from "talk-server/services/queue";
|
||||
import { Request } from "talk-server/types/express";
|
||||
|
||||
import TenantContext from "./context";
|
||||
|
||||
export interface TenantGraphQLMiddlewareOptions {
|
||||
schema: GraphQLSchema;
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
redis: Redis;
|
||||
queue: TaskQueue;
|
||||
}
|
||||
|
||||
export default async ({
|
||||
schema,
|
||||
config,
|
||||
mongo,
|
||||
redis,
|
||||
queue,
|
||||
}: TenantGraphQLMiddlewareOptions) => {
|
||||
return graphqlMiddleware(config, async (req: Request) => {
|
||||
// Load the tenant and user from the request.
|
||||
const { tenant, user, tenantCache } = req;
|
||||
export default async ({ schema, config }: TenantGraphQLMiddlewareOptions) =>
|
||||
graphqlBatchMiddleware(
|
||||
graphqlMiddleware(config, async (req: Request) => {
|
||||
if (!req.talk) {
|
||||
throw new Error("talk was not set");
|
||||
}
|
||||
|
||||
// Return the graph options.
|
||||
return {
|
||||
schema,
|
||||
context: new TenantContext({
|
||||
req,
|
||||
mongo,
|
||||
redis,
|
||||
tenant: tenant!,
|
||||
user,
|
||||
tenantCache,
|
||||
queue,
|
||||
}),
|
||||
};
|
||||
});
|
||||
};
|
||||
const { context } = req.talk;
|
||||
if (!context) {
|
||||
throw new Error("context was not set");
|
||||
}
|
||||
|
||||
const { tenant } = context;
|
||||
if (!tenant) {
|
||||
throw new Error("tenant was not set");
|
||||
}
|
||||
|
||||
// Return the graph options.
|
||||
return {
|
||||
schema,
|
||||
context: tenant,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { Request } from "express";
|
||||
|
||||
import TenantContext from "talk-server/graph/tenant/context";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
import TenantCache from "talk-server/services/tenant/cache";
|
||||
|
||||
export interface Request extends Request {
|
||||
user?: User;
|
||||
export interface TalkRequest {
|
||||
cache?: {
|
||||
tenant: TenantCache;
|
||||
};
|
||||
tenant?: Tenant;
|
||||
tenantCache: TenantCache;
|
||||
context?: {
|
||||
tenant?: TenantContext;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Request extends Request {
|
||||
talk?: TalkRequest;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user