diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..42fefdd --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + extends: 'airbnb-base', + parserOptions: { + ecmaVersion: 6, + }, + plugins: ['import'], + + rules: { + 'comma-dangle': 1, + 'no-param-reassign': [1, { props: false }], + 'arrow-parens': 0, + 'import/extensions': 'off', + 'import/no-unresolved': 'off', + 'no-plusplus': 0, + 'no-console': 0, + 'no-confusing-arrow': 0, + 'import/no-extraneous-dependencies': 0, + 'react/require-extension': 'off', + 'react/prefer-stateless-function': 'off', + 'no-trailing-spaces': [2, { skipBlankLines: true }], + 'react/forbid-prop-types': 0, + 'no-unused-expressions': [1, { allowTernary: true }], + 'max-len': [ + 2, + { + code: 120, + ignoreStrings: true, + ignoreTemplateLiterals: true, + }, + ], + }, +}; diff --git a/package.json b/package.json index 54dde6d..2652f35 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,13 @@ "graphql-tools": "^2.24.0", "graphql-type-json": "^0.2.0", "graphql-yoga": "^1.8.0", - "hubspot-api": "^1.0.1", + "hubspot-api": "^1.3.0", "moment": "^2.22.0", "nodemon": "^1.17.3" + }, + "devDependencies": { + "eslint": "^5.5.0", + "eslint-config-airbnb-base": "^13.1.0", + "eslint-plugin-import": "^2.14.0" } } diff --git a/query-resolvers.js b/query-resolvers.js index 5ddf1dc..ab7ce7e 100644 --- a/query-resolvers.js +++ b/query-resolvers.js @@ -7,44 +7,44 @@ const assertHasCredentials = ctx => { } }; -const flattenProps = properties => Object.keys(properties).reduce((acc, curr) => { - acc[curr] = properties[curr].value; - return acc; -}, {}); +const flattenProps = properties => + Object.keys(properties).reduce((acc, curr) => { + acc[curr] = properties[curr].value; + return acc; + }, {}); const contactsResponse = contact => { - const {vid, properties} = contact; - return Object.assign({ - vid - }, flattenProps(properties)); + const { vid, properties } = contact; + return Object.assign({ vid }, flattenProps(properties)); }; const companiesResponse = company => { - const {portalId, properties, additionalDomains} = company; - return Object.assign({ - portalId, - properties - }, flattenProps(properties)); + const { portalId, properties, additionalDomains } = company; + return Object.assign( + { + portalId, + properties, + }, + flattenProps(properties), + ); }; module.exports = { - version: (_, opts, context) => { - return pjs.version; - }, + version: (_, opts, context) => pjs.version, contacts: async (_, opts, context) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; // Define extra properties as required by the schema const property = ['email', 'firstname', 'lastname', 'company']; - Object.assign(opts, {property}); + Object.assign(opts, { property }); const response = await hs.contacts.getContacts(opts); - const {contacts} = response; + const { contacts } = response; return contacts.map(contactsResponse); }, contact: async (_, opts, context) => { assertHasCredentials(context); - const {hs} = context; - const {id, email, utk} = opts; + const { hs } = context; + const { id, email, utk } = opts; let response; if (id) { response = await hs.contacts.getById(id); @@ -55,55 +55,73 @@ module.exports = { } else { throw new Error('You must specify one of `id`, `email`, `utk` in your query'); } - const {vid, properties} = response; + const { vid, properties } = response; return contactsResponse(response); }, blogAuthor: async (_, opts, context) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; const response = await hs.blog.getAuthor(opts.id); return response; }, blogAuthors: async (_, opts, context) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; const response = await hs.blog.getAuthors(opts); - const {objects} = response; + const { objects } = response; return objects; }, - page: async (_, opts, context, {cacheControl}) => { + page: async (_, opts, context, { cacheControl }) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; const response = await hs.pages.getPageById(opts.id); return response; }, - pages: async (_, opts, context, {cacheControl}) => { + pages: async (_, opts, context, { cacheControl }) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; const response = await hs.pages.getPages(opts); - const {objects} = response; + const { objects } = response; + return objects; + }, + tables: async (_, opts, context, { cacheControl }) => { + assertHasCredentials(context); + const { hs } = context; + const response = await hs.hubdb.getTables(opts); + const { objects } = response; + return objects; + }, + rows: async (_, opts, context, { cacheControl }) => { + assertHasCredentials(context); + const { hs } = context; + const response = await hs.hubdb.getTableRows(opts.tableId, opts.portalId); + const { objects } = response; return objects; }, blogPost: async (_, opts, context) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; const response = await hs.blog.getPostById(opts); return response; }, blogPosts: async (_, opts, context) => { - const {contentGroupId: content_group_id, blogAuthorId: blog_author_id, limit} = opts; + const { contentGroupId: content_group_id, blogAuthorId: blog_author_id, limit } = opts; assertHasCredentials(context); - const {hs} = context; - const response = await hs.blog.getPosts({content_group_id, blog_author_id, limit}); - const {objects} = response; + const { hs } = context; + const response = await hs.blog.getPosts({ + content_group_id, + blog_author_id, + limit, + }); + const { objects } = response; return objects; }, workflows: async (_, opts, context) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; const response = await hs.workflows.getAll(); - const {workflows} = response; + const { workflows } = response; // Filtering (as this is not provided by the API) @@ -111,9 +129,9 @@ module.exports = { }, workflow: async (_, opts, context) => { assertHasCredentials(context); - const {hs} = context; + const { hs } = context; const response = await hs.workflows.getWorkflow(opts.id); debug(response); return response; - } + }, }; diff --git a/schemas/hubspot.graphql.js b/schemas/hubspot.graphql.js index 8284775..b41ff73 100644 --- a/schemas/hubspot.graphql.js +++ b/schemas/hubspot.graphql.js @@ -4,25 +4,53 @@ an exhaustive list of attributes that are available in the API. You should extend the schemas to include *only* the fields you need in your applications. - For a full list of available fields for each schema, please check out it's + For a full list of available fields for each schema, please check out its corresponding REST API endpoint at https://developers.hubspot.com/docs/overview - + */ -let typeDefProps = { +const typeDefProps = { contact: { vid: 'ID!', firstname: 'String', lastname: 'String', email: 'String', - company: 'String' + company: 'String', }, page: { id: 'ID!', name: 'String', css_text: 'String', widget_containers: 'UnparsedObject', - widgets: 'UnparsedObject' + widgets: 'UnparsedObject', + }, + row: { + id: 'ID!', + createdAt: 'Float', + name: 'String', + path: 'String', + hs_name: 'String', + hs_path: 'String', + values: '[UnparsedObject]', + isSoftEditable: 'Boolean', + }, + table: { + id: 'ID!', + name: 'String', + portalId: 'Int', + createdAt: 'Int', + createdBy: 'UnparsedObject', + updatedBy: 'UnparsedObject', + updated: 'Int', + publishedAt: 'Float', + updatedAt: 'Float', + columns: '[UnparsedObject]', + label: 'String', + cosObjectType: 'String', + deleted: 'Boolean', + rowCount: 'Int', + useForPages: 'Boolean', + columnCount: 'Int', }, blog: { id: 'ID!', @@ -33,7 +61,7 @@ let typeDefProps = { archived: 'Boolean!', campaign: 'String', campaign_name: 'String', - content_group_id: 'Int' + content_group_id: 'Int', }, blogAuthor: { avatar: 'String', @@ -55,15 +83,13 @@ let typeDefProps = { twitterUsername: 'String', updated: 'Int', username: 'String', - website: 'String' + website: 'String', }, workflow: { id: 'ID', name: 'String', type: 'String', - name: 'String', actions: 'UnparsedObject', - id: 'Int', description: 'String', enabled: 'Boolean', portalId: 'Int', @@ -86,83 +112,107 @@ let typeDefProps = { triggerSets: 'UnparsedObject', suppressionListIds: 'UnparsedObject', lastUpdatedBy: 'String', - metaData: 'UnparsedObject' - } + metaData: 'UnparsedObject', + }, }; const queryFields = [ { method: 'page', arguments: { - id: 'ID!' + id: 'ID!', }, - returns: 'Page!' - }, { + returns: 'Page!', + }, + { method: 'pages', arguments: { offset: 'Int', - limit: 'Int!' + limit: 'Int!', }, - returns: '[Page!]!' - }, { + returns: '[Page!]!', + }, + { + method: 'tables', + returns: '[Table!]!', + }, + { + method: 'rows', + arguments: { + portalId: 'Int!', + tableId: 'Int!', + }, + returns: '[Row!]!', + }, + { method: 'blogPosts', arguments: { contentGroupId: 'ID!', blogAuthorId: 'Int', - limit: 'Int!' + limit: 'Int!', }, - returns: '[BlogPost!]!' - }, { + returns: '[BlogPost!]!', + }, + { method: 'blogPost', arguments: { - id: 'ID!' + id: 'ID!', }, - returns: 'BlogPost' - }, { + returns: 'BlogPost', + }, + { method: 'blogAuthor', arguments: { - id: 'ID!' + id: 'ID!', }, - returns: 'BlogAuthor' - }, { + returns: 'BlogAuthor', + }, + { method: 'blogAuthors', arguments: { - limit: 'Int!' + limit: 'Int!', }, - returns: '[BlogAuthor!]!' - }, { + returns: '[BlogAuthor!]!', + }, + { method: 'version', - returns: 'String!' - }, { + returns: 'String!', + }, + { method: 'contact', arguments: { id: 'ID', email: 'String', - utk: 'String' + utk: 'String', }, - returns: 'Contact' - }, { + returns: 'Contact', + }, + { method: 'contacts', arguments: { - count: 'Int!' + count: 'Int!', }, - returns: '[Contact!]!' - }, { + returns: '[Contact!]!', + }, + { method: 'workflow', arguments: { - id: 'ID!' + id: 'ID!', }, - returns: 'Workflow' - }, { + returns: 'Workflow', + }, + { method: 'workflows', - returns: '[Workflow]' - } + returns: '[Workflow]', + }, ]; const extractQueryMethod = qf => { // blogPosts(contentGroupId: ID!, blogAuthorId: Int, limit: Int!): [BlogPost!]! if (qf.arguments) { - const argumentMap = Object.keys(qf.arguments).map(arg => `${arg}: ${qf.arguments[arg]}`).join(', '); + const argumentMap = Object.keys(qf.arguments) + .map(arg => `${arg}: ${qf.arguments[arg]}`) + .join(', '); return `${qf.method}(${argumentMap}): ${qf.returns}`; } return `${qf.method}: ${qf.returns}`; @@ -174,7 +224,9 @@ const blogAuthorFields = Object.keys(typeDefProps.blogAuthor); Object.keys(typeDefProps).forEach(typeDef => { Object.assign(typeDefProps, { - [typeDef]: Object.keys(typeDefProps[typeDef]).map(prop => `\t${prop}: ${typeDefProps[typeDef][prop]}`).join('\r\n') + [typeDef]: Object.keys(typeDefProps[typeDef]) + .map(prop => `\t${prop}: ${typeDefProps[typeDef][prop]}`) + .join('\r\n'), }); }); @@ -193,6 +245,14 @@ module.exports = { ${typeDefProps.page} } + type Table { + ${typeDefProps.table} + } + + type Row { + ${typeDefProps.row} + } + type BlogPost { ${typeDefProps.blog} } @@ -208,5 +268,5 @@ module.exports = { type Workflow { ${typeDefProps.workflow} } - ` + `, };