Last active
March 20, 2024 14:53
-
-
Save DanRibbens/573cea51c9357a3a125d947c087ba3ca to your computer and use it in GitHub Desktop.
contentful example functions
This file contains hidden or 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 * as Migration from "@payloadcms/db-mongodb"; | |
import { Asset, ChainModifiers, createClient } from 'contentful'; | |
import * as ContentfulTypes from '../interfaces/contentful-case-studies'; | |
import { toSlatejsDocument } from '@contentful/contentful-slatejs-adapter'; | |
import * as PayloadTypes from "../interfaces/payload"; | |
import axios from "axios"; | |
import { Payload } from "payload"; | |
import https from "https"; | |
import { CollectionSlugs } from "../constants/slugs"; | |
export async function up({ payload }: Migration.MigrateUpArgs): Promise<void> { | |
const client = createClient({ | |
space: process.env.CONTENTFUL_SPACE_ID, | |
accessToken: process.env.CONTENTFUL_TOKEN, | |
}) | |
const response = await client.getEntries({ | |
content_type: 'caseStudyContent', | |
include: 10, | |
}); | |
const items = response.items as ContentfulTypes.ICaseStudyContent[]; | |
await Promise.all(items.map(async (item) => { | |
const fields = item.fields as ContentfulTypes.ICaseStudyContentFields; | |
const seoFields = fields.seo?.fields as ContentfulTypes.ISeoFields | undefined; | |
const contentEntries = (fields.content as any)?.content; | |
const parsedContent = await Promise.all(contentEntries.map(async (item: any) => { | |
switch (item?.nodeType) { | |
case 'embedded-entry-block': | |
return generateBlock(payload, item?.data?.target); | |
case 'hr': | |
return generateBlockSeparator(); | |
default: | |
return null; | |
} | |
})); | |
const content = parsedContent.filter((block) => block !== null) as PayloadTypes.CollectionCaseStudies['content']; | |
const seoImage = await createDocumentMedia(payload, seoFields?.pageThumbnail, true); | |
const headerMedia = await createDocumentMedia(payload, fields.headerImage); | |
const headerIntroInsertedBlock = await generateBlock(payload, fields.headerInsertedElement); | |
const resultsInsertedBlock = await generateBlock(payload, fields.resultsInsertedElement); | |
await payload.create({ | |
collection: 'case-studies', | |
data: { | |
isFromContentful: true, | |
seo: { | |
title: seoFields?.pageTitle, | |
description: seoFields?.pageDescription, | |
image: seoImage?.id | |
}, | |
slug: fields.slug, | |
title: fields.title, | |
categories: fields.categories ?? [], | |
header: { | |
meta: { | |
description: fields.headerDescription, | |
leftText: fields.headerLeftText, | |
rightText: fields.headerRightText, | |
}, | |
media: { | |
asset: headerMedia?.id, | |
showControls: !!fields.showHeaderMediaControls | |
}, | |
intro: { | |
leftText: convertToRichText(fields.introLeftText), | |
rightText: convertToRichText(fields.introRightText), | |
insertedBlock: [headerIntroInsertedBlock] | |
} | |
}, | |
content, | |
results: { | |
insertedBlock: [resultsInsertedBlock], | |
title: fields.resultsTitle, | |
content: convertToRichText(fields.resultsContent), | |
websiteUrl: fields.websiteUrl, | |
quote: { | |
content: fields.clientsQuote, | |
author: fields.quoteAuthor, | |
}, | |
credits: { | |
title: fields.creditsTitle, | |
content: convertToRichText(fields.creditsContent) | |
} | |
}, | |
} | |
}); | |
})); | |
}; | |
export async function down({ payload }: Migration.MigrateDownArgs): Promise<void> { | |
await payload.delete({ collection: 'case-studies', where: { isFromContentful: { equals: true } } }); | |
await payload.delete({ collection: 'medias', where: { isFromContentful: { equals: true } } }); | |
await payload.delete({ collection: 'images', where: { isFromContentful: { equals: true } } }); | |
}; | |
async function createDocumentMedia(payload: Payload, asset?: Asset<ChainModifiers, string>, isForImageCollection = false) { | |
if (!asset) { | |
return undefined; | |
}; | |
const collection = isForImageCollection ? CollectionSlugs.IMAGES : CollectionSlugs.MEDIAS | |
const fileName = typeof asset?.fields?.file?.fileName === 'string' ? asset?.fields?.file?.fileName : asset?.fields?.file?.fileName.fileName; | |
const name = `${fileName} - ${asset.sys.id}`; | |
const fieldFileUrl = asset?.fields?.file?.url | |
const fileUrl = typeof fieldFileUrl === 'string' ? fieldFileUrl : fieldFileUrl.url; | |
const fullFileUrl = `https:${fileUrl}`; | |
const fileContentType = asset?.fields?.file?.contentType; | |
const fileMimeType = typeof fileContentType === 'string' ? fileContentType : fileContentType.contentType; | |
const maxRetries = 3; // Maximum number of retries | |
let attempt = 0; // Current attempt | |
let success = false; // Flag to track success | |
let buffer; // To store the response data | |
while (attempt < maxRetries && !success) { | |
try { | |
const fileResponse = await axios.get(fullFileUrl, { responseType: 'arraybuffer', httpsAgent: new https.Agent({ keepAlive: true }) }); | |
buffer = Buffer.from(fileResponse.data, 'binary'); | |
success = true; // If the request succeeds, set success to true | |
} catch (error) { | |
attempt++; | |
console.error(`Attempt ${attempt} failed: ${error.message}`); | |
if (attempt >= maxRetries) { | |
throw new Error(`Failed to fetch document media after ${maxRetries} attempts`); | |
} | |
} | |
} | |
const label = typeof asset?.fields?.title === 'string' ? asset?.fields?.title : fileName; | |
const altText = typeof asset?.fields?.description === 'string' ? asset?.fields?.description : undefined; | |
return await payload.create({ | |
collection, | |
file: { | |
data: buffer, | |
mimetype: fileMimeType, | |
name, | |
size: buffer.byteLength, | |
}, | |
data: { | |
label, | |
altText, | |
isFromContentful: true, | |
}, | |
}) | |
} | |
function convertToRichText(field: any | undefined) { | |
switch (typeof field) { | |
case 'string': | |
return [{ type: "paragraph", children: [{ text: field }] }]; | |
case 'undefined': | |
return undefined; | |
default: | |
return toSlatejsDocument({ document: field }); | |
} | |
} | |
function generateBlock(payload: Payload, component?: ContentfulTypes.Components) { | |
if (!component) { | |
return null; | |
} | |
switch (component?.sys?.contentType?.sys?.id) { | |
case 'componentEmbed': | |
return generateBlockEmbedded(component as ContentfulTypes.IComponentEmbed); | |
case 'componentFlexibleTextBlock': | |
return generateBlockFlexibleText(component as ContentfulTypes.IComponentFlexibleTextBlock); | |
case 'componentFlexibleMediaBlock': | |
return generateBlockFlexibleMedia(payload, component as ContentfulTypes.IComponentFlexibleMediaBlock); | |
default: | |
return null; | |
} | |
} | |
function generateBlockFlexibleText(component?: ContentfulTypes.IComponentFlexibleTextBlock) { | |
const fields = component.fields as ContentfulTypes.IComponentFlexibleTextBlockFields; | |
const block: PayloadTypes.BlockCaseStudyFlexibleText = { | |
id: null, | |
blockName: null, | |
blockType: 'case-study-flexible-text', | |
layout: { | |
title: fields.title, | |
headline: fields.headline, | |
titleSpacing: parseFlexibleTextBlockTitle(fields.titleBottomSpacing), | |
content: convertToRichText(fields.content), | |
backgroundColor: parseBackgroundColor(fields.backgroundColor) | |
}, | |
titleLayout: { | |
offsets: { | |
desktop: fields.titleOffset || 0, | |
mobile: fields.mobileTitleOffset || 0 | |
}, | |
widths: { | |
desktop: fields.titleWidth || 12, | |
mobile: fields.mobileTitleWidth || 12 | |
} | |
}, | |
contentLayout: { | |
offsets: { | |
desktop: fields.contentOffset || 0, | |
mobile: fields.mobileContentOffset || 0 | |
}, | |
widths: { | |
desktop: fields.contentWidth || 12, | |
mobile: fields.mobileContentWidth || 12 | |
} | |
}, | |
}; | |
return block; | |
} | |
async function generateBlockFlexibleMedia(payload: Payload, component?: ContentfulTypes.IComponentFlexibleMediaBlock) { | |
const fields = component.fields as ContentfulTypes.IComponentFlexibleMediaBlockFields; | |
const mediaFiles = (fields?.mediaFiles ?? []) as Asset<ChainModifiers, string>[]; | |
const media = await Promise.all(mediaFiles.map(async (asset) => { | |
const document = await createDocumentMedia(payload, asset); | |
const relation: PayloadTypes.BlockCaseStudyFlexibleMedia['layout']['media'][0] = { | |
relationTo: CollectionSlugs.MEDIAS, | |
value: document?.id | |
}; | |
return relation; | |
})) | |
const block: PayloadTypes.BlockCaseStudyFlexibleMedia = { | |
id: null, | |
blockName: null, | |
blockType: 'case-study-flexible-media', | |
layout: { | |
mediaLayout: parseMediaLayout(fields.mediaLayout), | |
media, | |
showMediaControlsForVideos: fields.showMediaControlsForVideos, | |
backgroundColor: parseBackgroundColor(fields.backgroundColor), | |
}, | |
offsets: { | |
desktop: fields.desktopOffset || 0, | |
mobile: fields.mobileOffset || 0 | |
}, | |
widths: { | |
desktop: fields.desktopWidth || 6, | |
mobile: fields.mobileWidth || 6 | |
}, | |
footer: { | |
notes: convertToRichText(fields.footerNote) | |
} | |
}; | |
return block; | |
} | |
function generateBlockEmbedded(component?: ContentfulTypes.IComponentEmbed) { | |
const fields = component.fields as ContentfulTypes.IComponentEmbedFields; | |
const block: PayloadTypes.BlockCaseStudyEmbedded = { | |
id: null, | |
blockName: null, | |
blockType: 'case-study-embedded', | |
code: { | |
description: fields.description, | |
snippet: fields.snippet, | |
} | |
}; | |
return block; | |
} | |
function generateBlockSeparator() { | |
const block: PayloadTypes.BlockCaseStudySeparator = { | |
id: null, | |
blockName: null, | |
blockType: 'case-study-separator' | |
}; | |
return block; | |
}; | |
function parseFlexibleTextBlockTitle(spacing: ContentfulTypes.IComponentFlexibleTextBlockFields['titleBottomSpacing']): PayloadTypes.BlockCaseStudyFlexibleText['layout']['titleSpacing'] { | |
switch (spacing) { | |
case 'Small': | |
return 'small'; | |
case 'Medium': | |
return 'medium'; | |
case 'large': | |
return 'large'; | |
default: | |
return 'small'; | |
} | |
} | |
function parseMediaLayout(mediaLayout: ContentfulTypes.IComponentFlexibleMediaBlockFields['mediaLayout']): PayloadTypes.BlockCaseStudyFlexibleMedia['layout']['mediaLayout'] { | |
switch (mediaLayout) { | |
case 'Horizontal': | |
return 'horizontal'; | |
case 'Single': | |
return 'single'; | |
case 'Gallery': | |
return 'gallery'; | |
case 'Screen Width (Ignore Custom Offsets/Widths)': | |
return 'screen-width'; | |
default: | |
return 'single' | |
} | |
} | |
function parseBackgroundColor(backgroundColor: (ContentfulTypes.IComponentFlexibleTextBlockFields | ContentfulTypes.IComponentFlexibleMediaBlockFields)['backgroundColor']) { | |
if (!backgroundColor) { | |
return ''; | |
} | |
switch (backgroundColor) { | |
case 'Black': | |
return '#000000'; | |
case 'Magnolia': | |
return '#E9DEFF'; | |
case 'SugarCane': | |
return '#EAEAE5'; | |
case 'Saltpan': | |
return '#EAEAE5'; | |
case 'DeepSea': | |
return '#12846B'; | |
case 'Blue': | |
return '#07518E'; | |
case 'Eggshell': | |
return '#EFEADD'; | |
case 'Purple': | |
return '#7F39B5'; | |
case 'LightGray': | |
return '#EAEAEA'; | |
case 'Orange': | |
return '#FF6B4E'; | |
case 'DarkPurple': | |
return '#2F1730'; | |
case 'DarkVanilla': | |
return '#D4BFAB'; | |
case 'DarkSeaGreen': | |
return '#87B888'; | |
case 'Cornsilk': | |
return '#F3EDCC'; | |
case 'ClayTerrace': | |
return '#D57D3B'; | |
case 'OrangeRed': | |
return '#FF4501'; | |
default: | |
return ''; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment