Еще в октябре 2020 года Brightcove запустила наш новый бренд - это сопровождалось развертыванием новой сборки www.brightcove.com в Gatsby с использованием Contentful и Netlify. В январе мы переместили наш англоязычный блог, и поэтому нам пришлось найти замену представлению каналов (изначально обслуживаемое через Node / SailsJS / Pug). К счастью, это было несложно благодаря функциям Netlify и Contentful's JavaScript SDK.
RSS-канал - это, по сути, просто длинный XML-файл с элементами, которые извлекают читатели. Таким образом, функция должна делать три вещи:
- Получить контент
- Соберите и отформатируйте ленту
- Вернуть отформатированный фид
Во-первых, внутри нашего проекта Gatsby, развернутого в Netlify, создайте папку / functions / (если вы еще этого не сделали). В папке создайте файл с именем rss-feed.js.
В rss-feed.js начните с импорта API доставки контента Contentful javascript и настройте клиент с вашими кредитами:
const contentful = require(“contentful”); const client = contentful.createClient({ space: "SPACE_ID", accessToken: process.env.GATSBY_CONTENTFUL_ACCESS_TOKEN });
Вы можете использовать ту же переменную env, которая используется для доступа к Contentful, что и остальная часть вашего приложения - rad.
Затем мы можем настроить обработчик, который действительно будет отвечать на запрос.
exports.handler = async (event, context) => { // get posts from contentful and do stuff return { statusCode: 200, body: "MY FEED WILL GO HERE" }; };
Функции Netlify должны возвращать статус и тело. В нашем случае этим телом будет форматированный XML с набором данных. Но сначала давайте добавим вызов Contentful.
const posts = await client.getEntries({ content_type: "post", locale: "en-US", order: "-fields.publishedAt" });
Это асинхронный вызов настроенного клиента Contentful выше. Наш тип контента был определен как «публикация», и мы ищем публикации на английском языке, поэтому мы передадим языковой стандарт «en-US». Чтобы убедиться, что они возвращаются первыми с самыми последними сообщениями, мы добавляем запрос порядка «-fields.publishedAt» (вы можете использовать любой параметр, который хотите здесь, на основе вашей собственной модели контента).
Теперь, когда мы получаем сообщения, пора создать ленту. Два метода ниже делают именно это: настраивают открывающий и закрывающий теги для всего фида, а также специфичное для элемента форматирование для каждой записи. Спасибо шаблонным литералам за то, что сделали это супер простым.
const sanitize = (string) => { const newString = string.replace(/\&/g, "&"); return newString; }; const dateString = (dstring) => { const target = new Date(dstring); return target.toUTCString(); }; const feedWrapper = (contents) => { return `<?xml version="1.0" encoding="utf-8" ?> <rss version="2.0" xml:base="https://site.com/en/blog/feed" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>My Blog - The Leading Blog</title> <link>https://site.com/en/blog</link> <description>Superior Posts</description> <language>en</language> ${contents} </channel> </rss> `; }; const stringify = (obj) => { const template = (entry, id) => ` <item> <title>${sanitize(entry.title) || ""}</title> <link>https://site.com/en/blog/${entry.slug || ""}</link> <category>https://site.com/en/blog/${ entry.category.fields.slug }</category> <pubDate>${dateString(entry.publishedAt)}</pubDate> <description>${sanitize(entry.body)}</description> <dc:creator>${entry.author.fields.name}</dc:creator> <guid>https://site.com/en/blog/${entry.slug}</guid> </item> `; if (obj && obj.fields && Object.keys(obj.fields).length > 0) { return template(obj.fields, obj.sys.id); } else { return ""; } };
Хорошо, давайте поговорим о некоторых из них. Методы sanitize и dateString просто очищают контент, возвращаемый Contentful; плохие символы HTML (в частности, «&») рассердят читателей ленты, поэтому santize заменяет их. Чтобы преобразовать метку времени в миллисекундах в удобочитаемую дату / время, метод dateString возвращает строку в формате UTC - опять же, чтобы сделать значение ‹pubDate› действительным.
Метод feedWrapper содержит открывающие и закрывающие теги каналов и принимает параметр contents - этот параметр будет списком записей, созданных методом stringify. Давайте составим этот список:
const buildFeed = (posts) => { let entries = ""; posts.items.forEach((item, index) => { if (item.fields && Object.keys(item.fields).length > 0) { entries += stringify(item); } }); return feedWrapper(entries); };
Мы создали пустую строку из записей, которая добавляется циклом forEach элементов сообщения, которые мы передаем методу из содержательного ответа. Мы проверяем, есть ли у объекта сообщения поля с нужным нам содержимым, и генерируем строку шаблона для каждого из них. Затем метод передает построенный список строк ‹item›… .. ‹/item› методу feedWrapper и возвращает скомпилированный канал XML.
Собираем все вместе, это выглядит так:
const contentful = require("contentful"); const client = contentful.createClient({ space: "SPACE_ID", accessToken: process.env.GATSBY_CONTENTFUL_ACCESS_TOKEN }); const feedWrapper = (contents) => { return `<?xml version="1.0" encoding="utf-8" ?> <rss version="2.0" xml:base="https://site.com/en/blog/feed" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>My Blog - The Leading Blog</title> <link>https://site.com/en/blog</link> <description>Superior Posts</description> <language>en</language> ${contents} </channel> </rss> `; }; const sanitize = (string) => { const newString = string.replace(/\&/g, "&"); return newString; }; const dateString = (dstring) => { const target = new Date(dstring); return target.toUTCString(); }; const stringify = (obj) => { const template = (entry, id) => ` <item> <title>${sanitize(entry.title) || ""}</title> <link>https://site.com/en/blog/${entry.slug || ""}</link> <category>https://site.com/en/blog/${ entry.category.fields.slug }</category> <pubDate>${dateString(entry.publishedAt)}</pubDate> <description>${sanitize(entry.body || "")}</description> <dc:creator>${entry.author.fields.name}</dc:creator> <guid>https://site.com/en/blog/${entry.slug}</guid> </item> `; if (obj && obj.fields && Object.keys(obj.fields).length > 0) { return template(obj.fields, obj.sys.id); } else { return ""; } }; const buildFeed = (posts) => { let entries = ""; posts.items.forEach((item, index) => { if (item.fields && Object.keys(item.fields).length > 0) { entries += stringify(item); } }); return feedWrapper(entries); }; exports.handler = async (event, context) => { // get posts from contentful const posts = await client.getEntries({ content_type: "post", locale: "en-US", order: "-fields.publishedAt" }); const formatted = posts ? buildFeed(posts) : ""; return { statusCode: 200, body: formatted }; };
Функция ожидает публикации, создает ленту, перебирая элементы в цикле, затем вставляет элементы в оболочку XML и возвращает ее в качестве тела ответа. Теперь, чтобы сделать это маршрутом в вашем приложении netlify, вы можете создать перенаправление Netlify. В нашем случае мы уже широко используем файл /static/_redirects
, поэтому нам просто нужно было добавить новую строку:
/en/blog/feed/all /.netlify/functions/rss-feed 200!
Теперь мы можем указать пользователям URL-адрес en/blog/feed/all
, и он выполнит функцию для получения сообщений и возврата xml: https://www.brightcove.com/en/blog/feed/all
Это был довольно быстрый перенос, и я уверен, что в канал можно добавить больше (медиаресурсы и т. Д.), Но при этом наш канал заработал без особых проблем. Я удалил несколько дополнительных библиотек, которые нам пришлось использовать для очистки данных ответа, но стоит отметить, что если вы получаете Markdown обратно из CMS, его следует проанализировать и очистить - я обнаружил markdown- it и string , чтобы быть полезными для нормализации данных во что-то, что предпочитает парсер RSS.
Пожалуйста, выключите звук, если я хочу добавить сюда что-нибудь еще! Спасибо за прочтение!