create-environment-api.js

/**
 * Contentful Environment API. Contains methods to access any operations at a space
 * level, such as creating and reading entities contained in a space.
 * @namespace ContentfulEnvironmentAPI
 */
import cloneDeep from 'lodash/cloneDeep'
import { createRequestConfig } from 'contentful-sdk-core'
import errorHandler from './error-handler'
import entities from './entities'

/**
 * @memberof ContentfulEnvironmentAPI
 * @typedef {object} ContentfulEnvironmentAPI
 * @prop {function} delete
 * @prop {function} update
 * @prop {function} getContentType
 * @prop {function} getContentTypes
 * @prop {function} createContentType
 * @prop {function} createContentTypeWithId
 * @prop {function} getEntry
 * @prop {function} getEntries
 * @prop {function} createEntry
 * @prop {function} createEntryWithId
 * @prop {function} getAsset
 * @prop {function} getAssets
 * @prop {function} createAsset
 * @prop {function} createAssetWithId
 * @prop {function} getLocale
 * @prop {function} getLocales
 * @prop {function} createLocale
 * @prop {function} getUiExtension
 * @prop {function} getUiExtensions
 * @prop {function} createUiExtension
 * @prop {function} createUiExtensionWithId
 * @prop {function} getAppInstallation
 * @prop {function} getAppInstallations
 * @prop {function} createAppInstallation
 * @prop {function} getEntrySnapshots
 * @prop {function} getContentTypeSnapshots
 */

/**
 * Creates API object with methods to access the Environment API
 * @private
 * @param {object} params - API initialization params
 * @prop {object} http - HTTP client instance
 * @prop {object} entities - Object with wrapper methods for each kind of entity
 * @return {ContentfulEnvironmentAPI}
 */
