storyblok-ts-client
Version:
Typescript library for working with Storyblok management API.
1,282 lines (1,235 loc) • 41.2 kB
text/typescript
import {AxiosResponse} from 'axios'
import * as FormData from 'form-data'
import {
IAsset,
IAssetFolder,
IComponent,
IPendingAsset,
IPendingAssetFolder,
IPendingComponent,
IPendingStory,
IRegistration,
ISpace,
IStory,
} from '../interfaces'
import {imageToBuffer} from '../utilities/imageProcessing'
import {ICustomAxiosRequestConfig, Storyblok} from './Storyblok'
export const retrySettings: {
burst: ICustomAxiosRequestConfig
extended: ICustomAxiosRequestConfig
} = {
burst: {
retries: 20,
retryDelay: 1750,
},
extended: {
retries: 40,
retryDelay: 1250,
},
}
interface IApiClientClass {
assetFolders: {
create: (n: string) => Promise<IAssetFolder>
delete: (i: number) => Promise<void>
deleteExisting: () => Promise<void[]>
get: (i: number) => Promise<IAssetFolder>
getByName: (s: string) => Promise<IAssetFolder[]>
getExisting: () => Promise<IAssetFolder[]>
}
assets: {
count: () => Promise<number>
createFromImage: (
d: IPendingAsset,
f: string,
c?: boolean,
s?: number
) => Promise<string>
delete: (i: number) => Promise<IAsset>
deleteExisting: () => Promise<IAsset[]>
get: (i: number) => Promise<IAsset>
getByPage: (p?: number, pp?: number) => Promise<IAsset[]>
getByUrl: (u: string) => Promise<IAsset>
getExisting: () => Promise<IAsset[]>
register: (d: IPendingAsset) => Promise<IRegistration>
upload: (b: Buffer, r: IRegistration) => Promise<string>
}
components: {
create: (d: IPendingComponent) => Promise<IComponent>
delete: (i: number) => Promise<IComponent>
deleteExisting: () => Promise<IComponent[]>
get: (i: number) => Promise<IComponent>
getExisting: () => Promise<IComponent[]>
update: (d: IComponent) => Promise<IComponent>
}
spaces: {
get: () => Promise<ISpace>
}
stories: {
count: () => Promise<number>
countPages: (p: number) => Promise<number>
create: (d: IPendingStory) => Promise<IStory>
delete: (i: number) => Promise<IStory>
deleteExisting: () => Promise<IStory[]>
get: (i: number) => Promise<IStory>
getByPage: (p?: number, pp?: number) => Promise<IStory[]>
getExisting: () => Promise<IStory[]>
publish: (i: number) => Promise<IStory>
publishPendings: () => Promise<IStory[]>
reorder: (i: number, ai: number) => Promise<IStory>
update: (d: IStory) => Promise<IStory>
}
}
let storyblok: Storyblok | undefined
/**
* Management API wrapper around Storyblok class.
*
* @export
* @class ApiClient
* @implements {IStoryblokClass}
* @param {string} apiToken - API access token.
* @param {number} spaceId - Storyblok working space id.
* @example
* const {ApiClient} = require('storyblok-ts-client')
* const apiClient = new ApiClient('fake_api_token', 12345)
*/
export class ApiClient implements IApiClientClass {
private spaceId: number
private storyblok: Storyblok
constructor(apiToken: string, spaceId: number) {
this.spaceId = spaceId
if (!storyblok) {
storyblok = new Storyblok(apiToken)
}
this.storyblok = storyblok
}
/**
* Object that contains API methods for asset folder operations
*
* @readonly
* @name ApiClient#assetFolders
* @memberof ApiClient
*/
public get assetFolders() {
return {
/**
* Create an asset folder.
*
* @name ApiClient#assetFolders#create
* @param {string} name - Name of asset folder to create.
* @returns {Promise}
* @fulfil {IAssetFolder} Details of the asset folder created.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assetFolders
*/
create: (name: string): Promise<IAssetFolder> =>
this.createAssetFolder(name),
/**
* Delete a specific asset folder.
*
* @name ApiClient#assetFolders#delete
* @param {number} id - Id of asset folder to be deleted.
* @returns {Promise}
* @fulfil {void}
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assetFolders
*/
delete: (id: number): Promise<void> => this.deleteAssetFolder(id),
/**
* Delete all existing asset folders.
*
* @name ApiClient#assetFolders#deleteExisting
* @returns {Promise}
* @fulfil {void[]}
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assetFolders
*/
deleteExisting: (): Promise<void[]> => this.deleteExistingAssetFolders(),
/**
* Get a specific asset folder.
*
* @name ApiClient#assetFolders#get
* @param {number} id - Id of the target asset folder.
* @returns {Promise}
* @fulfil {IAssetFolder} Asset folder information.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assetFolders
*/
get: (id: number): Promise<IAssetFolder> => this.getAssetFolder(id),
/**
* Get asset folders by matching asset folders names to the supplied string.
*
* @name ApiClient#assetFolders#getByName
* @param {string} searchString - String to search by.
* @returns {Promise}
* @fulfil {IAssetFolder[]} List of matched asset folders.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assetFolders
*/
getByName: (searchString: string): Promise<IAssetFolder[]> =>
this.getAssetFolderByName(searchString),
/**
* Get existing asset folders.
*
* @name ApiClient#assetFolders#getExisting
* @returns {Promise}
* @fulfil {IAssetFolder[]} List of existing asset folders.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assetFolders
*/
getExisting: (): Promise<IAssetFolder[]> =>
this.getExistingAssetFolders(),
}
}
/**
* Object that contains API methods for asset operations
*
* @readonly
* @name ApiClient#assets
* @memberof ApiClient
*/
public get assets() {
return {
/**
* Get total number of existing assets.
*
* @name ApiClient#assets#count
* @returns {Promise}
* @fulfil {number} A count of existing assets.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
count: (): Promise<number> => this.countAssets(),
/**
* Create an asset and upload the physical file.
*
* @name ApiClient#assets#createFromImage
* @param {IPendingAsset} data - Asset information.
* @param {string} filePath - Absolute file path to the image.
* @param {boolean} compress - Flag to compress image.
* @param {number} sizeLimit - Resizing dimension limit value.
* @returns {Promise}
* @fulfil {string} public access url of the new asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
createFromImage: (
data: IPendingAsset,
filePath: string,
compress?: boolean,
sizeLimit?: number
): Promise<string> =>
this.createAssetFromImage(data, filePath, compress, sizeLimit),
/**
* Delete a specific asset.
*
* @name ApiClient#assets#delete
* @param {number} id - Id of the asset to be deleted.
* @returns {Promise}
* @fulfil {IAsset} Information of the deleted asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
delete: (id: number): Promise<IAsset> => this.deleteAsset(id),
/**
* Delete all existing assets.
*
* @name ApiClient#assets#deleteExisting
* @returns {Promise}
* @fulfil {IAsset[]} Information on the deleted assets.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
deleteExisting: (): Promise<IAsset[]> => this.deleteExistingAssets(),
/**
* Get a specific asset.
*
* @name ApiClient#assets#get
* @param {number} id - Id of asset to fetch.
* @returns {Promise}
* @fulfil {IAsset} Details of the asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
get: (id: number): Promise<IAsset> => this.getAsset(id),
/**
* Get asset on a specific pagination page number.
*
* @name ApiClient#assets#getByPage
* @param {number} [page=1] - Pagination page.
* @param {number} [perPage=25] - Assets per page.
* @returns {Promise<IAsset[]>}
* @fulfil {IAsset[]} Assets on the pagination page.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
getByPage: (page?: number, perPage?: number): Promise<IAsset[]> =>
this.getAssetsByPage(page, perPage),
/**
* Find a specific asset by its public url.
*
* @name ApiClient#assets#getByUrl
* @param {string} url - Url to match by.
* @returns {Promise}
* @fulfil {IAsset} Matched asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
getByUrl: (url: string): Promise<IAsset> => this.getAssetByUrl(url),
/**
* List all existing assets.
*
* @name ApiClient#assets#getExisting
* @returns {Promise}
* @fulfil {IAsset[]} A list of existing assets.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
getExisting: (): Promise<IAsset[]> => this.getExistingAssets(),
/**
* Register a Storyblok asset.
*
* @name ApiClient#assets#register
* @param {IPendingAsset} asset - Information to create asset from.
* @param {string} asset.filename - File name to register for.
* @param {number} [asset.asset_folder_id] - (optional) Assign a asset folder.
* @param {number} [asset.id] - (optional) Id of existing asset to replace with this new asset.
* @returns {Promise}
* @fulfil {IRegistration} Asset registration info (used for uploading).
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
register: (data: IPendingAsset): Promise<IRegistration> =>
this.registerAsset(data),
/**
* Upload a registered asset with failure-retry (20 retries and incremental delay period of 1250ms with +/- 500ms variance).
*
* @name ApiClient#assets#upload
* @param {Buffer} buffer - Buffered asset data.
* @param {IRegistration} registration - Registration info.
* @returns {Promise}
* @fulfil {string} Access url of the uploaded asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#assets
*/
upload: (buffer: Buffer, registration: IRegistration): Promise<string> =>
this.uploadAsset(buffer, registration),
}
}
/**
* Object that contains API methods for component operations
*
* @name ApiClient#components
* @readonly
* @memberof ApiClient
*/
public get components() {
return {
/**
* Create a component.
*
* @name ApiClient#components#create
* @param {IPendingComponent} data - Info on component to be created.
* @returns {Promise}
* @fulfil {IComponent} Details of the component that was created.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#components
*/
create: (data: IPendingComponent): Promise<IComponent> =>
this.createComponent(data),
/**
* Delete a specific component.
*
* @name ApiClient#components#delete
* @param {number} id - Id of component to be deleted.
* @returns {Promise}
* @fulfil {IComponent} Details of the deleted component.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#components
*/
delete: (id: number): Promise<IComponent> => this.deleteComponent(id),
/**
* Delete existing components.
*
* @name ApiClient#components#deleteExisting
* @returns {Promise}
* @fulfil {IComponent[]} A list of deleted components details.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#components
*/
deleteExisting: (): Promise<IComponent[]> =>
this.deleteExistingComponents(),
/**
* Fetch for a specific component.
*
* @name ApiClient#components#get
* @param {number} id - Component id to fetch by.
* @returns {Promise}
* @fulfil {IComponent} Details of the component definition.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#components
*/
get: (id: number): Promise<IComponent> => this.getComponent(id),
/**
* List existing components.
*
* @name ApiClient#components#getExisting
* @returns {Promise}
* @fulfil {IComponent[]} A list of component definitions.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#components
*/
getExisting: (): Promise<IComponent[]> => this.getExistingComponents(),
/**
* Update a component.
*
* @name ApiClient#components#update
* @param {IComponent} data - Storyblok component data object with modified info.
* @returns {Promise}
* @fulfil {IComponent} Details of component that was updated.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
update: (data: IComponent): Promise<IComponent> =>
this.updateComponent(data),
}
}
/**
* Object that contains API methods for space operations
*
* @name ApiClient#spaces
* @readonly
* @memberof ApiClient
*/
public get spaces() {
return {
/**
* Get information on the working Storyblok space.
*
* @name ApiClient#spaces#get
* @returns {Promise}
* @fulfil {ISpace} Working space information.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#spaces
*/
get: (): Promise<ISpace> => this.getSpace(),
}
}
/**
* Object that contains API methods for story operations
*
* @name ApiClient#stories
* @readonly
* @memberof ApiClient
*/
public get stories() {
return {
/**
* Get total number of existing stories (including folders).
*
* @name ApiClient#stories#count
* @returns {Promise}
* @fulfil {number} A count of existing stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
count: (): Promise<number> => this.countStories(),
/**
* Get total pagination page count.
*
* @name ApiClient#stories#countPages
* @param {number} [perPage] - (optional) How many stories per page. Defaults to 25.
* @returns {Promise}
* @fulfil {number} Total story pagination page count.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
countPages: (perPage: number = 25): Promise<number> =>
this.countStoryPages(perPage),
/**
* Create a story.
*
* @name ApiClient#stories#create
* @param {IPendingStory} data - Storyblok story data object.
* @returns {Promise}
* @fulfil {IStory} Details of story that was created.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
create: (data: IPendingStory): Promise<IStory> => this.createStory(data),
/**
* Delete a specific story.
*
* @name ApiClient#stories#delete
* @param {IStory} id - Id of the story to be deleted.
* @returns {Promise}
* @fulfil {IStory} Details of the story that was deleted.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
delete: (id: number): Promise<IStory> => this.deleteStory(id),
/**
* Delete all existing stories.
*
* @name ApiClient#stories#deleteExisting
* @returns {Promise}
* @fulfil {IStory[]} A list of deleted stories details.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
deleteExisting: (): Promise<IStory[]> => this.deleteExistingStories(),
/**
* Get a specific story.
*
* @name ApiClient#stories#get
* @param {number} id - Id of the content story.
* @returns {Promise}
* @fulfil {IStory} Details of content story.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
get: (id: number): Promise<IStory> => this.getStory(id),
/**
* Get stories on a pagination page.
*
* @name ApiClient#stories#getByPage
* @param {number} page - Pagination page number.
* @param {number} [perPage] - (optional) How many stories per page. Defaults to 25.
* @returns {Promise}
* @fulfil {IStory[]} A page of stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
getByPage: (page?: number, perPage?: number): Promise<IStory[]> =>
this.getStoriesByPage(page, perPage),
/**
* List all existing stories.
*
* @name ApiClient#stories#getExisting
* @returns {Promise}
* @fulfil {IStory[]} A list of existing content stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
getExisting: (): Promise<IStory[]> => this.getExistingStories(),
/**
* Publish a specific story.
*
* @name ApiClient#stories#publish
* @param {number} id - Id of the story to publish
* @returns {Promise}
* @fulfil {IStory} Details of the published story
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
publish: (id: number): Promise<IStory> => this.publishStory(id),
/**
* Publish all unpublished stories.
*
* @name ApiClient#stories#publishPendings
* @returns {Promise}
* @fulfil {IStory[]} List of published stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
publishPendings: (): Promise<IStory[]> => this.publishPendingStories(),
/**
* Update a story's sequential order.
*
* @name ApiClient#stories#reorder
* @param {number} id - Id of the story to be moved.
* @param {number} afterId - Id of reference story to position after.
* @returns {Promise}
* @fulfil {IStory} Details of the moved story.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
reorder: (id: number, afterId: number): Promise<IStory> =>
this.reorderStory(id, afterId),
/**
* Update a story.
*
* @name ApiClient#stories#update
* @param {IStory} data - Modified story info.
* @returns {Promise}
* @fulfil {IStory} Details of story that was updated.
* @reject {AxiosError} Axios error.
* @memberof ApiClient#stories
*/
update: (data: IStory): Promise<IStory> => this.updateStory(data),
}
}
/**
* Get total number of existing assets.
*
* @name ApiClient#countAssets
* @returns {Promise}
* @fulfil {number} A count of existing assets.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public countAssets(): Promise<number> {
return this.getSpace()
.then(s => s.assets_count)
.catch(e => Promise.reject(e))
}
/**
* Get total number of existing stories (including folders).
*
* @name ApiClient#countStories
* @returns {Promise}
* @fulfil {number} A count of existing stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public countStories(): Promise<number> {
return this.getExistingStories()
.then(ss => ss.length)
.catch(e => Promise.reject(e))
}
/**
* Get total pagination page count.
*
* @name ApiClient#countStoryPages
* @param {number} [perPage] - (optional) How many stories per page. Defaults to 25.
* @returns {Promise}
* @fulfil {number} Total story pagination page count.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public countStoryPages(perPage: number = 25): Promise<number> {
const url = `/${this.spaceId}/stories`
type R = (r: AxiosResponse) => number
const responseHandler: R = r => {
const total = r.headers.total as number
return Math.ceil(total / perPage)
}
return this.storyblok
.get(url, retrySettings.burst)
.then(responseHandler)
.catch(r => Promise.reject(r))
}
/**
* Create an asset folder.
*
* @name ApiClient#createAssetFolder
* @param {string} name - Name of asset folder to create.
* @returns {Promise}
* @fulfil {IAssetFolder} Details of the asset folder created.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public createAssetFolder(name: string): Promise<IAssetFolder> {
const url = `/${this.spaceId}/asset_folders`
const data: IPendingAssetFolder = {name}
return this.storyblok
.post(url, {asset_folder: data}, retrySettings.burst)
.then(r => r.data.asset_folder)
.catch(e => Promise.reject(e))
}
/**
* Create an asset and upload the physical file.
*
* @name ApiClient#createAssetFromImage
* @param {IPendingAsset} data - Asset information.
* @param {string} filePath - Absolute file path to the image.
* @param {boolean} compress - Flag to compress image.
* @param {number} sizeLimit - Resizing dimension limit value.
* @returns {Promise}
* @fulfil {string} Public access url of the new asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public async createAssetFromImage(
data: IPendingAsset,
filePath: string,
compress: boolean = true,
sizeLimit: number = 640
): Promise<string> {
try {
const [registration, buffer] = await Promise.all([
this.registerAsset(data),
imageToBuffer(filePath, compress, sizeLimit),
])
return await this.uploadAsset(buffer, registration)
} catch (e) {
throw e
}
}
/**
* Create a component.
*
* @name ApiClient#components#create
* @param {IPendingComponent} data - Info on component to be created.
* @returns {Promise}
* @fulfil {IComponent} Details of the component that was created.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public createComponent(data: IPendingComponent): Promise<IComponent> {
const url = `/${this.spaceId}/components`
return this.storyblok
.post(url, {component: data}, retrySettings.burst)
.then(r => r.data.component)
.catch(e => Promise.reject(e))
}
/**
* Create a story.
*
* @name ApiClient#createStory
* @param {IPendingStory} data - Storyblok story data object.
* @returns {Promise}
* @fulfil {IStory} Details of story that was created.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public createStory(data: IPendingStory): Promise<IStory> {
const url = `/${this.spaceId}/stories`
return this.storyblok
.post(url, {story: data}, retrySettings.burst)
.then(r => r.data.story)
.catch(e => Promise.reject(e))
}
/**
* Delete a specific asset.
*
* @name ApiClient#deleteAsset
* @param {number} id - Id of the asset to be deleted.
* @returns {Promise}
* @fulfil {IAsset} Information of the deleted asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public deleteAsset(id: number): Promise<IAsset> {
const url: string = `/${this.spaceId}/assets/${id}`
return this.storyblok
.delete(url, retrySettings.burst)
.then(r => r.data)
.catch(e => Promise.reject(e))
}
/**
* Delete a specific asset folder.
*
* @name ApiClient#deleteAssetFolder
* @param {number} id - Id of asset folder to be deleted.
* @returns {Promise}
* @fulfil {void}
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public deleteAssetFolder(id: number): Promise<void> {
const url = `/${this.spaceId}/asset_folders/${id}`
return this.storyblok
.delete(url, retrySettings.burst)
.then(() => Promise.resolve())
.catch(e => Promise.reject(e))
}
/**
* Delete a specific component.
*
* @name ApiClient#deleteComponent
* @param {number} id - Id of component to be deleted.
* @returns {Promise}
* @fulfil {IComponent} Details of the deleted component.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public deleteComponent(id: number): Promise<IComponent> {
const url = `/${this.spaceId}/components/${id}`
return this.storyblok
.delete(url, retrySettings.burst)
.then(r => r.data.component)
.catch(e => Promise.reject(e))
}
/**
* Delete a specific story.
*
* @name ApiClient#deleteStory
* @param {IStory} id - Id of the story to be deleted.
* @returns {Promise}
* @fulfil {IStory} Details of the story that was deleted.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public deleteStory(id: number): Promise<IStory> {
const url = `/${this.spaceId}/stories/${id}`
return this.storyblok
.delete(url, retrySettings.burst)
.then(r => r.data.story)
.catch(e => Promise.reject(e))
}
/**
* Delete all existing asset folders.
*
* @name ApiClient#deleteExistingAssetFolders
* @returns {Promise}
* @fulfil {void[]}
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public deleteExistingAssetFolders(): Promise<void[]> {
type M = (af: IAssetFolder) => Promise<void>
const mapFn: M = af => this.deleteAssetFolder(af.id as number)
return this.getExistingAssetFolders()
.then(afs => Promise.all(afs.map(mapFn)))
.catch(e => Promise.reject(e))
}
/**
* Delete all existing assets.
*
* @name ApiClient#deleteExistingAssets
* @returns {Promise}
* @fulfil {IAsset[]} Information on the deleted assets.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public deleteExistingAssets(): Promise<IAsset[]> {
type M = (a: IAsset) => Promise<IAsset>
const mapFn: M = a => this.deleteAsset(a.id as number)
return this.getExistingAssets()
.then(as => Promise.all(as.map(mapFn)))
.catch(e => Promise.reject(e))
}
/**
* Delete existing components.
*
* @name ApiClient#deleteExistingComponents
* @returns {Promise}
* @fulfil {IComponent[]} A list of deleted components details.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public deleteExistingComponents(): Promise<IComponent[]> {
type M = (c: IComponent) => Promise<IComponent>
const mapFn: M = c => this.deleteComponent(c.id as number)
return this.getExistingComponents()
.then(cs => Promise.all(cs.map(mapFn)))
.catch(e => Promise.reject(e))
}
/**
* Delete all existing stories.
*
* @name ApiClient#deleteExistingStories
* @returns {Promise}
* @fulfil {IStory[]} A list of deleted stories details.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public async deleteExistingStories(): Promise<IStory[]> {
try {
const existing: IStory[] = await this.getExistingStories()
type F = (s: IStory) => boolean
const filterFn: F = s => (s.parent_id as number) === 0
const root: IStory[] = existing.filter(filterFn)
type M = (s: IStory) => Promise<IStory>
const mapFn: M = s => this.deleteStory(s.id as number)
const deleted: IStory[] = await Promise.all(root.map(mapFn))
const remainder: IStory[] = await this.getExistingStories()
return deleted.concat(await Promise.all(remainder.map(mapFn)))
} catch (e) {
throw e
}
}
/**
* Get a specific asset.
*
* @name ApiClient#getAsset
* @param {number} id - Id of asset to fetch.
* @returns {Promise}
* @fulfil {IAsset} Details of the asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getAsset(id: number): Promise<IAsset> {
const url = `/${this.spaceId}/assets/${id}`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data)
.catch(e => Promise.reject(e))
}
/**
* Find a specific asset by its public url.
*
* @name ApiClient#getAssetByUrl
* @param {string} url - Url to match by.
* @returns {Promise}
* @fulfil {IAsset} Matched asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getAssetByUrl(url: string): Promise<IAsset> {
type RH = (as: IAsset[]) => IAsset
const responseHandler: RH = as => {
type F = (a: IAsset) => boolean
const findFn: F = a => a.filename === url
const asset = as.find(findFn)
if (!asset) {
throw new Error('unable to find an asset by this url')
} else {
return asset
}
}
return this.getExistingAssets()
.then(responseHandler)
.catch(e => Promise.reject(e))
}
/**
* Get a specific asset folder.
*
* @name ApiClient#getAssetFolder
* @param {number} id - Id of the target asset folder.
* @returns {Promise}
* @fulfil {IAssetFolder} Asset folder information.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getAssetFolder(id: number): Promise<IAssetFolder> {
const url = `/${this.spaceId}/asset_folders/${id}`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data.asset_folder)
.catch(e => Promise.reject(e))
}
/**
* Get asset folders by matching asset folders names to the supplied string.
*
* @name ApiClient#getAssetFolderByName
* @param {string} searchString - String to search by.
* @returns {Promise}
* @fulfil {IAssetFolder[]} List of matched asset folders.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getAssetFolderByName(searchString: string): Promise<IAssetFolder[]> {
const url = `/${this.spaceId}/asset_folders`
const query = `?search=${searchString}`
return this.storyblok
.get(url + query, retrySettings.burst)
.then(r => r.data.asset_folders)
.catch(e => Promise.reject(e))
}
/**
* Get asset on a specific pagination page number.
*
* @param {number} [page=1] - Pagination page.
* @param {number} [perPage=25] - Assets per page.
* @returns {Promise<IAsset[]>}
* @fulfil {IAsset[]} Assets on the pagination page.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getAssetsByPage(
page: number = 1,
perPage: number = 25
): Promise<IAsset[]> {
const url = `/${this.spaceId}/assets`
const query = `?per_page=${perPage}&page=${page}`
return this.storyblok
.get(url + query, retrySettings.burst)
.then(r => r.data.assets)
.catch(e => Promise.reject(e))
}
/**
* Fetch for a specific component.
*
* @name ApiClient#getComponent
* @param {number} id - Component id to fetch by.
* @returns {Promise}
* @fulfil {IComponent} Details of the component definition.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getComponent(id: number): Promise<IComponent> {
const url = `/${this.spaceId}/components/${id}`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data.component)
.catch(e => Promise.reject(e))
}
/**
* List all existing assets.
*
* @name ApiClient#getExistingAssets
* @returns {Promise}
* @fulfil {IAsset[]} A list of existing assets.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public async getExistingAssets(): Promise<IAsset[]> {
try {
const perPage = 1000
const total = await this.countAssets()
const pageCount = Math.ceil(total / perPage)
if (pageCount === 0) {
return []
}
const pageIndices = Array.from(Array(pageCount).keys())
type M = (pi: number) => Promise<IAsset[]>
const mapFn: M = pi => this.getAssetsByPage(pi + 1, perPage)
const arrayOfAssets = await Promise.all(pageIndices.map(mapFn))
return [].concat(...(arrayOfAssets as any[]))
} catch (e) {
throw e
}
}
/**
* List existing components.
*
* @name ApiClient#getExistingComponents
* @returns {Promise}
* @fulfil {IComponent[]} A list of component definitions.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getExistingComponents(): Promise<IComponent[]> {
const url = `/${this.spaceId}/components`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data.components)
.catch(e => Promise.reject(e))
}
/**
* Get existing asset folders.
*
* @name ApiClient#getExistingAssetFolders
* @returns {Promise}
* @fulfil {IAssetFolder[]} List of existing asset folders.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getExistingAssetFolders(): Promise<IAssetFolder[]> {
const url = `/${this.spaceId}/asset_folders?search`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data.asset_folders)
.catch(e => Promise.reject(e))
}
/**
* List all existing stories.
*
* @name ApiClient#getExistingStories
* @returns {Promise}
* @fulfil {IStory[]} A list of existing content stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public async getExistingStories(): Promise<IStory[]> {
try {
const perPage = 1000
const pageCount = await this.countStoryPages(perPage)
if (pageCount === 0) {
return []
}
const pageIndices = Array.from(Array(pageCount).keys())
type M = (pi: number) => Promise<IStory[]>
const mapFn: M = pi => this.getStoriesByPage(pi + 1, perPage)
const arrayOfStories = await Promise.all(pageIndices.map(mapFn))
return [].concat(...(arrayOfStories as any[]))
} catch (e) {
throw e
}
}
/**
* Get information on the working Storyblok space.
*
* @name ApiClient#getSpace
* @returns {Promise}
* @fulfil {ISpace} Working space information.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getSpace(): Promise<ISpace> {
const url = `/${this.spaceId}`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data.space)
.catch(e => Promise.reject(e))
}
/**
* Get stories on a pagination page.
*
* @name ApiClient#getStoriesByPage
* @param {number} page - Pagination page number.
* @param {number} [perPage] - (optional) How many stories per page. Defaults to 25.
* @returns {Promise}
* @fulfil {IStory[]} A page of stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public getStoriesByPage(
page: number = 1,
perPage: number = 25
): Promise<IStory[]> {
const url = `/${this.spaceId}/stories`
const query = `?per_page=${perPage}&page=${page}`
return this.storyblok
.get(url + query, retrySettings.burst)
.then(r => r.data.stories)
.catch(e => Promise.reject(e))
}
public getStory(id: number): Promise<IStory> {
const url = `/${this.spaceId}/stories/${id}`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data.story)
.catch(e => Promise.reject(e))
}
/**
* Publish all unpublished stories.
*
* @name ApiClient#stories#publishPendings
* @returns {Promise}
* @fulfil {IStory[]} List of published stories.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public async publishPendingStories(): Promise<IStory[]> {
try {
const existing = await this.getExistingStories()
type F = (s: IStory) => boolean
const filterFn: F = s => !s.is_folder && !s.published
const pendings = existing.filter(filterFn)
type M = (s: IStory) => Promise<IStory>
const mapFn: M = s => this.publishStory(s.id as number)
return await Promise.all(pendings.map(mapFn))
} catch (e) {
throw e
}
}
/**
* Publish a specific story.
*
* @name ApiClient#publishStory
* @param {number} id - Id of the story to publish
* @returns {Promise}
* @fulfil {IStory} Details of the published story
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public publishStory(id: number): Promise<IStory> {
const url = `/${this.spaceId}/stories/${id}/publish`
return this.storyblok
.get(url, retrySettings.burst)
.then(r => r.data.story)
.catch(e => Promise.reject(e))
}
/**
* Register a Storyblok asset.
*
* @name ApiClient#registerAsset
* @param {IPendingAsset} asset - Information to create asset from.
* @param {string} asset.filename - File name to register for.
* @param {number} [asset.asset_folder_id] - (optional) Assign a asset folder.
* @param {number} [asset.id] - (optional) Id of existing asset to replace with this new asset.
* @returns {Promise}
* @fulfil {IRegistration} Asset registration info (used for uploading).
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public registerAsset(data: IPendingAsset): Promise<IRegistration> {
const url = `/${this.spaceId}/assets`
return this.storyblok
.post(url, data, retrySettings.extended)
.then(r => r.data)
.catch(e => Promise.reject(e))
}
/**
* Update a story's sequential order.
*
* @name ApiClient#reorderStory
* @param {number} id - Id of the story to be moved.
* @param {number} afterId - Reference story to position after.
* @returns {Promise}
* @fulfil {IStory} Details of the moved story.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public reorderStory(id: number, afterId: number): Promise<IStory> {
const url = `/${this.spaceId}/stories/${id}/move`
const query = `?after_id=${afterId}`
return this.storyblok
.put(url + query, retrySettings.burst)
.then(() => this.getStory(id))
.catch(e => Promise.reject(e))
}
/**
* Update a component.
*
* @name ApiClient#updateComponent
* @param {IComponent} data - Storyblok component data object with modified info.
* @returns {Promise}
* @fulfil {IComponent} Details of component that was updated.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public updateComponent(data: IComponent): Promise<IComponent> {
const url = `/${this.spaceId}/components/${data.id}`
return this.storyblok
.put(url, {component: data}, retrySettings.burst)
.then(r => r.data.component)
.catch(e => Promise.reject(e))
}
/**
* Update a story.
*
* @name ApiClient#updateStory
* @param {IStory} data - Storyblok story data object with modified info.
* @returns {Promise}
* @fulfil {IStory} Details of story that was updated.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public updateStory(data: IStory): Promise<IStory> {
const url = `/${this.spaceId}/stories/${data.id}`
return this.storyblok
.put(url, {story: data}, retrySettings.burst)
.then(r => r.data.story)
.catch(e => Promise.reject(e))
}
/**
* Upload a registered asset with failure-retry (20 retries and incremental delay period of 1250ms with +/- 500ms variance).
*
* @name ApiClient#uploadAsset
* @param {Buffer} buffer - Buffered asset data.
* @param {IRegistration} registration - Registration info.
* @returns {Promise}
* @fulfil {string} Access url of the uploaded asset.
* @reject {AxiosError} Axios error.
* @memberof ApiClient
*/
public uploadAsset(
buffer: Buffer,
registration: IRegistration
): Promise<string> {
const formData = new FormData()
const formFields = registration.fields
for (const key in formFields) {
if (key in formFields) {
formData.append(key, formFields[key])
}
}
formData.append('file', buffer)
return new Promise((resolve, reject) => {
const variance = () => Math.floor(Math.random() * 500) + 1
const retries = 20
const retryDelay = 1250
let retryCount = 0
const callback = (e: any): any => {
if (!e || retryCount > retries) {
return e ? reject(e) : resolve(registration.pretty_url)
} else {
retryCount += 1
return new Promise(r => {
setTimeout(() => {
console.log(`retry no. ${retryCount}`)
console.log(`asset: ${registration.public_url}`)
console.log(`post - ${registration.post_url}`)
console.log(e)
return r()
}, (retryDelay - variance()) * retryCount)
}).then(() => formData.submit(registration.post_url, callback))
}
}
formData.submit(registration.post_url, callback)
})
}
}