gatsby-source-strapi
Version:
Gatsby source plugin for building websites using Strapi as a data source
217 lines (180 loc) • 5.9 kB
JavaScript
import { Parser } from "commonmark";
import qs from "qs";
import { createRemoteFileNode } from "gatsby-source-filesystem";
import { getContentTypeSchema } from "./helpers";
const reader = new Parser();
/**
* Retrieves all medias from the markdown
* @param {String} text
* @param {String} apiURL
* @returns {Object[]}
*/
const extractFiles = (text, apiURL) => {
const files = [];
// parse the markdown content
const parsed = reader.parse(text);
const walker = parsed.walker();
let event, node;
while ((event = walker.next())) {
node = event.node;
// process image nodes
if (event.entering && node.type === "image") {
let destination;
const alternativeText = node.firstChild?.literal || "";
if (/^\//.test(node.destination)) {
destination = `${apiURL}${node.destination}`;
} else if (/^http/i.test(node.destination)) {
destination = node.destination;
}
if (destination) {
files.push({ url: destination, src: node.destination, alternativeText });
}
}
}
return files.filter(Boolean);
};
/**
* Download file and create node
* @param {Object} file
* @param {Object} ctx
* @returns {String} node Id
*/
export const downloadFile = async (file, context) => {
const {
actions: { createNode, touchNode },
cache,
createNodeId,
getNode,
store,
strapiConfig,
} = context;
const { apiURL, remoteFileHeaders } = strapiConfig;
let fileNodeID;
const mediaDataCacheKey = `strapi-media-${file.id}`;
const cacheMediaData = await cache.get(mediaDataCacheKey);
// If we have cached media data and it wasn't modified, reuse
// previously created file node to not try to redownload
if (cacheMediaData && cacheMediaData.updatedAt === file.updatedAt) {
fileNodeID = cacheMediaData.fileNodeID;
touchNode(getNode(fileNodeID));
}
if (!fileNodeID) {
try {
// full media url
const source_url = `${file.url.startsWith("http") ? "" : apiURL}${file.url}`;
const fileNode = await createRemoteFileNode({
url: source_url,
store,
cache,
createNode,
createNodeId,
httpHeaders: remoteFileHeaders || {},
});
if (fileNode) {
fileNodeID = fileNode.id;
await cache.set(mediaDataCacheKey, {
fileNodeID,
updatedAt: file.updatedAt,
});
}
} catch (error) {
// Ignore
console.log("err", error);
}
}
return fileNodeID;
};
/**
* Extract images and create remote nodes for images in all fields.
* @param {Object} item the entity
* @param {Object} context gatsby function
* @param {String} uid the main schema uid
*/
const extractImages = async (item, context, uid) => {
const { schemas, strapiConfig, axiosInstance } = context;
const schema = getContentTypeSchema(schemas, uid);
const { apiURL } = strapiConfig;
for (const attributeName of Object.keys(item)) {
const value = item[attributeName];
const attribute = schema.schema.attributes[attributeName];
const type = attribute?.type || undefined;
if (value && type) {
if (type === "richtext") {
const extractedFiles = extractFiles(value.data, apiURL);
const files = await Promise.all(
extractedFiles.map(async ({ url }) => {
const filters = qs.stringify(
{
filters: { url: url.replace(`${apiURL}`, "") },
},
{ encode: false },
);
const { data } = await axiosInstance.get(`/api/upload/files?${filters}`);
const file = data[0];
if (!file) {
return;
}
const fileNodeID = await downloadFile(file, context);
return { fileNodeID, file };
}),
);
const fileNodes = files.filter(Boolean);
for (const [index, fileNode] of fileNodes.entries()) {
item[attributeName].medias.push({
alternativeText: extractedFiles[index].alternativeText,
url: extractedFiles[index].url,
src: extractedFiles[index].src,
localFile___NODE: fileNode.fileNodeID,
file: fileNode.file,
});
}
}
if (type === "dynamiczone") {
for (const element of value) {
await extractImages(element, context, element.strapi_component);
}
}
if (type === "component") {
if (attribute.repeatable) {
for (const element of value) {
await extractImages(element, context, attribute.component);
}
} else {
await extractImages(value, context, attribute.component);
}
}
if (type === "relation") {
await extractImages(value, context, attribute.target);
}
if (type === "media") {
const isMultiple = attribute.multiple;
const imagesField = isMultiple ? value : [value];
// Dowload all files
const files = await Promise.all(
imagesField.map(async (file) => {
const fileNodeID = await downloadFile(file, context);
return fileNodeID;
}),
);
const images = files.filter(Boolean);
if (images && images.length > 0) {
if (isMultiple) {
for (let index = 0; index < value.length; index++) {
item[attributeName][index][`localFile___NODE`] = images[index];
}
} else {
item[attributeName][`localFile___NODE`] = isMultiple ? images : images[0];
}
}
}
}
}
};
// Downloads media from image type fields
export const downloadMediaFiles = async (entities, context, contentTypeUid) =>
Promise.all(
entities.map(async (entity) => {
await extractImages(entity, context, contentTypeUid);
return entity;
}),
);