Skip to content

Commit

Permalink
add HN2, which is relay-compatible
Browse files Browse the repository at this point in the history
  • Loading branch information
clayallsopp committed Apr 28, 2016
1 parent 19c7e27 commit 07fec4c
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 0 deletions.
2 changes: 2 additions & 0 deletions graphqlhub-schemas/src/graphqlhub.js
Expand Up @@ -6,6 +6,7 @@ import {
} from 'graphql';

import * as HN from './hn';
import * as HN2 from './hn2';
import * as REDDIT from './reddit';
import * as KEYVALUE from './keyvalue';
import * as GITHUB from './github';
Expand All @@ -14,6 +15,7 @@ import * as GIPHY from './giphy';

let schemas = {
hn : HN,
hn2 : HN2,
reddit : REDDIT,
keyValue : KEYVALUE,
github : GITHUB,
Expand Down
320 changes: 320 additions & 0 deletions graphqlhub-schemas/src/hn2.js
@@ -0,0 +1,320 @@
import {
getItem,
getUser,
getTopStoryIds,
getNewStoryIds,
getAskStoryIds,
getShowStoryIds,
getJobStoryIds
} from './apis/hn';

import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLInt,
GraphQLEnumType,
GraphQLBoolean,
GraphQLList,
GraphQLID
} from 'graphql';

import {
nodeDefinitions,
fromGlobalId,
toGlobalId,
globalIdField,
connectionDefinitions,
connectionArgs,
connectionFromPromisedArray
} from 'graphql-relay';

const itemTypeName = 'item';

const ConnectionTypes = {};
const getConnectionType = (nodeType) => {
if (!ConnectionTypes[nodeType]) {
ConnectionTypes[nodeType] = connectionDefinitions({ nodeType }).connectionType;
}
return ConnectionTypes[nodeType];
}

const kidsField = () => {
return {
type : getConnectionType(CommentType),
description : 'The item\'s comments, in ranked display order.',
args : connectionArgs,
resolve : (item, args) => {
// todo - trim initial kids array so we don't over-fetch
let kidsPromise = Promise.all(item.kids.map(getItem))
return connectionFromPromisedArray(kidsPromise, args)
}
};
};

const itemFields = () => {
return {
hnId : {
type : new GraphQLNonNull(GraphQLString),
description : 'The item\'s unique id.',
resolve : (item) => item.id.toString()
},
id: globalIdField(itemTypeName),
deleted : {
type : GraphQLBoolean,
description : 'if the item is deleted'
},
by : {
type : new GraphQLNonNull(UserType),
description : 'The item\'s author.',
resolve : (item) => {
return getUser(item.by);
}
},
time : {
type : new GraphQLNonNull(GraphQLInt),
description : 'Creation date of the item, in Unix Time.'
},
timeISO : {
type : new GraphQLNonNull(GraphQLString),
description : 'Creation date of the item, in ISO8601',
resolve : (item) => {
let date = new Date(item.time * 1000);
return date.toISOString();
}
},
text : {
type : GraphQLString,
description : 'The comment, story or poll text. HTML.'
},
dead : {
type : GraphQLBoolean,
description : 'if the item is dead'
},
url : {
type : GraphQLString,
description : 'The URL of the story.'
},
score : {
type : GraphQLInt,
description : 'The story\'s score, or the votes for a pollopt.'
},
title : {
type : GraphQLString,
description : 'The title of the story, poll or job.'
},
parent : {
type : nodeInterface,
description : 'The item\'s parent. For comments, either another comment or the relevant story. For pollopts, the relevant poll.',
resolve : (item) => {
if (!item.parent) {
return null;
}
return getItem(item.parent);
}
},

descendants : {
type : GraphQLInt,
description : 'In the case of stories or polls, the total comment count.'
}
};
}

const { nodeInterface, nodeField } = nodeDefinitions(
(globalId) => {
const { type, id } = fromGlobalId(globalId);
if (type === 'HackerNewsV2User') {
return getUser(id);
}
return getItem(id);
},
(obj) => {
if (typeof obj.karma !== 'undefined') {
return UserType;
}
return {
job: JobType,
story: StoryType,
comment: CommentType,
poll: PollType,
pollopt: PollPartType,
}[obj.type] || StoryType;
}
);

const StoryType = new GraphQLObjectType({
name: 'HackerNewsV2Story',
fields: () => ({
id: itemFields().id,
hnId: itemFields().hnId,
by: itemFields().by,
descendants: itemFields().descendants,
score: itemFields().score,
time: itemFields().time,
timeISO: itemFields().timeISO,
title: itemFields().title,
url: itemFields().url,
text: itemFields().text,
kids: kidsField(CommentType),
deleted: itemFields().deleted,
dead: itemFields().dead,
}),
interfaces: [nodeInterface]
});

const JobType = new GraphQLObjectType({
name: 'HackerNewsV2Job',
fields: () => ({
id: itemFields().id,
hnId: itemFields().hnId,
by: itemFields().by,
score: itemFields().score,
text: itemFields().text,
time: itemFields().time,
timeISO: itemFields().timeISO,
title: itemFields().title,
url: itemFields().url,
deleted: itemFields().deleted,
dead: itemFields().dead,
}),
interfaces: [nodeInterface]
});

