@botonic/plugin-contentful
Version:
## What Does This Plugin Do?
344 lines (314 loc) • 11.7 kB
text/typescript
import * as contentful from 'contentful'
import * as cms from '../cms'
import {
Asset,
CommonFields,
Content,
ContentType,
Context,
DateRangeContent,
DEFAULT_CONTEXT,
DEFAULT_REFERENCES_TO_INCLUDE,
PagingOptions,
ScheduleContent,
TopContent,
TopContentType,
} from '../cms'
import { ContentfulOptions, DEFAULT_FALLBACK_CACHE_LIMIT_KB } from '../plugin'
import { SearchCandidate } from '../search'
import { AssetDelivery } from './contents/asset'
import { ButtonDelivery } from './contents/button'
import { CarouselDelivery } from './contents/carousel'
import { ContentsDelivery } from './contents/contents'
import { CustomDelivery } from './contents/custom'
import { DateRangeDelivery } from './contents/date-range'
import { DocumentDelivery } from './contents/document'
import { HandoffDelivery } from './contents/handoff'
import { ImageDelivery } from './contents/image'
import { InputDelivery } from './contents/input'
import { PayloadDelivery } from './contents/payload'
import { QueueDelivery } from './contents/queue'
import { ReferenceDelivery } from './contents/reference'
import { ScheduleDelivery } from './contents/schedule'
import { StartUpDelivery } from './contents/startup'
import { TextDelivery } from './contents/text'
import { UrlDelivery } from './contents/url'
import { VideoDelivery } from './contents/video'
import { CachedClientApi } from './delivery/cache'
import { ClientApiErrorReporter, ReducedClientApi } from './delivery/client-api'
import { FallbackCachedClientApi } from './delivery/fallback-cache'
import { AdaptorDeliveryApi, DeliveryApi } from './delivery-api'
import {
ContentfulEntryUtils,
createContentfulClientApi,
} from './delivery-utils'
import { IgnoreFallbackDecorator } from './ignore-fallback-decorator'
import { KeywordsDelivery } from './search/keywords'
export class Contentful implements cms.CMS {
private readonly _delivery: DeliveryApi
private readonly _contents: ContentsDelivery
private readonly _carousel: CarouselDelivery
private readonly _document: DocumentDelivery
private readonly _text: TextDelivery
private readonly _startUp: StartUpDelivery
private readonly _url: UrlDelivery
private readonly _payload: PayloadDelivery
private readonly _keywords: KeywordsDelivery
private readonly _schedule: ScheduleDelivery
private readonly _dateRange: DateRangeDelivery
private readonly _image: ImageDelivery
private readonly _video: VideoDelivery
private readonly _handoff: HandoffDelivery
private readonly _input: InputDelivery
private readonly _custom: CustomDelivery
private readonly _asset: AssetDelivery
private readonly _queue: QueueDelivery
private readonly _button: ButtonDelivery
/**
*
* See https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/api-rate-limits
* for API rate limits
*
* https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/
*/
constructor(options: ContentfulOptions) {
const logger = options.logger ?? console.error
const reporter: ClientApiErrorReporter = (
msg: string,
func: string,
args,
error: any
) => {
logger(`${msg}. '${func}(${String(args)})' threw '${String(error)}'`)
return Promise.resolve()
}
let client: ReducedClientApi = createContentfulClientApi(options)
if (!options.disableFallbackCache) {
client = new FallbackCachedClientApi(
client,
options.fallbackCacheLimitKB ?? DEFAULT_FALLBACK_CACHE_LIMIT_KB,
reporter,
logger
)
}
if (!options.disableCache) {
client = new CachedClientApi(client, options.cacheTtlMs, reporter)
}
const deliveryApi = new AdaptorDeliveryApi(client, options)
const resumeErrors = options.resumeErrors || false
const delivery = new IgnoreFallbackDecorator(deliveryApi)
this._contents = new ContentsDelivery(delivery, resumeErrors)
this._delivery = delivery
this._button = new ButtonDelivery(delivery, resumeErrors)
this._carousel = new CarouselDelivery(delivery, this._button, resumeErrors)
this._document = new DocumentDelivery(delivery, resumeErrors)
this._text = new TextDelivery(delivery, this._button, resumeErrors)
this._startUp = new StartUpDelivery(delivery, this._button, resumeErrors)
this._url = new UrlDelivery(delivery, resumeErrors)
this._payload = new PayloadDelivery(delivery, resumeErrors)
this._image = new ImageDelivery(delivery, resumeErrors)
this._video = new VideoDelivery(delivery, resumeErrors)
this._asset = new AssetDelivery(delivery, resumeErrors)
this._schedule = new ScheduleDelivery(delivery, resumeErrors)
this._queue = new QueueDelivery(delivery, this._schedule, resumeErrors)
this._handoff = new HandoffDelivery(
delivery,
this._queue,
this._text,
resumeErrors
)
this._input = new InputDelivery(delivery, resumeErrors)
this._custom = new CustomDelivery(delivery, resumeErrors)
this._dateRange = new DateRangeDelivery(delivery, resumeErrors)
const followUp = new ReferenceDelivery(
this._delivery,
this._carousel,
this._text,
this._image,
this._startUp,
this._video,
this._document,
this._url,
this._handoff,
this._queue,
this._schedule,
this._input,
this._payload,
this._dateRange
)
;[
this._carousel,
this._text,
this._image,
this._startUp,
this._video,
this._document,
this._url,
this._handoff,
this._queue,
this._schedule,
this._input,
this._payload,
this._dateRange,
].forEach(d => d.setReference(followUp))
this._keywords = new KeywordsDelivery(delivery)
}
button(id: string, context: Context = DEFAULT_CONTEXT): Promise<cms.Button> {
return this._button.button(id, context)
}
element(
id: string,
context: Context = DEFAULT_CONTEXT
): Promise<cms.Element> {
return this._carousel.element(id, context)
}
async carousel(id: string, context = DEFAULT_CONTEXT): Promise<cms.Carousel> {
return this._carousel.carousel(id, context)
}
document(id: string, context = DEFAULT_CONTEXT): Promise<cms.Document> {
return this._document.document(id, context)
}
async text(id: string, context = DEFAULT_CONTEXT): Promise<cms.Text> {
return this._text.text(id, context)
}
async startUp(id: string, context = DEFAULT_CONTEXT): Promise<cms.StartUp> {
return this._startUp.startUp(id, context)
}
async url(id: string, context = DEFAULT_CONTEXT): Promise<cms.Url> {
return this._url.url(id, context)
}
async payload(id: string, context = DEFAULT_CONTEXT): Promise<cms.Payload> {
return this._payload.payload(id, context)
}
async queue(id: string, context = DEFAULT_CONTEXT): Promise<cms.Queue> {
return this._queue.queue(id, context)
}
image(id: string, context = DEFAULT_CONTEXT): Promise<cms.Image> {
return this._image.image(id, context)
}
video(id: string, context = DEFAULT_CONTEXT): Promise<cms.Video> {
return this._video.video(id, context)
}
chitchat(id: string, context = DEFAULT_CONTEXT): Promise<cms.Chitchat> {
return this._text.text(id, context)
}
async handoff(id: string, context = DEFAULT_CONTEXT): Promise<cms.Handoff> {
return this._handoff.handoff(id, context)
}
async input(id: string, context = DEFAULT_CONTEXT): Promise<cms.Input> {
return this._input.input(id, context)
}
async custom(id: string, context = DEFAULT_CONTEXT): Promise<cms.Custom> {
return this._custom.custom(id, context)
}
topContents<T extends TopContent>(
model: TopContentType,
context = DEFAULT_CONTEXT,
filter?: (cf: CommonFields) => boolean,
paging = new PagingOptions()
): Promise<T[]> {
return this._contents.topContents(
model,
context,
(entry, ctxt) => this.topContentFromEntry(entry, ctxt),
filter,
paging
)
}
async content(
id: string,
context = DEFAULT_CONTEXT,
referencesToInclude = DEFAULT_REFERENCES_TO_INCLUDE
): Promise<Content> {
const entry = await this._delivery.getEntry(id, context, {
include: referencesToInclude,
})
return this.contentFromEntry(entry, context)
}
contents<T extends Content>(
contentType: ContentType,
context = DEFAULT_CONTEXT,
paging = new PagingOptions()
): Promise<T[]> {
return this._contents.contents(
contentType,
context,
(entry, ctxt) => this.contentFromEntry(entry, ctxt),
paging
)
}
async topContentFromEntry<T extends TopContent>(
entry: contentful.Entry<any>,
context: Context
): Promise<T> {
const model = ContentfulEntryUtils.getContentModel(entry)
const retype = (c: TopContent) => c as T
switch (model) {
case ContentType.CAROUSEL:
return retype(await this._carousel.fromEntry(entry, context))
case ContentType.DOCUMENT:
return retype(await this._document.fromEntry(entry, context))
case ContentType.QUEUE:
return retype(await this._queue.fromEntry(entry, context))
case ContentType.CHITCHAT:
case ContentType.TEXT:
return retype(await this._text.fromEntry(entry, context))
case ContentType.IMAGE:
return retype(await this._image.fromEntry(entry, context))
case ContentType.VIDEO:
return retype(await this._video.fromEntry(entry, context))
case ContentType.HANDOFF:
return retype(await this._handoff.fromEntry(entry, context))
case ContentType.INPUT:
return retype(await this._input.fromEntry(entry, context))
case ContentType.URL:
return retype(await this._url.fromEntry(entry, context))
case ContentType.PAYLOAD:
return retype(await this._payload.fromEntry(entry, context))
case ContentType.STARTUP:
return retype(await this._startUp.fromEntry(entry, context))
case ContentType.SCHEDULE:
return retype(await this._schedule.fromEntry(entry, context))
case ContentType.DATE_RANGE:
return retype(await this._dateRange.fromEntry(entry, context))
default:
throw new Error(`${model} is not a Content type`)
}
// no need to wrap in an CMSException with the content id because asyncMap
// already does it
}
// TODO move all delivery instances to a class
async contentFromEntry<T extends Content>(
entry: contentful.Entry<any>,
context: Context
): Promise<T> {
const model = ContentfulEntryUtils.getContentModel(entry)
const retype = (c: Content) => c as T
switch (model) {
case ContentType.BUTTON:
return retype(this._button.fromEntry(entry, context))
case ContentType.ELEMENT:
return retype(await this._carousel.elementFromEntry(entry, context))
default:
return retype(await this.topContentFromEntry(entry, context))
}
}
async contentsWithKeywords(
context = DEFAULT_CONTEXT,
paging = new PagingOptions()
): Promise<SearchCandidate[]> {
return this._keywords.contentsWithKeywords(context, paging)
}
async schedule(id: string, context: Context): Promise<ScheduleContent> {
return this._schedule.schedule(id, context)
}
asset(id: string, context = DEFAULT_CONTEXT): Promise<cms.Asset> {
return this._asset.asset(id, context)
}
assets(context = DEFAULT_CONTEXT): Promise<Asset[]> {
return this._asset.assets(context)
}
dateRange(id: string, context: Context): Promise<DateRangeContent> {
return this._dateRange.dateRange(id, context)
}
}