@botonic/plugin-contentful
Version:
Botonic Plugin Contentful is one of the **[available](https://github.com/hubtype/botonic/tree/master/packages)** plugins for Botonic. **[Contentful](http://www.contentful.com)** is a CMS (Content Management System) which manages contents of a great variet
134 lines (120 loc) • 3.67 kB
text/typescript
import * as contentful from 'contentful'
import { Entry } from 'contentful'
import {
AssetId,
CmsException,
Content,
ContentId,
ContentType,
Context,
isCustomModel,
isSameModel,
ResourceId,
} from '../cms'
import { asyncMap } from '../util/async'
import { DeliveryApi } from './delivery-api'
import { ContentfulEntryUtils, ContentWithNameFields } from './delivery-utils'
export abstract class ResourceDelivery {
constructor(
protected readonly delivery: DeliveryApi,
protected readonly resumeErrors: boolean
) {}
urlFromAssetRequired(assetField: contentful.Asset): string {
if (!assetField.fields.file) {
throw new CmsException(
`found empty ${assetField.sys.type} asset. Missing localization?`
)
}
return 'https:' + assetField.fields.file.url
}
urlFromAssetOptional(
assetField: contentful.Asset | undefined = undefined,
context: Context
): string | undefined {
if (!assetField) {
return undefined
}
if (!assetField.fields.file) {
this.logOrThrow(
`found empty asset. Missing localization?`,
context,
undefined,
new AssetId(assetField.sys.id, assetField.sys.type)
)
return undefined
}
return this.urlFromAssetRequired(assetField)
}
protected checkEntry(entry: Entry<any>) {
if (entry.fields == undefined) {
// this can also happen when the chain of content references is too long.
// Try increasing the {include} key in the call to getEntry of the top component
throw new CmsException(
`Cannot find '${entry.sys.type}' with id '${entry.sys.id}' Broken reference? Not published?`
)
}
}
protected logOrThrow(
doing: string,
context: Context,
reason: any,
resourceId: ResourceId
) {
if (this.resumeErrors) {
console.error(
`ERROR: ${doing} on content ${resourceId.toString()} on locale '${String(
context.locale
)}'. Returning content with partial data.`
)
return
}
throw new CmsException(doing, reason, resourceId)
}
protected asyncMap<T extends Content>(
context: Context,
entries: Entry<any>[],
factory: (entry: Entry<any>) => Promise<T>
): Promise<T[]> {
return asyncMap(context, entries, factory, undefined, (entry, e) => {
const contentId = this.getContentIdForLogs(entry)
this.logOrThrow(`Loading content failed`, context, e, contentId)
return undefined
})
}
protected getContentIdForLogs(entry: contentful.Entry<any>): ContentId {
if (ContentfulEntryUtils.isFullEntry(entry)) {
return ContentfulEntryUtils.getContentId(entry)
}
return new ContentId('<UNKNOWN MODEL TYPE>' as ContentType, entry.sys.id)
}
}
export abstract class ContentDelivery extends ResourceDelivery {
constructor(
readonly modelType: ContentType,
delivery: DeliveryApi,
resumeErrors: boolean
) {
super(delivery, resumeErrors)
}
async getEntry<T>(
id: string,
context: Context,
query: any = {}
): Promise<contentful.Entry<T>> {
const entry = await this.delivery.getEntry<T>(id, context, query)
const gotType = ContentfulEntryUtils.getContentModel(entry)
if (
!isCustomModel(gotType, this.modelType) &&
!isSameModel(gotType, this.modelType)
) {
throw new Error(
`Requested model with id '${id}' of type '${this.modelType}' but got '${gotType}'`
)
}
return entry
}
entryId(entry: Entry<ContentWithNameFields>): string {
return entry.fields.name + '/' + entry.sys.id
}
}
export abstract class TopContentDelivery extends ContentDelivery {}