Skip to content

Instantly share code, notes, and snippets.

@iDVB
Last active March 3, 2022 18:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iDVB/d9e330846d8fd3524bc9549a0a5b467c to your computer and use it in GitHub Desktop.
Save iDVB/d9e330846d8fd3524bc9549a0a5b467c to your computer and use it in GitHub Desktop.
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