text: const fs = require('fs-extra');
  const os = require('os');
  const path = require('path');
  const security = require('@tryghost/security');
  const events = require('../../lib/common/events');
  const themeService = require('../../services/themes');
  const limitService = require('../../services/limits');
  const models = require('../../models');
  const request = require('@tryghost/request');
  const errors = require('@tryghost/errors/lib/errors');
  const i18n = require('../../../shared/i18n');
  
  module.exports = {
      docName: 'themes',
  
      browse: {
          permissions: true,
          query() {
              return themeService.getJSON();
          }
      },
  
      activate: {
          headers: {
              cacheInvalidate: true
          },
          options: [
              'name'
          ],
          validation: {
              options: {
                  name: {
                      required: true
                  }
              }
          },
          permissions: true,
          async query(frame) {
              let themeName = frame.options.name;
  
              if (limitService.isLimited('customThemes')) {
                  await limitService.errorIfWouldGoOverLimit('customThemes', {value: themeName});
              }
  
              const newSettings = [{
                  key: 'active_theme',
                  value: themeName
              }];
  
              return themeService.activate(themeName)
                  .then((checkedTheme) => {
                      // @NOTE: we use the model, not the API here, as we don't want to trigger permissions
                      return models.Settings.edit(newSettings, frame.options)
                          .then(() => checkedTheme);
                  })
                  .then((checkedTheme) => {
                      return themeService.getJSON(themeName, checkedTheme);
                  });
          }
      },
  
      install: {
          headers: {},
          options: [
              'source',
              'ref'
          ],
          validation: {
              options: {
                  source: {
                      required: true,
                      values: ['github']
                  },
                  ref: {
                      required: true
                  }
              }
          },
          permissions: {
              method: 'add'
          },
          async query(frame) {
              if (frame.options.source === 'github') {
                  const [org, repo] = frame.options.ref.toLowerCase().split('/');
  
                  //TODO: move the organization check to config
                  if (limitService.isLimited('customThemes') && org.toLowerCase() !== 'tryghost') {
                      await limitService.errorIfWouldGoOverLimit('customThemes', {value: repo.toLowerCase()});
                  }
  
                  // omit /:ref so we fetch the default branch
                  const zipUrl = `https://api.github.com/repos/${org}/${repo}/zipball`;
                  const zipName = `${repo}.zip`;
  
                  // store zip in a unique temporary folder to avoid conflicts
                  const downloadBase = path.join(os.tmpdir(), security.identifier.uid(10));
                  const downloadPath = path.join(downloadBase, zipName);
  
                  await fs.ensureDir(downloadBase);
  
                  try {
                      // download zip file
                      const response = await request(zipUrl, {
                          followRedirect: true,
                          headers: {
                              accept: 'application/vnd.github.v3+json'
                          },
                          encoding: null
                      });
  
                      await fs.writeFile(downloadPath, response.body);
  
                      // install theme from zip
                      const zip = {
                          path: downloadPath,
                          name: zipName
                      };
                      const {theme, themeOverridden} = await themeService.storage.setFromZip(zip);
  
                      if (themeOverridden) {
                          this.headers.cacheInvalidate = true;
                      }
  
                      events.emit('theme.uploaded', {name: theme.name});
  
                      return theme;
                  } catch (e) {
                      if (e.statusCode && e.statusCode === 404) {
                          return Promise.reject(new errors.BadRequestError({
                              message: i18n.t('errors.api.themes.repoDoesNotExist'),
                              context: zipUrl
                          }));
                      }
  
                      throw e;
                  } finally {
                      // clean up tmp dir with downloaded file
                      fs.remove(downloadBase);
                  }
              }
          }
      },
  
      upload: {
          headers: {},
          permissions: {
              method: 'add'
          },
          async query(frame) {
              if (limitService.isLimited('customThemes')) {
                  // Sending a bad string to make sure it fails (empty string isn't valid)
                  await limitService.errorIfWouldGoOverLimit('customThemes', {value: '.'});
              }
  
              // @NOTE: consistent filename uploads
              frame.options.originalname = frame.file.originalname.toLowerCase();
  
              let zip = {
                  path: frame.file.path,
                  name: frame.file.originalname
              };
  
              return themeService.storage.setFromZip(zip)
                  .then(({theme, themeOverridden}) => {
                      if (themeOverridden) {
                          // CASE: clear cache
                          this.headers.cacheInvalidate = true;
                      }
                      events.emit('theme.uploaded', {name: theme.name});
                      return theme;
                  });
          }
      },
  
      download: {
          options: [
              'name'
          ],
          validation: {
              options: {
                  name: {
                      required: true
                  }
              }
          },
          permissions: {
              method: 'read'
          },
          query(frame) {
              let themeName = frame.options.name;
  
              return themeService.storage.getZip(themeName);
          }
      },
  
      destroy: {
          statusCode: 204,
          headers: {
              cacheInvalidate: true
          },
          options: [
              'name'
          ],
          validation: {
              options: {
                  name: {
                      required: true
                  }
              }
          },
          permissions: true,
          query(frame) {
              let themeName = frame.options.name;
  
              return themeService.storage.destroy(themeName);
          }
      }
  };
  