const PollType = new GraphQLObjectType({
name: 'HackerNewsV2Poll',
fields: () => ({
id: itemFields().id,
hnId: itemFields().hnId,
by: itemFields().by,
descendants: itemFields().descendants,
score: itemFields().score,
time: itemFields().time,
timeISO: itemFields().timeISO,
title: itemFields().title,
text: itemFields().text,
kids: kidsField(CommentType),
deleted: itemFields().deleted,
dead: itemFields().dead,
parts : {
type : new GraphQLList(PollPartType),
description : 'A list of related pollopts, in display order.',
resolve : (item) => {
if (!item.parts) {
return null;
}
let promises = item.parts.map((partId) => {
return getItem(partId)
});
return Promise.all(promises);
}
},
}),
interfaces: [nodeInterface]
});

const CommentType = new GraphQLObjectType({
name: 'HackerNewsV2Comment',
fields: () => ({
id: itemFields().id,
hnId: itemFields().hnId,
by: itemFields().by,
parent: itemFields().parent,
text: itemFields().text,
time: itemFields().time,
timeISO: itemFields().timeISO,
kids: kidsField(CommentType),
deleted: itemFields().deleted,
dead: itemFields().dead,
}),
interfaces: [nodeInterface]
});

const PollPartType = new GraphQLObjectType({
name: 'HackerNewsV2PollPart',
fields: () => ({
id: itemFields().id,
hnId: itemFields().hnId,
by: itemFields().by,
score: itemFields().score,
time: itemFields().time,
timeISO: itemFields().timeISO,
text: itemFields().text,
parent: itemFields().parent,
deleted: itemFields().deleted,
}),
interfaces: [nodeInterface]
});

const UserType = new GraphQLObjectType({
name: 'HackerNewsV2User',
fields: () => ({
id: globalIdField(),
hnId: itemFields().hnId,
delay : {
type : GraphQLInt,
description : 'Delay in minutes between a comment\'s creation and its visibility to other users.'
},
created : {
type : GraphQLInt,
description : 'Creation date of the user, in Unix Time.'
},
createdISO : {
type : GraphQLString,
description : 'Creation date of the user, in ISO8601',
resolve : (user) => {
let date = new Date(user.created * 1000);
return date.toISOString();
}
},
about : {
type : GraphQLString,
description : 'The user\'s optional self-description. HTML.'
},
submitted : {
type : getConnectionType(nodeInterface),
description: 'List of the user\'s stories, polls and comments.',
args : connectionArgs,
resolve : (user, args) => {
// todo - trim initial kids array so we don't over-fetch
let promise = Promise.all(user.submitted.map(getItem))
return connectionFromPromisedArray(promise, args)
}
}
}),
interfaces: [nodeInterface]
});


let hnType = new GraphQLObjectType({
name : 'HackerNewsAPIV2',
description : 'The Hacker News V2 API; this is Relay-compatible (uses Nodes and Connections)',
fields : {
node: nodeField,

nodeFromHnId: {
type: new GraphQLNonNull(nodeInterface),
description: 'To ensure Node IDs are globally unique, GraphQLHub coerces ' +
'IDs returned by the HN API. Use this field to get nodes via normal HN IDs',
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
},
isUserId: {
type: new GraphQLNonNull(GraphQLBoolean)
}
},
resolve(source, args, context, info) {
const typeName = args.isUserId ? UserType.name : itemTypeName;
const id = toGlobalId(typeName, args.id);
return nodeField.resolve(source, { id }, context, info);
}
}
}
});

console.log(UserType);

export const QueryObjectType = hnType;
2 changes: 2 additions & 0 deletions src/server.js
Expand Up @@ -104,6 +104,8 @@ let SHORTCUTS = {
,
giphy : '/playground?query=%23%20Hit%20the%20Play%20button%20above!%0A%23%20Hit%20"Docs"%20on%20the%20right%20to%20explore%20the%20API%0A%0A%7B%0A%20%20graphQLHub%0A%20%20giphy%20%7B%0A%09%09random(tag%3A"javascript")%20%7B%0A%20%20%20%20%09id%0A%20%20%20%20%20%20url%0A%20%20%20%20%20%20images%20%7B%0A%20%20%20%20%20%20%20%20original%20%7B%0A%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%09%7D%0A%20%20%7D%0A%7D'
,
hn2 : '/playground?query=%23%20Welcome%20to%20GraphQLHub!%20Type%20your%20GraphQL%20query%20here%2C%20or%0A%23%20explore%20the%20"Docs"%20to%20the%20right%0A%0A%7B%0A%20%20hn2%20%7B%0A%20%20%20%20nodeFromHnId(id%3A"clayallsopp"%2C%20isUserId%3Atrue)%20%7B%0A%20%20%20%20%20%20id%0A%20%20%20%20%20%20...%20on%20HackerNewsV2User%20%7B%0A%20%20%20%20%20%20%20%20submitted(first%3A%205)%20%7B%0A%20%20%20%20%20%20%20%20%20%20pageInfo%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20hasNextPage%0A%20%20%20%20%20%20%20%20%20%20%20%20endCursor%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20edges%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20cursor%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20...%20on%20HackerNewsV2Story%20%7B%0A%09%09%09%09%09%09%09%09score%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20...%20on%20HackerNewsV2Comment%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20text%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D'
,
};
Object.keys(SHORTCUTS).forEach((shortcut) => {
app.get(`/playground/${shortcut}`, (req, res) => {
Expand Down

0 comments on commit 07fec4c

Please sign in to comment.