feature: added generated graph types

This commit is contained in:
Wyatt Johnson
2018-06-28 17:06:45 -06:00
parent 65c8da0f34
commit 986c92598d
17 changed files with 201 additions and 91 deletions
+9
View File
@@ -9125,6 +9125,15 @@
"cross-fetch": "2.0.0"
}
},
"graphql-schema-typescript": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/graphql-schema-typescript/-/graphql-schema-typescript-1.2.1.tgz",
"integrity": "sha512-ipZh3Epm/Kqcy6MF5FM6uxwCMFok07q+6qyxFOa7ViRufcjzH9Y3nECmECH5WgqRGl2wR6TmskbZd5qJJrGpoA==",
"dev": true,
"requires": {
"yargs": "^11.0.0"
}
},
"graphql-subscriptions": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-0.5.8.tgz",
+3 -1
View File
@@ -20,7 +20,8 @@
"lint": "npm-run-all --parallel lint:*",
"lint:server": "tslint --project ./tsconfig.json",
"lint:client": "tslint --project ./src/core/client/tsconfig.json",
"docz:watch": "docz dev"
"docz:watch": "docz dev",
"postinstall": "node ./scripts/types.js"
},
"author": "",
"license": "Apache-2.0",
@@ -95,6 +96,7 @@
"fluent-langneg": "^0.1.0",
"fluent-react": "^0.7.0",
"graphql-playground-middleware-express": "^1.7.0",
"graphql-schema-typescript": "^1.2.1",
"html-webpack-plugin": "^3.2.0",
"jest": "^23.2.0",
"loader-utils": "^1.1.0",
+96
View File
@@ -0,0 +1,96 @@
const { Linter, Configuration } = require("tslint");
const { generateTSTypesAsString } = require("graphql-schema-typescript");
const { getGraphQLConfig } = require("graphql-config");
const path = require("path");
const fs = require("fs");
function lint(files) {
const linter = new Linter({ fix: true });
for (const { fileName, types } of files) {
const configuration = Configuration.findConfiguration(null, fileName)
.results;
linter.lint(fileName, types, configuration);
}
}
function getFileName(name) {
return path.join(
__dirname,
"..",
"src",
"core",
"server",
"graph",
name,
"schema",
"__generated__",
"types.ts"
);
}
async function main() {
const config = getGraphQLConfig(__dirname);
const projects = config.getProjects();
const files = [
{
name: "tenant",
fileName: getFileName("tenant"),
config: {
contextType: "TenantContext",
importStatements: [
'import { Cursor } from "talk-server/models/connection";',
'import TenantContext from "talk-server/graph/tenant/context";',
],
customScalarType: { Cursor: "Cursor", Time: "string" },
},
},
{
name: "management",
fileName: getFileName("management"),
config: {
contextType: "ManagementContext",
importStatements: [
'import ManagementContext from "talk-server/graph/management/context";',
],
},
},
];
for (const file of files) {
// Load the graph schema.
const schema = projects[file.name].getSchema();
// Create the generated directory.
const dir = path.dirname(file.fileName);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
// Create the types for this file.
file.types = await generateTSTypesAsString(schema, {
tabSpaces: 2,
typePrefix: "GQL",
contextType: "any",
strictNulls: false,
minimizeInterfaceImplementation: true,
...file.config,
});
}
// Send the files off to the linter to be linted and written.
lint(files);
return files;
}
main()
.then(files => {
for (const { fileName } of files) {
console.log(`Generated ${fileName}`);
}
})
.catch(err => {
console.error(err);
});
@@ -1,5 +1,5 @@
import Cursor from "../../common/scalars/cursor";
import { GQLResolver } from "talk-server/graph/management/schema/__generated__/types";
export default {
Cursor,
};
const Resolvers: GQLResolver = {};
export default Resolvers;
@@ -1,6 +1,8 @@
import { IResolvers } from "graphql-tools";
import loadSchema from "talk-server/graph/common/schema";
import resolvers from "talk-server/graph/management/resolvers";
export default function getManagementSchema() {
return loadSchema("management", resolvers);
return loadSchema("management", resolvers as IResolvers);
}
@@ -7,27 +7,22 @@ Time represented as an ISO8601 string.
"""
scalar Time
"""
Cursor represents a paginating cursor.
"""
scalar Cursor
################################################################################
## Tenant
################################################################################
type Tenant {
id: ID!
id: ID!
"""
organizationName is the name of the organization.
"""
organizationName: String
"""
organizationName is the name of the organization.
"""
organizationName: String
"""
organizationContactEmail is the email of the organization.
"""
organizationContactEmail: String
"""
organizationContactEmail is the email of the organization.
"""
organizationContactEmail: String
}
################################################################################
@@ -35,5 +30,5 @@ type Tenant {
################################################################################
type Query {
tenant(id: ID!): Tenant
tenant(id: ID!): Tenant
}
@@ -1,7 +1,11 @@
import DataLoader from "dataloader";
import Context from "talk-server/graph/tenant/context";
import {
ConnectionInput,
AssetToCommentsArgs,
CommentToRepliesArgs,
} from "talk-server/graph/tenant/schema/__generated__/types";
import {
retrieveAssetConnection,
retrieveMany,
retrieveRepliesConnection,
@@ -11,8 +15,8 @@ export default (ctx: Context) => ({
comment: new DataLoader((ids: string[]) =>
retrieveMany(ctx.db, ctx.tenant.id, ids)
),
forAsset: (assetID: string, input: ConnectionInput) =>
forAsset: (assetID: string, input: AssetToCommentsArgs) =>
retrieveAssetConnection(ctx.db, ctx.tenant.id, assetID, input),
forParent: (assetID: string, parentID: string, input: ConnectionInput) =>
forParent: (assetID: string, parentID: string, input: CommentToRepliesArgs) =>
retrieveRepliesConnection(ctx.db, ctx.tenant.id, assetID, parentID, input),
});
@@ -1,12 +1,12 @@
import TenantContext from "talk-server/graph/tenant/context";
import { CreateCommentInput } from "talk-server/graph/tenant/resolvers/mutation";
import { GQLCreateCommentInput } from "talk-server/graph/tenant/schema/__generated__/types";
import { Comment } from "talk-server/models/comment";
import { create } from "talk-server/services/comments";
export default (ctx: TenantContext) => ({
create: (input: CreateCommentInput): Promise<Comment> => {
create: (input: GQLCreateCommentInput): Promise<Comment> => {
// FIXME: remove tenant + user !
return create(ctx.db, ctx.tenant!.id, {
return create(ctx.db, ctx.tenant.id, {
author_id: ctx.user!.id,
asset_id: input.assetID,
body: input.body,
@@ -1,10 +1,11 @@
import Context from "talk-server/graph/tenant/context";
import { GQLAssetTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { Asset } from "talk-server/models/asset";
import { ConnectionInput } from "talk-server/models/comment";
export default {
comments: async (asset: Asset, input: ConnectionInput, ctx: Context) =>
const Asset: GQLAssetTypeResolver<Asset> = {
comments: (asset, input, ctx) =>
ctx.loaders.Comments.forAsset(asset.id, input),
// TODO: implement this.
isClosed: () => false,
};
export default Asset;
@@ -1,9 +1,11 @@
import Context from "talk-server/graph/tenant/context";
import { Comment, ConnectionInput } from "talk-server/models/comment";
import { GQLCommentTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { Comment } from "talk-server/models/comment";
export default {
author: async (comment: Comment, _: any, ctx: Context) =>
const Comment: GQLCommentTypeResolver<Comment> = {
author: async (comment, args, ctx) =>
ctx.loaders.Users.user.load(comment.author_id),
replies: async (comment: Comment, input: ConnectionInput, ctx: Context) =>
replies: async (comment, input, ctx) =>
ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input),
};
export default Comment;
@@ -1,13 +1,17 @@
import Cursor from "../../common/scalars/cursor";
import Cursor from "talk-server/graph/common/scalars/cursor";
import { GQLResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import Asset from "./asset";
import Comment from "./comment";
import Mutation from "./mutation";
import Query from "./query";
export default {
const Resolvers: GQLResolver = {
Asset,
Comment,
Cursor,
Query,
Mutation,
};
export default Resolvers;
@@ -1,23 +1,7 @@
import { ClientMutationProps } from "talk-server/graph/common/resolvers/mutation";
import TenantContext from "talk-server/graph/tenant/context";
import { Comment } from "talk-server/models/comment";
import { GQLMutationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
export interface CreateCommentInput extends ClientMutationProps {
assetID: string;
parentID?: string;
body: string;
}
export interface CreateCommentPayload extends ClientMutationProps {
comment: Comment;
}
const Mutation = {
createComment: async (
source: void,
input: CreateCommentInput,
ctx: TenantContext
): Promise<CreateCommentPayload> => ({
const Mutation: GQLMutationTypeResolver<void> = {
createComment: async (source, { input }, ctx) => ({
comment: await ctx.mutators.Comment.create(input),
clientMutationId: input.clientMutationId,
}),
@@ -1,10 +1,8 @@
import TenantContext from "talk-server/graph/tenant/context";
import { GQLQueryTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
export default {
asset: async (
source: void,
{ id }: { id: string; url: string },
ctx: TenantContext
) => ctx.loaders.Assets.asset.load(id),
settings: async (parent: any, args: any, ctx: TenantContext) => ctx.tenant,
const Query: GQLQueryTypeResolver<void> = {
asset: (source, args, ctx) => ctx.loaders.Assets.asset.load(args.id),
settings: (parent, args, ctx) => ctx.tenant,
};
export default Query;
+3 -1
View File
@@ -1,6 +1,8 @@
import { IResolvers } from "graphql-tools";
import loadSchema from "talk-server/graph/common/schema";
import resolvers from "talk-server/graph/tenant/resolvers";
export default function getTenantSchema() {
return loadSchema("tenant", resolvers);
return loadSchema("tenant", resolvers as IResolvers);
}
@@ -31,9 +31,9 @@ enum MODERATION_MODE {
}
"""
Wordlist describes all the available wordlists.
WordlistSettings describes all the available wordlists.
"""
type Wordlist {
type WordlistSettings {
"""
banned words will by default reject the comment if it is found.
"""
@@ -46,7 +46,20 @@ type Wordlist {
}
# Settings stores the global settings for a given installation.
################################################################################
## Settings
################################################################################
"""
Settings stores the global settings for a given Tenant.
"""
type Settings {
"""
domain is the domain that is associated with this Tenant.
"""
domain: String!
"""
moderation is the moderation mode for all Asset's on the site.
"""
@@ -152,7 +165,7 @@ type Settings {
"""
wordlist will return a given list of words.
"""
wordlist: Wordlist!
wordlist: WordlistSettings!
"""
domains will return a given list of whitelisted domains.
@@ -222,8 +235,8 @@ type Comment {
replies will return the replies to this comment.
"""
replies(
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
first: Int! = 10
orderBy: COMMENT_SORT! = CREATED_AT_DESC
after: Cursor
): CommentsConnection
}
@@ -299,8 +312,8 @@ type Asset {
comments are the comments on the Asset.
"""
comments(
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
first: Int! = 10
orderBy: COMMENT_SORT! = CREATED_AT_DESC
after: Cursor
): CommentsConnection
+4 -1
View File
@@ -1,5 +1,8 @@
import bunyan from "bunyan";
const logger = bunyan.createLogger({ name: "talk" });
const logger = bunyan.createLogger({
name: "talk",
serializers: bunyan.stdSerializers,
});
export default logger;
+12 -17
View File
@@ -1,11 +1,13 @@
import { merge } from "lodash";
import { Db } from "mongodb";
import uuid from "uuid";
import { Omit, Sub } from "talk-common/types";
import { GQLCOMMENT_SORT } from "talk-server/graph/tenant/schema/__generated__/types";
import { ActionCounts } from "talk-server/models/actions";
import { Connection, Cursor } from "talk-server/models/connection";
import Query from "talk-server/models/query";
import { TenantResource } from "talk-server/models/tenant";
import uuid from "uuid";
function collection(db: Db) {
return db.collection<Readonly<Comment>>("comments");
@@ -121,16 +123,9 @@ export async function retrieveMany(db: Db, tenantID: string, ids: string[]) {
return ids.map(id => comments.find(comment => comment.id === id) || null);
}
export enum CommentSort {
CREATED_AT_DESC = "CREATED_AT_DESC",
CREATED_AT_ASC = "CREATED_AT_ASC",
REPLIES_DESC = "REPLIES_DESC",
RESPECT_DESC = "RESPECT_DESC",
}
export interface ConnectionInput {
first: number;
orderBy: CommentSort;
orderBy: GQLCOMMENT_SORT;
after?: Cursor;
}
@@ -144,12 +139,12 @@ export interface ConnectionInput {
function nodesToEdge(input: ConnectionInput, nodes: Comment[]) {
let getCursor: (comment: Comment, index: number) => Cursor;
switch (input.orderBy) {
case CommentSort.CREATED_AT_DESC:
case CommentSort.CREATED_AT_ASC:
case GQLCOMMENT_SORT.CREATED_AT_DESC:
case GQLCOMMENT_SORT.CREATED_AT_ASC:
getCursor = comment => comment.created_at;
break;
case CommentSort.REPLIES_DESC:
case CommentSort.RESPECT_DESC:
case GQLCOMMENT_SORT.REPLIES_DESC:
case GQLCOMMENT_SORT.RESPECT_DESC:
getCursor = (_, index) =>
(input.after ? (input.after as number) : 0) + index + 1;
break;
@@ -226,25 +221,25 @@ async function retrieveConnection(
) {
// Apply some sorting options.
switch (input.orderBy) {
case CommentSort.CREATED_AT_DESC:
case GQLCOMMENT_SORT.CREATED_AT_DESC:
query.orderBy({ created_at: -1 });
if (input.after) {
query.where({ created_at: { $lt: input.after as Date } });
}
break;
case CommentSort.CREATED_AT_ASC:
case GQLCOMMENT_SORT.CREATED_AT_ASC:
query.orderBy({ created_at: 1 });
if (input.after) {
query.where({ created_at: { $gt: input.after as Date } });
}
break;
case CommentSort.REPLIES_DESC:
case GQLCOMMENT_SORT.REPLIES_DESC:
query.orderBy({ reply_count: -1, created_at: -1 });
if (input.after) {
query.after(input.after as number);
}
break;
case CommentSort.RESPECT_DESC:
case GQLCOMMENT_SORT.RESPECT_DESC:
query.orderBy({ "action_counts.respect": -1, created_at: -1 });
if (input.after) {
query.after(input.after as number);