Skip to content

Instantly share code, notes, and snippets.

@DanRibbens
Last active March 20, 2024 14:53
Show Gist options
  • Save DanRibbens/573cea51c9357a3a125d947c087ba3ca to your computer and use it in GitHub Desktop.
Save DanRibbens/573cea51c9357a3a125d947c087ba3ca to your computer and use it in GitHub Desktop.
contentful example functions
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