export default function createEnvironmentApi ({
  http,
  httpUpload
}) {
  const {wrapEnvironment} = entities.environment
  const {wrapContentType, wrapContentTypeCollection} = entities.contentType
  const {wrapEntry, wrapEntryCollection} = entities.entry
  const {wrapAsset, wrapAssetCollection} = entities.asset
  const {wrapLocale, wrapLocaleCollection} = entities.locale
  const {wrapSnapshotCollection} = entities.snapshot
  const {wrapEditorInterface} = entities.editorInterface
  const {wrapUpload} = entities.upload
  const {wrapUiExtension, wrapUiExtensionCollection} = entities.uiExtension
  const {wrapAppInstallation, wrapAppInstallationCollection} = entities.appInstallation
  /**
   * Environment instances.
   * @namespace Environment
   */

  /**
   * Deletes the environment
   * @memberof Environment
   * @func delete
   * @return {Promise} Promise for the deletion. It contains no data, but the Promise error case should be handled.
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.delete())
   * .then(() => console.log('Environment deleted.'))
   * .catch(console.error)
  */
  function deleteEnvironment () {
    return http.delete('')
      .then((response) => {}, errorHandler)
  }

  /**
   * Updates the environment
   * @memberof Environment
   * @func update
   * @return {Promise<Environment.Environment>} Promise for the updated environment.
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => {
   *   environment.name = 'New name'
   *   return environment.update()
   * })
   * .then((environment) => console.log(`Environment ${environment.sys.id} renamed.`)
   * .catch(console.error)
  */
  function updateEnvironment () {
    const raw = this.toPlainObject()
    const data = cloneDeep(raw)
    delete data.sys
    return http.put('', data, {
      headers: {
        'X-Contentful-Version': raw.sys.version
      }
    })
      .then((response) => wrapEnvironment(http, response.data), errorHandler)
  }

  /**
   * Gets a Content Type
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - Content Type ID
   * @return {Promise<ContentType.ContentType>} Promise for a Content Type
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getContentType('<content_type_id>'))
   * .then((contentType) => console.log(contentType))
   * .catch(console.error)
   */
  function getContentType (id) {
    return http.get('content_types/' + id)
      .then((response) => wrapContentType(http, response.data), errorHandler)
  }

  /**
   * Gets an EditorInterface for a ContentType
   * @memberof ContentfulEnvironmentAPI
   * @param {string} contentTypeId - Content Type ID
   * @return {Promise<EditorInterface.EditorInterface>} Promise for an EditorInterface
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getEditorInterfaceForContentType('<content_type_id>'))
   * .then((EditorInterface) => console.log(EditorInterface))
   * .catch(console.error)
   */
  function getEditorInterfaceForContentType (contentTypeId) {
    return http.get('content_types/' + contentTypeId + '/editor_interface')
      .then((response) => wrapEditorInterface(http, response.data), errorHandler)
  }

  /**
   * Gets a collection of Content Types
   * @memberof ContentfulEnvironmentAPI
   * @param {object=} query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details.
   * @return {Promise<ContentType.ContentTypeCollection>} Promise for a collection of Content Types
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getContentTypes())
   * .then((response) => console.log(response.items))
   * .catch(console.error)
  */
  function getContentTypes (query = {}) {
    return http.get('content_types', createRequestConfig({query: query}))
      .then((response) => wrapContentTypeCollection(http, response.data), errorHandler)
  }

  /**
   * Creates a Content Type
   * @memberof ContentfulEnvironmentAPI
   * @see {ContentType}
   * @param {object} data - Object representation of the Content Type to be created
   * @return {Promise<ContentType.ContentType>} Promise for the newly created Content Type
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createContentType({
   *   name: 'Blog Post',
   *   fields: [
   *     {
   *       id: 'title',
   *       name: 'Title',
   *       required: true,
   *       localized: false,
   *       type: 'Text'
   *     }
   *   ]
   * }))
   * .then((contentType) => console.log(contentType))
   * .catch(console.error)
   */
  function createContentType (data) {
    return http.post('content_types', data)
      .then((response) => wrapContentType(http, response.data), errorHandler)
  }

  /**
   * Creates a Content Type with a custom ID
   * @memberof ContentfulEnvironmentAPI
   * @see {ContentType.ContentType}
   * @param {string} id - Content Type ID
   * @param {object} data - Object representation of the Content Type to be created
   * @return {Promise<ContentType.ContentType>} Promise for the newly created Content Type
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createContentTypeWithId('<content-type-id>', {
   *   name: 'Blog Post',
   *   fields: [
   *     {
   *       id: 'title',
   *       name: 'Title',
   *       required: true,
   *       localized: false,
   *       type: 'Text'
   *     }
   *   ]
   * }))
   * .then((contentType) => console.log(contentType))
   * .catch(console.error)
   */
  function createContentTypeWithId (id, data) {
    return http.put('content_types/' + id, data)
      .then((response) => wrapContentType(http, response.data), errorHandler)
  }

  /**
   * Gets an Entry
   * Warning: if you are using the select operator, when saving, any field that was not selected will be removed
   * from your entry in the backend
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - Entry ID
   * @param {object=} query - Object with search parameters. In this method it's only useful for `locale`.
   * @return {Promise<Entry.Entry>} Promise for an Entry
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getEntry('<entry-id>'))
   * .then((entry) => console.log(entry))
   * .catch(console.error)
   */
  function getEntry (id, query = {}) {
    normalizeSelect(query)
    return http.get('entries/' + id, createRequestConfig({query: query}))
      .then((response) => wrapEntry(http, response.data), errorHandler)
  }

  /**
   * Gets a collection of Entries
   * Warning: if you are using the select operator, when saving, any field that was not selected will be removed
   * from your entry in the backend
   * @memberof ContentfulEnvironmentAPI
   * @param {object=} query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details.
   * @return {Promise<Entry.EntryCollection>} Promise for a collection of Entries
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getEntries({'content_type': 'foo'})) // you can add more queries as 'key': 'value'
   * .then((response) => console.log(response.items))
   * .catch(console.error)
   */
  function getEntries (query = {}) {
    normalizeSelect(query)
    return http.get('entries', createRequestConfig({query: query}))
      .then((response) => wrapEntryCollection(http, response.data), errorHandler)
  }

  /**
   * Creates a Entry
   * @memberof ContentfulEnvironmentAPI
   * @see {Entry.Entry}
   * @param {string} contentTypeId - The Content Type ID of the newly created Entry
   * @param {object} data - Object representation of the Entry to be created
   * @return {Promise<Entry.Entry>} Promise for the newly created Entry
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createEntry('<content_type_id>', {
   *   fields: {
   *     title: {
   *       'en-US': 'Entry title'
   *     }
   *   }
   * }))
   * .then((entry) => console.log(entry))
   * .catch(console.error)
   */
  function createEntry (contentTypeId, data) {
    return http.post('entries', data, {
      headers: {
        'X-Contentful-Content-Type': contentTypeId
      }
    })
      .then((response) => wrapEntry(http, response.data), errorHandler)
  }

  /**
   * Creates a Entry with a custom ID
   * @memberof ContentfulEnvironmentAPI
   * @see {Entry.Entry}
   * @param {string} contentTypeId - The Content Type of the newly created Entry
   * @param {string} id - Entry ID
   * @param {object} data - Object representation of the Entry to be created
   * @return {Promise<Entry.Entry>} Promise for the newly created Entry
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * // Create entry
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createEntryWithId('<content_type_id>', '<entry_id>', {
   *   fields: {
   *     title: {
   *       'en-US': 'Entry title'
   *     }
   *   }
   * }))
   * .then((entry) => console.log(entry))
   * .catch(console.error)
   */
  function createEntryWithId (contentTypeId, id, data) {
    return http.put('entries/' + id, data, {
      headers: {
        'X-Contentful-Content-Type': contentTypeId
      }
    })
      .then((response) => wrapEntry(http, response.data), errorHandler)
  }

  /**
   * Gets an Asset
   * Warning: if you are using the select operator, when saving, any field that was not selected will be removed
   * from your entry in the backend
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - Asset ID
   * @param {object=} query - Object with search parameters. In this method it's only useful for `locale`.
   * @return {Promise<Asset.Asset>} Promise for an Asset
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getAsset('<asset_id>'))
   * .then((asset) => console.log(asset))
   * .catch(console.error)
  */
  function getAsset (id, query = {}) {
    normalizeSelect(query)
    return http.get('assets/' + id, createRequestConfig({query: query}))
      .then((response) => wrapAsset(http, response.data), errorHandler)
  }

  /**
   * Gets a collection of Assets
   * Warning: if you are using the select operator, when saving, any field that was not selected will be removed
   * from your entry in the backend
   * @memberof ContentfulEnvironmentAPI
   * @param {object=} query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details.
   * @return {Promise<Asset.AssetCollection>} Promise for a collection of Assets
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getAssets())
   * .then((response) => console.log(response.items))
   * .catch(console.error)
  */
  function getAssets (query = {}) {
    normalizeSelect(query)
    return http.get('assets', createRequestConfig({query: query}))
      .then((response) => wrapAssetCollection(http, response.data), errorHandler)
  }

  /**
   * Creates a Asset. After creation, call asset.processForLocale or asset.processForAllLocales to start asset processing.
   * @memberof ContentfulEnvironmentAPI
   * @see {Asset.Asset}
   * @param {object} data - Object representation of the Asset to be created. Note that the field object should have an upload property on asset creation, which will be removed and replaced with an url property when processing is finished.
   * @return {Promise<Asset.Asset>} Promise for the newly created Asset
   * @example
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * // Create asset
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createAsset({
   *   fields: {
   *     title: {
   *       'en-US': 'Playsam Streamliner'
   *    },
   *    file: {
   *       'en-US': {
   *         contentType: 'image/jpeg',
   *        fileName: 'example.jpeg',
   *        upload: 'https://example.com/example.jpg'
   *      }
   *    }
   *   }
   * }))
   * .then((asset) => asset.processForLocale("en-US")) // OR asset.processForAllLocales()
   * .then((asset) => console.log(asset))
   * .catch(console.error)
   */
  function createAsset (data) {
    return http.post('assets', data)
      .then((response) => wrapAsset(http, response.data), errorHandler)
  }

  /**
   * Creates a Asset with a custom ID. After creation, call asset.processForLocale or asset.processForAllLocales to start asset processing.
   * @memberof ContentfulEnvironmentAPI
   * @see {Asset.Asset}
   * @param {string} id - Asset ID
   * @param {object} data - Object representation of the Asset to be created. Note that the field object should have an upload property on asset creation, which will be removed and replaced with an url property when processing is finished.
   * @return {Promise<Asset.Asset>} Promise for the newly created Asset
   * @example
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * // Create asset
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createAssetWithId('<asset_id>', {
   *   title: {
   *     'en-US': 'Playsam Streamliner'
   *   },
   *   file: {
   *     'en-US': {
   *       contentType: 'image/jpeg',
   *       fileName: 'example.jpeg',
   *       upload: 'https://example.com/example.jpg'
   *     }
   *   }
   * }))
   * .then((asset) => asset.process())
   * .then((asset) => console.log(asset))
   * .catch(console.error)
   */
  function createAssetWithId (id, data) {
    return http.put('assets/' + id, data)
      .then((response) => wrapAsset(http, response.data), errorHandler)
  }

  /**
   * Creates a Asset based on files. After creation, call asset.processForLocale or asset.processForAllLocales to start asset processing.
   * @memberof ContentfulEnvironmentAPI
   * @see {Asset.Asset}
   * @param {object} data - Object representation of the Asset to be created. Note that the field object should have an uploadFrom property on asset creation, which will be removed and replaced with an url property when processing is finished.
   * @param {object} data.fields.file.[LOCALE].file - Can be a string, an ArrayBuffer or a Stream.
   * @return {Promise<Asset.Asset>} Promise for the newly created Asset
   * @example
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createAssetFromFiles({
   *   fields: {
   *     file: {
   *       'en-US': {
   *          contentType: 'image/jpeg',
   *          fileName: 'filename_english.jpg',
   *          file: createReadStream('path/to/filename_english.jpg')
   *       },
   *       'de-DE': {
   *          contentType: 'image/svg+xml',
   *          fileName: 'filename_german.svg',
   *          file: '<svg><path fill="red" d="M50 50h150v50H50z"/></svg>'
   *       }
   *     }
   *   }
   * }))
   * .then((asset) => console.log(asset))
   * .catch(console.error)
   */
  function createAssetFromFiles (data) {
    const { file } = data.fields
    return Promise.all(
      Object.keys(file).map((locale) => {
        const { contentType, fileName } = file[locale]
        return createUpload(file[locale])
          .then((upload) => {
            return {
              [locale]: {
                contentType,
                fileName,
                uploadFrom: {
                  sys: {
                    type: 'Link',
                    linkType: 'Upload',
                    id: upload.sys.id
                  }
                }
              }
            }
          })
      })
    )
      .then((uploads) => {
        data.fields.file = uploads.reduce((fieldsData, upload) => {
          return {
            ...fieldsData,
            ...upload
          }
        }, {})
        return createAsset(data)
      })
      .catch(errorHandler)
  }

  /**
   * Creates a Upload.
   * @memberof ContentfulEnvironmentAPI
   * @param {object} data - Object with file information.
   * @param {object} data.file - Actual file content. Can be a string, an ArrayBuffer or a Stream.
   * @return {Promise<Upload>} Upload object containing information about the uploaded file.
   * @example
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   * const uploadStream = createReadStream('path/to/filename_english.jpg')
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createUpload({file: uploadStream})
   * .then((upload) => console.log(upload))
   * .catch(console.error)
   */
  function createUpload (data) {
    const { file } = data
    if (!file) {
      return Promise.reject(new Error('Unable to locate a file to upload.'))
    }
    return httpUpload.post('uploads', file, {
      headers: {
        'Content-Type': 'application/octet-stream'
      }
    })
      .then(uploadResponse => {
        return wrapUpload(httpUpload, uploadResponse.data)
      })
      .catch(errorHandler)
  }

  /**
   * Gets an Upload
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - Upload ID
   * @return {Promise<Upload>} Promise for an Upload
   * @example
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   * const uploadStream = createReadStream('path/to/filename_english.jpg')
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getUpload('<upload-id>')
   * .then((upload) => console.log(upload))
   * .catch(console.error)
   */
  function getUpload (id) {
    return httpUpload.get('uploads/' + id)
      .then((response) => wrapUpload(http, response.data))
      .catch(errorHandler)
  }

  /**
   * Gets a Locale
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - Locale ID
   * @return {Promise<Locale.Locale>} Promise for an Locale
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getLocale('<locale_id>'))
   * .then((locale) => console.log(locale))
   * .catch(console.error)
  */
  function getLocale (id) {
    return http.get('locales/' + id)
      .then((response) => wrapLocale(http, response.data), errorHandler)
  }

  /**
   * Gets a collection of Locales
   * @memberof ContentfulEnvironmentAPI
   * @return {Promise<Locale.LocaleCollection>} Promise for a collection of Locales
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getLocales())
   * .then((response) => console.log(response.items))
   * .catch(console.error)
  */
  function getLocales () {
    return http.get('locales')
      .then((response) => wrapLocaleCollection(http, response.data), errorHandler)
  }

  /**
   * Creates a Locale
   * @memberof ContentfulEnvironmentAPI
   * @see {Locale.Locale}
   * @param {object} data - Object representation of the Locale to be created
   * @return {Promise<Locale.Locale>} Promise for the newly created Locale
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * // Create locale
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createLocale({
   *   name: 'German (Austria)',
   *   code: 'de-AT',
   *   fallbackCode: 'de-DE',
   *   optional: true
   * }))
   * .then((locale) => console.log(locale))
   * .catch(console.error)
   */
  function createLocale (data) {
    return http.post('locales', data)
      .then((response) => wrapLocale(http, response.data), errorHandler)
  }

  /**
   * Gets an UI Extension
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - Extension ID
   * @return {Promise<UiExtension.UiExtension>} Promise for an UI Extension
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getUiExtension('<extension-id>'))
   * .then((uiExtension) => console.log(uiExtension))
   * .catch(console.error)
   */
  function getUiExtension (id) {
    return http.get('extensions/' + id)
      .then((response) => wrapUiExtension(http, response.data), errorHandler)
  }

  /**
   * Gets a collection of UI Extension
   * @memberof ContentfulEnvironmentAPI
   * @return {Promise<UiExtension.UiExtensionCollection>} Promise for a collection of UI Extensions
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getUiExtensions()
   * .then((response) => console.log(response.items))
   * .catch(console.error)
   */
  function getUiExtensions () {
    return http.get('extensions')
      .then((response) => wrapUiExtensionCollection(http, response.data), errorHandler)
  }

  /**
   * Creates a UI Extension
   * @memberof ContentfulEnvironmentAPI
   * @see {UiExtension.UiExtension}
   * @param {object} data - Object representation of the UI Extension to be created
   * @return {Promise<UiExtension.UiExtension>} Promise for the newly created UI Extension
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createUiExtension({
   *   extension: {
   *     name: 'My awesome extension',
   *     src: 'https://example.com/my',
   *     fieldTypes: [
   *       {
   *         type: 'Symbol'
   *       },
   *       {
   *         type: 'Text'
   *       }
   *     ],
   *     sidebar: false
   *   }
   * }))
   * .then((uiExtension) => console.log(uiExtension))
   * .catch(console.error)
   */
  function createUiExtension (data) {
    return http.post('extensions', data)
      .then((response) => wrapUiExtension(http, response.data), errorHandler)
  }

  /**
   * Creates a UI Extension with a custom ID
   * @memberof ContentfulEnvironmentAPI
   * @see {UiExtension.UiExtension}
   * @param {string} id - Extension ID
   * @param {object} data - Object representation of the UI Extension to be created
   * @return {Promise<UiExtension.UiExtension>} Promise for the newly created UI Extension
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.createUiExtensionWithId('<extension_id>', {
   *   extension: {
   *     name: 'My awesome extension',
   *     src: 'https://example.com/my',
   *     fieldTypes: [
   *       {
   *         type: 'Symbol'
   *       },
   *       {
   *         type: 'Text'
   *       }
   *     ],
   *     sidebar: false
   *   }
   * }))
   * .then((uiExtension) => console.log(uiExtension))
   * .catch(console.error)
   */
  function createUiExtensionWithId (id, data) {
    return http.put('extensions/' + id, data)
      .then((response) => wrapUiExtension(http, response.data), errorHandler)
  }

  /**
   * Gets an App Installation
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - AppDefinition ID
   * @return {Promise<AppInstallation.AppInstallation>} Promise for an App Installation
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   *  .then((space) => space.getEnvironment('<environment-id>'))
   *  .then((environment) => environment.createAppInstallation('<app_definition_id>', {
   *    parameters: {
   *      someParameter: someValue
   *    }
   *   })
   *  .then((appInstallation) => console.log(appInstallation))
   *  .catch(console.error)
   */
  function createAppInstallation (appDefinitionId, parameters) {
    return http.put('app_installations/' + appDefinitionId, parameters)
      .then((response) => wrapAppInstallation(http, response.data), errorHandler)
  }

  /**
   * Gets an App Installation
   * @memberof ContentfulEnvironmentAPI
   * @param {string} id - AppDefintion ID
   * @return {Promise<AppInstallation.AppInstallation>} Promise for an App Installation
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   *  .then((space) => space.getEnvironment('<environment-id>'))
   *  .then((environment) => environment.getAppInstallation('<app-definition-id>'))
   *  .then((appInstallation) => console.log(appInstallation))
   *  .catch(console.error)
   */

  function getAppInstallation (id) {
    return http.get('app_installations/' + id)
      .then((response) => wrapAppInstallation(http, response.data), errorHandler)
  }

  /**
   * Gets a collection of App Installation
   * @memberof ContentfulEnvironmentAPI
   * @return {Promise<AppInstallation.AppInstallationCollection>} Promise for a collection of App Installations
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   *  .then((space) => space.getEnvironment('<environment-id>'))
   *  .then((environment) => environment.getAppInstallations()
   *  .then((response) => console.log(response.items))
   *  .catch(console.error)
   */
  function getAppInstallations () {
    return http.get('app_installations')
      .then((response) => wrapAppInstallationCollection(http, response.data), errorHandler)
  }

  /**
   * Gets all snapshots of an entry
   * @memberof ContentfulEnvironmentAPI
   * @func getEntrySnapshots
   * @param {string} entryId - Entry ID
   * @param {object=} query - query additional query paramaters
   * @param {number=} query.skip - optional, number of items to skip
   * @param {number=} query.limit - optional, limit total number of snapshots returned
   * @param
   * @return {Promise<Snapshot.SnapshotCollection>} Promise for a collection of Entry Snapshots
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getEntrySnapshots('<entry_id>'))
   * .then((snapshots) => console.log(snapshots.items))
   * .catch(console.error)
   */
  function getEntrySnapshots (entryId, query = {}) {
    return http.get(`entries/${entryId}/snapshots`, createRequestConfig({query: query}))
      .then((response) => wrapSnapshotCollection(http, response.data), errorHandler)
  }

  /**
   * Gets all snapshots of a contentType
   * @memberof ContentfulEnvironmentAPI
   * @func getContentTypeSnapshots
   * @param {string} contentTypeId - Content Type ID
   * @param {object=} query - query additional query paramaters
   * @param {number=} query.skip - optional, number of items to skip
   * @param {number=} query.limit - optional, limit total number of snapshots returned
   * @return {Promise<Snapshot.SnapshotCollection>} Promise for a collection of Content Type Snapshots
   * @example
   * const contentful = require('contentful-management')
   *
   * const client = contentful.createClient({
   *   accessToken: '<content_management_api_key>'
   * })
   *
   * client.getSpace('<space_id>')
   * .then((space) => space.getEnvironment('<environment-id>'))
   * .then((environment) => environment.getContentTypeSnapshots('<contentTypeId>'))
   * .then((snapshots) => console.log(snapshots.items))
   * .catch(console.error)
   */
  function getContentTypeSnapshots (contentTypeId, query = {}) {
    return http.get(`content_types/${contentTypeId}/snapshots`, createRequestConfig({query: query}))
      .then((response) => wrapSnapshotCollection(http, response.data), errorHandler)
  }

  /*
   * @private
   * sdk relies heavily on sys metadata
   * so we cannot omit the sys property on sdk level
   *
   */
  function normalizeSelect (query) {
    if (query.select && !/sys/i.test(query.select)) {
      query.select += ',sys'
    }
  }

  /**
   * Creates SDK Entry object (locally) from entry data
   * @memberof ContentfulEnvironmentAPI
   * @param {object} entryData - Entry Data
   * @return {Entry.Entry} Entry
   * @example
   * environment.getEntry('entryId').then(entry => {
   *
   *   // Build a plainObject in order to make it usable for React (saving in state or redux)
   *   const plainObject = entry.toPlainObject();
   *
   *   // The entry is being updated in some way as plainObject:
   *   const updatedPlainObject = {
   *     ...plainObject,
   *     fields: {
   *       ...plainObject.fields,
   *       title: {
   *         'en-US': 'updatedTitle'
   *       }
   *     }
   *   };
   *
   *   // Rebuild an sdk object out of the updated plainObject:
   *   const entryWithMethodsAgain = environment.getEntryFromData(updatedPlainObject);
   *
   *   // Update with help of the sdk method:
   *   entryWithMethodsAgain.update();
   *
   * });
   **/
  function getEntryFromData (entryData) {
    return wrapEntry(http, entryData)
  }

  /**
   * Creates SDK Asset object (locally) from entry data
   * @memberof ContentfulEnvironmentAPI
   * @param {object} entryData - Asset ID
   * @return {Asset.Asset} Asset
   * @example
   * environment.getAsset('asset_id').then(asset => {
   *
   *   // Build a plainObject in order to make it usable for React (saving in state or redux)
   *   const plainObject = asset.toPlainObject();
   *
   *   // The asset is being updated in some way as plainObject:
   *   const updatedPlainObject = {
   *     ...plainObject,
   *     fields: {
   *       ...plainObject.fields,
   *       title: {
   *         'en-US': 'updatedTitle'
   *       }
   *     }
   *   };
   *
   *   // Rebuild an sdk object out of the updated plainObject:
   *   const assetWithMethodsAgain = environment.getAssetFromData(updatedPlainObject);
   *
   *   // Update with help of the sdk method:
   *   assetWithMethodsAgain.update();
   *
   * });
  */

  function getAssetFromData (assetData) {
    return wrapAsset(http, assetData)
  }

  return {
    getEntryFromData,
    getAssetFromData,
    delete: deleteEnvironment,
    update: updateEnvironment,
    getContentType: getContentType,
    getContentTypes: getContentTypes,
    createContentType: createContentType,
    createContentTypeWithId: createContentTypeWithId,
    getEditorInterfaceForContentType: getEditorInterfaceForContentType,
    getEntry: getEntry,
    getEntries: getEntries,
    createEntry: createEntry,
    createEntryWithId: createEntryWithId,
    getAsset: getAsset,
    getAssets: getAssets,
    createAsset: createAsset,
    createAssetWithId: createAssetWithId,
    createAssetFromFiles: createAssetFromFiles,
    getUpload: getUpload,
    createUpload: createUpload,
    getLocale: getLocale,
    getLocales: getLocales,
    createLocale: createLocale,
    getUiExtension: getUiExtension,
    getUiExtensions: getUiExtensions,
    createUiExtension: createUiExtension,
    createUiExtensionWithId: createUiExtensionWithId,
    createAppInstallation: createAppInstallation,
    getAppInstallation: getAppInstallation,
    getAppInstallations: getAppInstallations,
    getEntrySnapshots: getEntrySnapshots,
    getContentTypeSnapshots: getContentTypeSnapshots
  }
}