CMS service improvements

This commit is contained in:
William Grant 2021-09-03 15:21:12 +02:00
parent 7b789126d0
commit f567145e06
6 changed files with 137 additions and 90 deletions

View File

@ -4,7 +4,7 @@ import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { Contained } from '../components/Contained';
import { RichBody } from '../components/RichBody';
import { NAVIGATION } from '../constants';
import { CMS, NAVIGATION } from '../constants';
import { CmsApi, unslugify } from '../services/cms';
import { PageType, setPageType, SideMenuItem } from '../state/navigation';
import { ISplitPage } from '../types/cms';
@ -44,7 +44,7 @@ export async function getStaticProps({ params }) {
page,
href: `/${href}`,
},
revalidate: 60,
revalidate: CMS.CONTENT_REVALIDATE_RATE,
};
}

View File

@ -3,6 +3,7 @@ import Head from 'next/head';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { Article } from '../../components/article/Article';
import { CMS } from '../../constants';
import { CmsApi, generateLinkMeta } from '../../services/cms';
import { PageType, setPageType, setPostTitle } from '../../state/navigation';
import { IPost } from '../../types/cms';
@ -20,7 +21,7 @@ export async function getStaticPaths() {
// Contentful only allows 100 at a time
while (!foundAllPosts) {
const { posts: _posts } = await cms.fetchBlogEntries(100, page);
const { entries: _posts } = await cms.fetchBlogEntries(100, page);
if (_posts.length === 0) {
foundAllPosts = true;
@ -42,7 +43,7 @@ export async function getStaticProps({ params }) {
console.log(`Building page: %c${params.slug}`, 'color: purple;');
const cms = new CmsApi();
const post = await cms.fetchBlogBySlug(String(params?.slug) ?? '');
const post = await cms.fetchEntryBySlug(String(params?.slug) ?? '', 'post');
// embedded links in post body need metadata for preview
post.body = await generateLinkMeta(post.body);
const url = generateURL(params?.slug ? `/blog/${params?.slug}` : '/blog');
@ -55,7 +56,7 @@ export async function getStaticProps({ params }) {
post,
url,
},
revalidate: 60,
revalidate: CMS.CONTENT_REVALIDATE_RATE,
};
}

View File

@ -30,7 +30,7 @@ export const getServerSideProps: GetServerSideProps = async context => {
// Fetch posts even when tag, for related etc
// Pagination only occurs when tag isnt defined.
// If tag is defined, pagination is for tag results
const { posts, total: totalPosts } = await cms.fetchBlogEntries(
const { entries: posts, total: totalPosts } = await cms.fetchBlogEntries(
RESULTS_PER_PAGE,
tag ? 1 : page,
);
@ -42,7 +42,7 @@ export const getServerSideProps: GetServerSideProps = async context => {
let filteredTotalPosts = totalPosts;
if (tag) {
const {
posts: _tagPosts = [],
entries: _tagPosts = [],
total: _tagTotalPosts,
} = await cms.fetchBlogEntriesByTag(tag ?? '', RESULTS_PER_PAGE, page);
tagPosts = _tagPosts;
@ -50,7 +50,7 @@ export const getServerSideProps: GetServerSideProps = async context => {
} else {
// Retrieve all blog posts without the `dev-update` tag when not searching by tag
const {
posts: _tagPosts = [],
entries: _tagPosts = [],
total: _tagTotalPosts,
} = await cms.fetchBlogEntriesWithoutDevUpdates(RESULTS_PER_PAGE, page);

View File

@ -12,7 +12,7 @@ import { Contained } from '../components/Contained';
export const getServerSideProps: GetServerSideProps = async () => {
const cms = new CmsApi();
const { faqItems, total } = await cms.fetchFAQItems();
const { entries: faqItems, total } = await cms.fetchFAQItems();
return {
props: {

View File

@ -1,5 +1,5 @@
import { Document, Block, Inline } from '@contentful/rich-text-types';
import { ContentfulClientApi, createClient } from 'contentful';
import { ContentfulClientApi, createClient, EntryCollection } from 'contentful';
import { format, parseISO } from 'date-fns';
import React from 'react';
import BittrexSVG from '../assets/svgs/bittrex-logo.svg';
@ -16,21 +16,14 @@ import {
IPost,
ISplitPage,
IFAQItem,
IFetchBlogEntriesReturn,
IFetchEntriesReturn,
IFetchFAQItemsReturn,
} from '../types/cms';
import isLive from '../utils/environment';
import { generateURL } from '../utils/metadata';
import { fetchContent } from './embed';
interface IFetchBlogEntriesReturn {
posts: Array<IPost>;
total: number;
}
interface IFetchFAQItemsReturn {
faqItems: Array<IFAQItem>;
total: number;
}
function loadOptions(options: any) {
if (isLive()) options['fields.live'] = true;
return options;
@ -55,7 +48,7 @@ export class CmsApi {
quantity = CMS.BLOG_RESULTS_PER_PAGE,
page = 1,
): Promise<IFetchBlogEntriesReturn> {
const entries = await this.client.getEntries(
const _entries = await this.client.getEntries(
loadOptions({
content_type: 'post', // only fetch blog post entry
order: '-fields.date',
@ -64,35 +57,11 @@ export class CmsApi {
}),
);
if (entries && entries.items && entries.items.length > 0) {
const blogPosts = entries.items.map(entry => this.convertPost(entry));
return { posts: blogPosts, total: entries.total };
}
return { posts: [], total: 0 } as IFetchBlogEntriesReturn;
}
public async fetchBlogById(id): Promise<IPost> {
return this.client.getEntry(id).then(entry => {
if (entry) {
return this.convertPost(entry);
}
return null;
});
}
public async fetchBlogBySlug(slug: string): Promise<IPost> {
const entries = await this.client.getEntries({
content_type: 'post',
'fields.slug': slug,
});
if (entries?.items?.length > 0) {
const post = this.convertPost(entries.items[0]);
return post;
}
return null;
const results = await this.generateEntries(_entries, 'post');
return {
entries: results.entries as Array<IPost>,
total: results.total,
};
}
public async fetchBlogEntriesByTag(
@ -100,7 +69,7 @@ export class CmsApi {
quantity = CMS.BLOG_RESULTS_PER_PAGE_TAGGED,
page = 1,
): Promise<IFetchBlogEntriesReturn> {
const entries = await this.client.getEntries(
const _entries = await this.client.getEntries(
loadOptions({
content_type: 'post',
order: '-fields.date',
@ -110,12 +79,11 @@ export class CmsApi {
}),
);
if (entries?.items?.length > 0) {
const posts = entries.items.map(entry => this.convertPost(entry));
return { posts, total: entries.total };
}
return { posts: [], total: 0 } as IFetchBlogEntriesReturn;
const results = await this.generateEntries(_entries, 'post');
return {
entries: results.entries as Array<IPost>,
total: results.total,
};
}
public async fetchBlogEntriesWithoutDevUpdates(
@ -123,7 +91,7 @@ export class CmsApi {
page = 1,
): Promise<IFetchBlogEntriesReturn> {
const DEV_UPDATE_TAG = 'dev-update';
const entries = await this.client.getEntries(
const _entries = await this.client.getEntries(
loadOptions({
content_type: 'post', // only fetch blog post entry
order: '-fields.date',
@ -133,40 +101,90 @@ export class CmsApi {
}),
);
if (entries && entries.items && entries.items.length > 0) {
const blogPosts = entries.items.map(entry => this.convertPost(entry));
return { posts: blogPosts, total: entries.total };
}
return { posts: [], total: 0 } as IFetchBlogEntriesReturn;
const results = await this.generateEntries(_entries, 'post');
return {
entries: results.entries as Array<IPost>,
total: results.total,
};
}
public async fetchPageEntries(): Promise<TPages> {
try {
const entries = await this.client.getEntries({
content_type: 'splitPage', // only fetch blog post entry
order: 'fields.order',
const _entries = await this.client.getEntries(
loadOptions({
content_type: 'splitPage', // only fetch blog post entry
order: 'fields.order',
}),
);
const results = await this.generateEntries(_entries, 'splitPage');
const pages: TPages = {};
results.entries.forEach(page => {
const pageExists = SideMenuItem[page.id];
if (pageExists) {
pages[page.id] = page;
}
});
if (entries && entries.items && entries.items.length > 0) {
const pagesArray = entries.items.map(entry => this.convertPage(entry));
const pages: TPages = {};
pagesArray.forEach(page => {
const pageExists = SideMenuItem[page.id];
if (pageExists) {
pages[page.id] = page;
}
});
return pages;
}
return pages;
} catch (e) {
return {};
}
}
public async fetchFAQItems(): Promise<IFetchFAQItemsReturn> {
const _entries = await this.client.getEntries({
content_type: 'faq_item', // only fetch faq items
order: 'fields.id',
});
const results = await this.generateEntries(_entries, 'faq');
return {
entries: results.entries as Array<IFAQItem>,
total: results.total,
};
}
public async fetchEntryById(id): Promise<IPost> {
return this.client.getEntry(id).then(entry => {
if (entry) {
return this.convertPost(entry);
}
return null;
});
}
public async fetchEntryBySlug(
slug: string,
entryType: 'post' | 'splitPage',
): Promise<any> {
const _entries = await this.client.getEntries({
content_type: entryType, // only fetch specific type
'fields.slug': slug,
});
if (_entries?.items?.length > 0) {
let entry;
switch (entryType) {
case 'post':
entry = this.convertPost(_entries.items[0]);
break;
case 'splitPage':
entry = this.convertPage(_entries.items[0]);
break;
default:
break;
}
return entry;
}
return Promise.reject(
new Error(`Failed to fetch ${entryType} for ${slug}`),
);
}
public async fetchPageById(id: SideMenuItem): Promise<ISplitPage> {
return this.client
.getEntries({
@ -181,18 +199,29 @@ export class CmsApi {
});
}
public async fetchFAQItems(): Promise<IFetchFAQItemsReturn> {
const entries = await this.client.getEntries({
content_type: 'faq_item', // only fetch faq items
order: 'fields.id',
});
public async generateEntries(
entries: EntryCollection<unknown>,
entryType: 'post' | 'faq' | 'splitPage',
): Promise<IFetchEntriesReturn> {
let _entries: any = [];
if (entries && entries.items && entries.items.length > 0) {
const faqItems = entries.items.map(entry => this.convertFAQ(entry));
return { faqItems, total: entries.total };
switch (entryType) {
case 'post':
_entries = entries.items.map(entry => this.convertPost(entry));
break;
case 'faq':
_entries = entries.items.map(entry => this.convertFAQ(entry));
break;
case 'splitPage':
_entries = entries.items.map(entry => this.convertPage(entry));
break;
default:
break;
}
return { entries: _entries, total: entries.total };
}
return { faqItems: [], total: 0 } as IFetchFAQItemsReturn;
return { entries: _entries, total: 0 };
}
public convertImage = (rawImage): IFigureImage =>
@ -201,6 +230,8 @@ export class CmsApi {
imageUrl: rawImage.file.url.replace('//', 'https://'), // may need to put null check as well here
description: rawImage.description ?? null,
title: rawImage.title ?? null,
width: rawImage.file.details.image.width,
height: rawImage.file.details.image.height,
}
: null;

View File

@ -17,6 +17,8 @@ export type IFigureImage = {
title: string | null;
description: string | null;
imageUrl: string;
width: string | number;
height: string | number;
};
export interface IPost {
@ -50,3 +52,16 @@ export interface IFAQItem {
question: string;
answer: Document;
}
export interface IFetchEntriesReturn {
entries: Array<any>;
total: number;
}
export interface IFetchBlogEntriesReturn extends IFetchEntriesReturn {
entries: Array<IPost>;
}
export interface IFetchFAQItemsReturn extends IFetchEntriesReturn {
entries: Array<IFAQItem>;
}