src/wordpress.js (125 lines of code) (raw):
// Based on fetching + caching in https://github.com/samikeijonen/11ty-wp ❤️
const path = require('path');
const fetch = require('node-fetch');
const flatcache = require('flat-cache');
const AMO_PROD_BASE_URL = 'https://addons.mozilla.org';
const AMO_BASE_URL = process.env.AMO_BASE_URL || AMO_PROD_BASE_URL;
const AMO_BLOG_BASE_URL = `${AMO_BASE_URL}/blog`;
const WORDPRESS_BASE_URL =
process.env.WORDPRESS_BASE_URL || 'https://mozamo.wpengine.com';
async function getNumPages(endPoint) {
const result = await fetch(endPoint, { method: 'HEAD' });
return result.headers.get('x-wp-totalpages') || 1;
}
const getBaseApiURL = (amoBaseURL = AMO_BASE_URL) => {
// We only want to override the baseApiURL for the dev environment.
return amoBaseURL === 'https://addons-dev.allizom.org'
? amoBaseURL
: AMO_PROD_BASE_URL;
};
const fixInternalURLs = (content, { baseURL = WORDPRESS_BASE_URL } = {}) => {
if (process.env.DONT_FIX_INTERNAL_URLS === '1') {
return content;
}
const urlPattern = [
// Make sure the URL pattern accepts both http:// and https://.
baseURL.replace(/^https?:/, 'https?:'),
// Do not replace URLs containing `/wp-content/`.
'(?!/wp-content/)',
].join('');
return content.replace(new RegExp(urlPattern, 'g'), AMO_BLOG_BASE_URL);
};
const createPost = ({
author,
id,
slug,
title,
excerpt,
date,
modified,
content,
yoast_head,
featured_media,
}) => {
const permalink = `/blog/${slug}/`;
return {
author,
id,
slug,
title: title.rendered,
excerpt: fixInternalURLs(excerpt.rendered),
date,
modified,
content: fixInternalURLs(content.rendered),
permalink,
absolutePermalink: `${AMO_BASE_URL}${permalink}`,
seoHead: fixInternalURLs(yoast_head || ''),
featuredImage: featured_media || null,
};
};
async function fetchAll({ numPages, endPoint, type }) {
const allPages = [];
let allData = [];
// eslint-disable-next-line no-console
console.debug(`Fetching ${type} content from wordpress via REST API`);
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
// console.log(`${endPoint}&page=${pageNum}`);
const page = fetch(`${endPoint}&page=${pageNum}`);
allPages.push(page);
}
const results = await Promise.all(allPages);
for (const result of results) {
let json = await result.json();
// Filter post data and only keep/cache what we care about.
if (type === 'posts') {
json = json
.filter((item) => {
return item.status === 'publish' && item.slug;
})
.map(createPost);
}
allData.push(json);
allData = allData.flat();
}
return allData;
}
async function fetchData(type, endPoint) {
if (process.env.BUILD_WORDPRESS_THEME === '1') {
// eslint-disable-next-line no-console
console.debug(
"Not fetching any data because BUILD_WORDPRESS_THEME is set to '1'"
);
return {};
}
if (!endPoint.startsWith('/')) {
throw new Error(`endPoint="${endPoint}" must start with a slash`);
}
const url = `${WORDPRESS_BASE_URL}${endPoint}`;
// eslint-disable-next-line no-console
console.debug(`URL for ${type}: ${url}`);
const noCache = process.env.NO_CACHE === '1';
if (noCache) {
// eslint-disable-next-line no-console
console.debug('Cache is disabled');
}
const cache = flatcache.load(type, path.resolve(__dirname, '../cache'));
const date = new Date();
// Key set to today's date so at most we should only be fetching everything
// once per day.
const key = `${date.getUTCFullYear()}-${
date.getUTCMonth() + 1
}-${date.getUTCDate()}`;
const cachedData = cache.getKey(key);
if (noCache || !cachedData) {
const numPages = await getNumPages(url);
const allData = await fetchAll({ numPages, endPoint: url, type });
cache.setKey(key, allData);
cache.save();
return allData;
}
return cachedData;
}
const getMediaSize = ({ media, size }) => {
if (!media || !media.media_details || !media.media_details.sizes) {
return null;
}
return media.media_details.sizes[size] || null;
};
module.exports = {
getBaseApiURL,
AMO_BASE_URL,
AMO_BLOG_BASE_URL,
WORDPRESS_BASE_URL,
createPost,
fetchData,
fixInternalURLs,
getMediaSize,
};