UNPKG

storyblok-ts-client

Version:

Typescript library for working with Storyblok management API.

1,282 lines (1,235 loc) 41.2 kB
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) }) } }