Last active
March 3, 2022 18:42
-
-
Save iDVB/d9e330846d8fd3524bc9549a0a5b467c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import middy from '@middy/core' | |
import ssm from '@middy/ssm' | |
import { ApolloServer } from 'apollo-server-lambda' | |
import { makeExecutableSchema, mergeSchemas } from '@graphql-tools/schema' | |
import { | |
introspectSchema, | |
wrapSchema, | |
TransformObjectFields, | |
FieldNodeTransformer, | |
} from '@graphql-tools/wrap' | |
import { AsyncExecutor } from '@graphql-tools/utils' | |
import { fetch } from 'cross-fetch' | |
import { printSchema, print, SelectionSetNode, Kind, GraphQLError } from 'graphql' | |
import { CustomHandler } from '@klickmarketing/core' | |
const { CONTENTFUL_SPACE, CONTENTFUL_ENVIRONMENT } = process.env | |
const { CONTENTFUL_GCA_ENDPOINT, CDA_KEY_SSM, MEDIA_ASSET_BUCKET, CPA_KEY_SSM } = process.env | |
if (!CDA_KEY_SSM || !CPA_KEY_SSM || !MEDIA_ASSET_BUCKET) throw new Error('Env vars required!!') | |
const baseHandler: CustomHandler = async (event, context, callback) => { | |
const { queryStringParameters } = event | |
const previewValue = queryStringParameters?.preview | |
const isPreview = !!(previewValue === '2ipmzlBohKkwvGQLme6TE') | |
// console.log({ queryStringParameters, previewValue, isPreview }) | |
const contentfulKey = isPreview ? context.cpaKey : context.cdaKey | |
const executor: AsyncExecutor = async ({ document, variables: rawVariables }) => { | |
const query = print(document) | |
const variables = { ...rawVariables, preview: isPreview } | |
// console.log(rawVariables, variables) | |
const fetchResult = await fetch( | |
`${CONTENTFUL_GCA_ENDPOINT}/spaces/${CONTENTFUL_SPACE}/environments/${CONTENTFUL_ENVIRONMENT}`, | |
{ | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
Authorization: `Bearer ${contentfulKey}`, | |
}, | |
body: JSON.stringify({ query, variables }), | |
}, | |
) | |
return fetchResult.json() | |
} | |
const role = event.requestContext?.authorizer?.role | |
// console.log({ requestContext: event.requestContext, role }) | |
const remoteSchema = wrapSchema({ | |
schema: await introspectSchema(executor), | |
executor, | |
transforms: [createMyTransform()], | |
}) | |
const localSchema = makeExecutableSchema({ | |
typeDefs: printSchema(remoteSchema), | |
resolvers: getLocalResolvers(remoteSchema), | |
}) | |
const mergedSchema = mergeSchemas({ | |
schemas: [remoteSchema, localSchema], | |
}) | |
const server = new ApolloServer({ | |
schema: mergedSchema, | |
context: { | |
role, | |
}, | |
// introspection: true, | |
// plugins: [ApolloServerPluginLandingPageGraphQLPlayground()], | |
}) | |
const createServer = server.createHandler() | |
return createServer(event, context, callback) | |
} | |
const s3WebPrefix = 'https://s3.amazonaws.com/' | |
function getLocalResolvers(schema: any) { | |
const localResolvers = { | |
...filterMissingLinksFromContentfulCollections(schema), | |
ArticlePost: { | |
contentModulesCollection: (parent: any, args: any, context: any) => { | |
const isAllowed = getIsAllowed({ parent, context }) | |
if (!isAllowed) return null | |
return parent.contentModulesCollection | |
}, | |
documentsCollection: (parent: any, args: any, context: any) => { | |
const isAllowed = getIsAllowed({ parent, context }) | |
if (!isAllowed) return null | |
return parent.documentsCollection | |
}, | |
}, | |
VideoAsset: { | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
videoUri: (parent: any, args: any, context: any) => { | |
const isAllowed = getIsAllowed({ parent, context }) | |
if (!isAllowed) return null | |
const videoUri = `${s3WebPrefix}${MEDIA_ASSET_BUCKET}${parent.videoUri}` | |
return videoUri | |
}, | |
}, | |
} | |
return localResolvers | |
} | |
function getIsAllowed({ parent, context }: { parent: any; context: any }) { | |
const isAllowed = | |
!parent.restrictToRoles?.length || parent.restrictToRoles?.includes(context.role) | |
return isAllowed | |
} | |
function filterMissingLinksFromContentfulCollections(schema: any) { | |
const collectionResolvers = Object.keys(schema._typeMap) | |
.filter((key) => key.endsWith('Collection')) | |
.filter((key) => key !== 'Collection') // Note: This is because we have a content-type that is actually named "Collection" | |
const collectionResolversFilterNullItems = collectionResolvers.reduce((prev, current) => { | |
return { | |
...prev, | |
[current]: { | |
items: (parent: any, args: any, context: any) => { | |
return parent.items.filter((item: any) => { | |
if ( | |
item instanceof GraphQLError && | |
item.extensions?.contentful?.code === 'UNRESOLVABLE_LINK' | |
) { | |
// TODO: add some kind of logging or post to slack so we don't just leave these missing link errors. | |
console.log(item?.message) | |
return false | |
} | |
return true | |
}) | |
}, | |
}, | |
} | |
}, {}) | |
return collectionResolversFilterNullItems | |
} | |
const createMyTransform = () => { | |
const typeName = () => { | |
return undefined | |
} | |
const fieldName: FieldNodeTransformer = (typeName, fieldName, fieldNode) => { | |
const protectedFields = [ | |
typeName === 'VideoAssetCollection' && fieldName === 'items', | |
typeName === 'Query' && fieldName === 'videoAsset', | |
typeName === 'ArticlePostCollection' && fieldName === 'items', | |
typeName === 'Query' && fieldName === 'articlePost', | |
] | |
if (protectedFields.some(Boolean)) { | |
const newSelection = { | |
kind: Kind.FIELD, | |
name: { | |
kind: Kind.NAME, | |
value: 'restrictToRoles', | |
}, | |
} | |
const oldSelections = fieldNode.selectionSet?.selections || [] | |
const selectionSet: SelectionSetNode = { | |
...fieldNode.selectionSet, | |
kind: Kind.SELECTION_SET, | |
selections: [...oldSelections, newSelection], | |
} | |
const newFieldNode = { | |
...fieldNode, | |
selectionSet, | |
} | |
return newFieldNode | |
} | |
return fieldNode | |
} | |
return new TransformObjectFields(typeName, fieldName) | |
} | |
const middyHandler = middy(baseHandler).use( | |
ssm({ | |
awsClientOptions: { | |
region: 'us-east-1', | |
}, | |
fetchData: { | |
cdaKey: CDA_KEY_SSM, | |
cpaKey: CPA_KEY_SSM, | |
}, | |
setToContext: true, | |
}), | |
) | |
export const handler: CustomHandler = middyHandler |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment