Merge branch 'next' into permalink

This commit is contained in:
Chi Vinh Le
2018-08-03 16:05:43 +02:00
127 changed files with 3521 additions and 1363 deletions
+5 -1
View File
@@ -1,13 +1,17 @@
import { User } from "talk-server/models/user";
import { Request } from "talk-server/types/express";
export interface CommonContextOptions {
user?: User;
req?: Request;
}
export default class CommonContext {
public user?: User;
public req?: Request;
constructor({ user }: CommonContextOptions) {
constructor({ user, req }: CommonContextOptions) {
this.user = user;
this.req = req;
}
}
@@ -5,7 +5,7 @@ import {
GraphQLOptions,
} from "apollo-server-express";
import { FieldDefinitionNode, GraphQLError, ValidationContext } from "graphql";
import { Config } from "talk-server/config";
import { Config } from "talk-common/config";
// Sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L46-L57
const NoIntrospection = (context: ValidationContext) => ({
@@ -1,5 +1,5 @@
import { RedisPubSub } from "graphql-redis-subscriptions";
import { Config } from "talk-server/config";
import { Config } from "talk-common/config";
import { createRedisClient } from "talk-server/services/redis";
export async function createPubSub(config: Config): Promise<RedisPubSub> {
+8 -5
View File
@@ -1,16 +1,19 @@
import { Db } from "mongodb";
import CommonContext from "talk-server/graph/common/context";
import { Request } from "talk-server/types/express";
export interface ManagementContextOptions {
db: Db;
mongo: Db;
req?: Request;
}
export default class ManagementContext extends CommonContext {
public db: Db;
public mongo: Db;
constructor({ db }: ManagementContextOptions) {
super({});
constructor({ req, mongo }: ManagementContextOptions) {
super({ req });
this.db = db;
this.mongo = mongo;
}
}
@@ -1,13 +1,14 @@
import { GraphQLSchema } from "graphql";
import { Db } from "mongodb";
import { Config } from "talk-server/config";
import { Config } from "talk-common/config";
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
import { Request } from "talk-server/types/express";
import Context from "./context";
import ManagementContext from "./context";
export default (schema: GraphQLSchema, config: Config, db: Db) =>
graphqlMiddleware(config, async () => ({
export default (schema: GraphQLSchema, config: Config, mongo: Db) =>
graphqlMiddleware(config, async (req: Request) => ({
schema,
context: new Context({ db }),
context: new ManagementContext({ req, mongo }),
}));
+24 -5
View File
@@ -1,30 +1,49 @@
import { Redis } from "ioredis";
import { Db } from "mongodb";
import CommonContext from "talk-server/graph/common/context";
import { Tenant } from "talk-server/models/tenant";
import { User } from "talk-server/models/user";
import TenantCache from "talk-server/services/tenant/cache";
import { Request } from "talk-server/types/express";
import loaders from "./loaders";
import mutators from "./mutators";
export interface TenantContextOptions {
db: Db;
mongo: Db;
redis: Redis;
tenant: Tenant;
tenantCache: TenantCache;
req?: Request;
user?: User;
}
export default class TenantContext extends CommonContext {
public loaders: ReturnType<typeof loaders>;
public mutators: ReturnType<typeof mutators>;
public db: Db;
public mongo: Db;
public redis: Redis;
public user?: User;
public tenant: Tenant;
public tenantCache: TenantCache;
constructor({ user, tenant, db }: TenantContextOptions) {
super({ user });
constructor({
req,
user,
tenant,
mongo,
redis,
tenantCache,
}: TenantContextOptions) {
super({ user, req });
this.tenant = tenant;
this.tenantCache = tenantCache;
this.user = user;
this.mongo = mongo;
this.redis = redis;
this.loaders = loaders(this);
this.mutators = mutators(this);
this.db = db;
}
}
@@ -10,8 +10,8 @@ import { findOrCreate } from "talk-server/services/assets";
export default (ctx: TenantContext) => ({
findOrCreate: (input: FindOrCreateAssetInput) =>
findOrCreate(ctx.db, ctx.tenant, input),
findOrCreate(ctx.mongo, ctx.tenant, input),
asset: new DataLoader<string, Asset | null>(ids =>
retrieveManyAssets(ctx.db, ctx.tenant.id, ids)
retrieveManyAssets(ctx.mongo, ctx.tenant.id, ids)
),
});
@@ -14,7 +14,7 @@ import {
export default (ctx: Context) => ({
comment: new DataLoader((ids: string[]) =>
retrieveManyComments(ctx.db, ctx.tenant.id, ids)
retrieveManyComments(ctx.mongo, ctx.tenant.id, ids)
),
forAsset: (
assetID: string,
@@ -25,7 +25,7 @@ export default (ctx: Context) => ({
after,
}: AssetToCommentsArgs
) =>
retrieveCommentAssetConnection(ctx.db, ctx.tenant.id, assetID, {
retrieveCommentAssetConnection(ctx.mongo, ctx.tenant.id, assetID, {
first,
orderBy,
after,
@@ -40,9 +40,15 @@ export default (ctx: Context) => ({
after,
}: CommentToRepliesArgs
) =>
retrieveCommentRepliesConnection(ctx.db, ctx.tenant.id, assetID, parentID, {
first,
orderBy,
after,
}),
retrieveCommentRepliesConnection(
ctx.mongo,
ctx.tenant.id,
assetID,
parentID,
{
first,
orderBy,
after,
}
),
});
@@ -4,6 +4,6 @@ import { retrieveManyUsers, User } from "talk-server/models/user";
export default (ctx: Context) => ({
user: new DataLoader<string, User | null>(ids =>
retrieveManyUsers(ctx.db, ctx.tenant.id, ids)
retrieveManyUsers(ctx.mongo, ctx.tenant.id, ids)
),
});
+24 -4
View File
@@ -1,21 +1,41 @@
import { GraphQLSchema } from "graphql";
import { Redis } from "ioredis";
import { Db } from "mongodb";
import { Config } from "talk-server/config";
import { Config } from "talk-common/config";
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
import { Request } from "talk-server/types/express";
import TenantContext from "./context";
export default async (schema: GraphQLSchema, config: Config, db: Db) => {
export interface TenantGraphQLMiddlewareOptions {
schema: GraphQLSchema;
config: Config;
mongo: Db;
redis: Redis;
}
export default async ({
schema,
config,
mongo,
redis,
}: TenantGraphQLMiddlewareOptions) => {
return graphqlMiddleware(config, async (req: Request) => {
// Load the tenant and user from the request.
const { tenant, user } = req;
const { tenant, user, tenantCache } = req;
// Return the graph options.
return {
schema,
context: new TenantContext({ db, tenant: tenant!, user }),
context: new TenantContext({
req,
mongo,
redis,
tenant: tenant!,
user,
tenantCache,
}),
};
});
};
@@ -5,12 +5,17 @@ import { create } from "talk-server/services/comments";
export default (ctx: TenantContext) => ({
create: (input: GQLCreateCommentInput): Promise<Comment> => {
// FIXME: remove tenant + user !
return create(ctx.db, ctx.tenant, {
author_id: ctx.user!.id,
asset_id: input.assetID,
body: input.body,
parent_id: input.parentID,
});
return create(
ctx.mongo,
ctx.tenant,
ctx.user!,
{
author_id: ctx.user!.id,
asset_id: input.assetID,
body: input.body,
parent_id: input.parentID,
},
ctx.req
);
},
});
@@ -1,6 +1,9 @@
import TenantContext from "talk-server/graph/tenant/context";
import Comment from "./comment";
import Settings from "./settings";
export default (ctx: TenantContext) => ({
Comment: Comment(ctx),
Settings: Settings(ctx),
});
@@ -0,0 +1,11 @@
import { isNull, omitBy } from "lodash";
import TenantContext from "talk-server/graph/tenant/context";
import { GQLSettingsInput } from "talk-server/graph/tenant/schema/__generated__/types";
import { Tenant } from "talk-server/models/tenant";
import { update } from "talk-server/services/tenant";
export default ({ mongo, redis, tenantCache, tenant }: TenantContext) => ({
update: (input: GQLSettingsInput): Promise<Tenant | null> =>
update(mongo, redis, tenantCache, tenant, omitBy(input, isNull)),
});
@@ -1,5 +1,5 @@
import { GQLAuthIntegrationsTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { AuthIntegration, AuthIntegrations } from "talk-server/models/tenant";
import { AuthIntegration, AuthIntegrations } from "talk-server/models/settings";
const disabled: AuthIntegration = { enabled: false };
@@ -1,5 +1,5 @@
import { GQLAuthSettingsTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { Auth } from "talk-server/models/tenant";
import { Auth } from "talk-server/models/settings";
const AuthSettings: GQLAuthSettingsTypeResolver<Auth> = {
integrations: auth => auth.integrations,
@@ -1,5 +1,5 @@
import { GQLFacebookAuthIntegrationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { FacebookAuthIntegration } from "talk-server/models/tenant";
import { FacebookAuthIntegration } from "talk-server/models/settings";
const FacebookAuthIntegration: GQLFacebookAuthIntegrationTypeResolver<
FacebookAuthIntegration
@@ -1,5 +1,5 @@
import { GQLGoogleAuthIntegrationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { GoogleAuthIntegration } from "talk-server/models/tenant";
import { GoogleAuthIntegration } from "talk-server/models/settings";
const GoogleAuthIntegration: GQLGoogleAuthIntegrationTypeResolver<
GoogleAuthIntegration
@@ -1,5 +1,5 @@
import { GQLLocalAuthIntegrationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { LocalAuthIntegration } from "talk-server/models/tenant";
import { LocalAuthIntegration } from "talk-server/models/settings";
const LocalAuthIntegration: GQLLocalAuthIntegrationTypeResolver<
LocalAuthIntegration
@@ -5,6 +5,10 @@ const Mutation: GQLMutationTypeResolver<void> = {
comment: await ctx.mutators.Comment.create(input),
clientMutationId: input.clientMutationId,
}),
updateSettings: async (source, { input }, ctx) => ({
settings: await ctx.mutators.Settings.update(input.settings),
clientMutationId: input.clientMutationId,
}),
};
export default Mutation;
@@ -1,5 +1,5 @@
import { GQLOIDCAuthIntegrationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { OIDCAuthIntegration } from "talk-server/models/tenant";
import { OIDCAuthIntegration } from "talk-server/models/settings";
const OIDCAuthIntegration: GQLOIDCAuthIntegrationTypeResolver<
OIDCAuthIntegration
@@ -1,5 +1,5 @@
import { GQLSSOAuthIntegrationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { SSOAuthIntegration } from "talk-server/models/tenant";
import { SSOAuthIntegration } from "talk-server/models/settings";
const SSOAuthIntegration: GQLSSOAuthIntegrationTypeResolver<
SSOAuthIntegration
@@ -25,6 +25,19 @@ Cursor represents a paginating cursor.
"""
scalar Cursor
################################################################################
## Actions
################################################################################
enum ACTION_TYPE {
FLAG
DONTAGREE
}
enum ACTION_ITEM_TYPE {
COMMENTS
}
################################################################################
## Settings
################################################################################
@@ -287,7 +300,7 @@ type Settings {
"""
domains will return a given list of whitelisted domains.
"""
domains: [String!] @auth(roles: [ADMIN]) @auth(roles: [ADMIN])
domains: [String!] @auth(roles: [ADMIN])
"""
auth contains all the settings related to authentication and authorization.
@@ -301,6 +314,7 @@ type Settings {
enum USER_ROLE {
COMMENTER
STAFF
MODERATOR
ADMIN
}
@@ -390,8 +404,34 @@ type User {
################################################################################
enum COMMENT_STATUS {
"""
The comment is not PREMOD, but was not applied a moderation status by a
moderator.
"""
NONE
"""
The comment has been accepted by a moderator.
"""
ACCEPTED
"""
The comment has been rejected by a moderator.
"""
REJECTED
"""
The comment was created while the asset's premoderation option was on, and
new comments that haven't been moderated yet are referred to as
"premoderated" or "premod" comments.
"""
PREMOD
"""
SYSTEM_WITHHELD represents a comment that was withheld by the system because
it was flagged by an internal process for further review.
"""
SYSTEM_WITHHELD
}
"""
@@ -661,6 +701,66 @@ type CreateCommentPayload {
clientMutationId: String!
}
##################
## updateSettings
##################
"""
SettingsInput is the partial type of the Settings type for performing mutations.
"""
input SettingsInput {
moderation: MODERATION_MODE
requireEmailConfirmation: Boolean
infoBoxEnable: Boolean
infoBoxContent: String
questionBoxEnable: Boolean
questionBoxContent: String
questionBoxIcon: String
premodLinksEnable: Boolean
autoCloseStream: Boolean
customCssUrl: String
closedTimeout: Int
closedMessage: String
disableCommenting: Boolean
disableCommentingMessage: String
editCommentWindowLength: Int
charCountEnable: Boolean
charCount: Int
organizationName: String
organizationContactEmail: String
# wordlist: WordlistSettings @auth(roles: [ADMIN, MODERATOR])
domains: [String!]
# auth: AuthSettings!
}
"""
UpdateSettingsInput provides the input for the updateSettings Mutation.
"""
input UpdateSettingsInput {
settings: SettingsInput!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
"""
UpdateSettingsPayload contains the updated Settings after the updateSettings
mutation.
"""
type UpdateSettingsPayload {
"""
settings is the updated Settings.
"""
settings: Settings
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
##################
## Mutation
##################
@@ -670,6 +770,11 @@ type Mutation {
createComment will create a Comment as the current logged in User.
"""
createComment(input: CreateCommentInput!): CreateCommentPayload @auth
"""
updateSettings will update the Settings for the given Tenant.
"""
updateSettings(input: UpdateSettingsInput!): UpdateSettingsPayload @auth(roles: [ADMIN])
}
################################################################################