Repository: rickbergfalk/sqlpad
Pull request #450 (2019-07-23 04:34:03)

Remove ui config and related config db
This removes the configuration UI along with the backing config db nedb store as proposed in #447. This allows the project to lose some of the unnecessary complication surrounding the configuration code, and opens things up for even simpler solutions to be introduced. 

Further cleanup surrounding the config util will follow.


Patch:
diff --git a/client/package-lock.json b/client/package-lock.json
index fe233001..abe139c4 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -10947,14 +10947,6 @@
         "tslib": "^1.0.0"
       }
     },
-    "react-switch": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/react-switch/-/react-switch-5.0.0.tgz",
-      "integrity": "sha512-+zxY9xj9dMc8Y4gv/kkqQrirfEiIQ+SlQfJDW1Wi81L3xoh1fcbBYyJyh0TnhM/U/b6HxuBmkmU4Ooxgtuoavw==",
-      "requires": {
-        "prop-types": "^15.6.2"
-      }
-    },
     "react-window": {
       "version": "1.8.3",
       "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.3.tgz",
diff --git a/client/package.json b/client/package.json
index 970f710b..c800a572 100644
--- a/client/package.json
+++ b/client/package.json
@@ -26,7 +26,6 @@
     "react-router-dom": "^5.0.1",
     "react-scripts": "^3.0.1",
     "react-split-pane": "^0.1.87",
-    "react-switch": "^5.0.0",
     "react-window": "^1.8.3",
     "taucharts": "^2.7.3",
     "unistore": "^3.4.1",
diff --git a/client/src/configuration/ConfigItemInput.js b/client/src/configuration/ConfigItemInput.js
deleted file mode 100644
index 05804807..00000000
--- a/client/src/configuration/ConfigItemInput.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import React from 'react';
-import Switch from 'react-switch';
-import FormExplain from '../common/FormExplain';
-import HorizontalFormItem from '../common/HorizontalFormItem';
-import Input from '../common/Input';
-import Select from '../common/Select';
-
-function configIsBoolean(config) {
-  const { options } = config;
-  return (
-    typeof config.effectiveValue === 'boolean' &&
-    options &&
-    options.length === 2 &&
-    options.includes(true) &&
-    options.includes(false)
-  );
-}
-
-function ConfigItemInput({ config, onChange }) {
-  const handleChange = e => {
-    onChange(config.key, e.target.value);
-  };
-
-  const handleSelectChange = value => {
-    onChange(config.key, value);
-  };
-
-  const disabled =
-    config.effectiveValueSource === 'cli' ||
-    config.effectiveValueSource === 'saved cli' ||
-    config.effectiveValueSource === 'env';
-
-  const effectiveValueSourceLabels = {
-    cli: 'Command Line',
-    'saved cli': 'Saved Command Line',
-    env: 'Environment Varialbe'
-  };
-  const overriddenBy = effectiveValueSourceLabels[config.effectiveValueSource];
-
-  const defaultValue =
-    config.default === '' ? (
-      <em>empty</em>
-    ) : (
-      <span>{config.default.toString()}</span>
-    );
-
-  const popoverContent = (
-    <div style={{ maxWidth: 300 }}>
-      <p>{config.description}</p>
-      <p>
-        <span>Default:</span> {defaultValue}
-      </p>
-      {disabled && (
-        <>
-          <p>
-            <span>Set By:</span> {overriddenBy}
-          </p>
-          <p>
-            When set by command line or environment, item is not configurable
-            via UI.
-          </p>
-        </>
-      )}
-    </div>
-  );
-
-  let input;
-  if (configIsBoolean(config)) {
-    input = (
-      <Switch
-        id={config.key}
-        height={24}
-        width={48}
-        onColor={'#1890ff'}
-        checked={config.effectiveValue}
-        onChange={value => onChange(config.key, value)}
-      />
-    );
-  } else if (config.options) {
-    const optionNodes = config.options.map(option => {
-      return (
-        <option key={option} value={option}>
-          {option.toString()}
-        </option>
-      );
-    });
-    input = (
-      <Select
-        id={config.key}
-        className="w-100"
-        disabled={disabled}
-        onChange={handleSelectChange}
-        value={config.effectiveValue}
-      >
-        {optionNodes}
-      </Select>
-    );
-  } else {
-    input = (
-      <Input
-        id={config.key}
-        className="w-100"
-        disabled={disabled}
-        onChange={handleChange}
-        placeholder={config.label}
-        value={config.effectiveValue}
-      />
-    );
-  }
-
-  return (
-    <HorizontalFormItem label={config.label}>
-      <div>{input}</div>
-      <FormExplain>{popoverContent}</FormExplain>
-    </HorizontalFormItem>
-  );
-}
-
-export default ConfigItemInput;
diff --git a/client/src/configuration/ConfigurationForm.js b/client/src/configuration/ConfigurationForm.js
deleted file mode 100644
index 80828097..00000000
--- a/client/src/configuration/ConfigurationForm.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { connect } from 'unistore/react';
-import Button from '../common/Button';
-import HorizontalFormItem from '../common/HorizontalFormItem';
-import message from '../common/message';
-import { refreshAppContext } from '../stores/config';
-import fetchJson from '../utilities/fetch-json.js';
-import ConfigItemInput from './ConfigItemInput';
-
-function ConfigurationForm({ refreshAppContext, onClose }) {
-  const [configItems, setConfigItems] = useState([]);
-
-  const loadConfigValuesFromServer = async () => {
-    const json = await fetchJson('GET', '/api/config-items');
-    if (json.error) {
-      message.error(json.error);
-    }
-    setConfigItems(json.configItems);
-  };
-
-  useEffect(() => {
-    loadConfigValuesFromServer();
-  }, []);
-
-  async function saveConfigValues() {
-    const changedSaves = configItems
-      .filter(item => item.changed)
-      .map(item => {
-        return fetchJson('POST', `/api/config-values/${item.key}`, {
-          value: item.effectiveValue
-        });
-      });
-
-    const responses = await Promise.all(changedSaves);
-    const errorResponse = responses.find(r => r.error);
-    if (errorResponse) {
-      message.error('Save failed');
-    } else {
-      await refreshAppContext();
-      onClose();
-    }
-  }
-
-  const handleChange = (key, value) => {
-    const items = configItems.map(item => {
-      if (item.key === key) {
-        return { ...item, effectiveValue: value, changed: true };
-      }
-      return item;
-    });
-    setConfigItems(items);
-  };
-
-  const hasChanges = configItems.filter(config => config.changed);
-
-  // This used to set disabled, but that was weird with the modal
-  // reach modal sets focus on the first focusable element it would seem, which is a disabled button
-  // Not only does that prevent `esc` key from closing modal, it doesn't show what is focused either
-  const type = hasChanges.length > 0 ? 'primary' : null;
-
-  return (
-    <div>
-      <HorizontalFormItem>
-        <Button className="w-100" type={type} onClick={saveConfigValues}>
-          Save
-        </Button>
-      </HorizontalFormItem>
-      {configItems.map(config => (
-        <ConfigItemInput
-          key={config.key}
-          config={config}
-          onChange={handleChange}
-        />
-      ))}
-    </div>
-  );
-}
-
-export default connect(
-  [],
-  { refreshAppContext }
-)(React.memo(ConfigurationForm));
diff --git a/client/src/queryEditor/toolbar/Toolbar.js b/client/src/queryEditor/toolbar/Toolbar.js
index 4f69c90c..3fbed11f 100644
--- a/client/src/queryEditor/toolbar/Toolbar.js
+++ b/client/src/queryEditor/toolbar/Toolbar.js
@@ -16,7 +16,6 @@ import Drawer from '../../common/Drawer';
 import IconButton from '../../common/IconButton';
 import IconMenu from '../../common/IconMenu';
 import Input from '../../common/Input';
-import ConfigurationForm from '../../configuration/ConfigurationForm';
 import ConnectionListDrawer from '../../connections/ConnectionListDrawer';
 import {
   formatQuery,
@@ -87,7 +86,6 @@ function Toolbar({
   unsavedChanges
 }) {
   const [showTags, setShowTags] = useState(false);
-  const [showConfig, setShowConfig] = useState(false);
   const [showUsers, setShowUsers] = useState(false);
   const [redirectToSignIn, setRedirectToSignIn] = useState(false);
   const [showAbout, setShowAbout] = useState(false);
@@ -107,9 +105,6 @@ function Toolbar({
   let menuItems = [];
   if (isAdmin) {
     menuItems = [
-      <MenuItem key="config" onSelect={() => setShowConfig(true)}>
-        Configuration
-      </MenuItem>,
       <MenuItem key="connections" onSelect={() => setShowConnections(true)}>
         Connections
       </MenuItem>,
@@ -219,16 +214,6 @@ function Toolbar({
           </MenuItem>
         </IconMenu>
 
-        <Drawer
-          title={'Configuration'}
-          visible={showConfig}
-          width={600}
-          onClose={() => setShowConfig(false)}
-          placement={'right'}
-        >
-          <ConfigurationForm onClose={() => setShowConfig(false)} />
-        </Drawer>
-
         <Drawer
           title={'Users'}
           visible={showUsers}
diff --git a/server/app.js b/server/app.js
index 63f217e5..d5bec9ef 100644
--- a/server/app.js
+++ b/server/app.js
@@ -7,7 +7,6 @@ const session = require('express-session');
 const FileStore = require('session-file-store')(session);
 const configUtil = require('./lib/config');
 const version = require('./lib/version');
-const db = require('./lib/db');
 const {
   baseUrl,
   googleClientId,
@@ -87,7 +86,7 @@ if (debug) {
 // Add config helper to req
 app.use(async function(req, res, next) {
   try {
-    req.config = await configUtil.getHelper(db);
+    req.config = configUtil.getHelper();
     next();
   } catch (error) {
     console.error('Error getting config helper', error);
@@ -112,8 +111,6 @@ const routers = [
   require('./routes/query-result.js'),
   require('./routes/download-results.js'), // streams result download to browser
   require('./routes/schema-info.js'),
-  require('./routes/config-items.js'),
-  require('./routes/config-values.js'),
   require('./routes/tags.js'),
   require('./routes/format-sql.js'),
   require('./routes/signup-signin-signout.js')
diff --git a/server/lib/config/configItems.js b/server/lib/config/configItems.js
index 647bddac..62725169 100644
--- a/server/lib/config/configItems.js
+++ b/server/lib/config/configItems.js
@@ -2,11 +2,8 @@
 // Nothing else sent unless using configuration page/api
 // This is to reduce leaking unnecessary information
 
-// NOTE: sensitve=true items will be masked with *** in configuration page
-
 const configItems = [
   {
-    interface: 'env',
     key: 'ip',
     cliFlag: 'ip',
     envVar: 'SQLPAD_IP',
@@ -15,7 +12,6 @@ const configItems = [
       'IP address to bind to. By default SQLPad will listen from all available addresses (0.0.0.0).'
   },
   {
-    interface: 'env',
     key: 'port',
     cliFlag: 'port',
     envVar: 'SQLPAD_PORT',
@@ -23,7 +19,6 @@ const configItems = [
     description: 'Port for SQLPad to listen on.'
   },
   {
-    interface: 'env',
     key: 'systemdSocket',
     cliFlag: 'systemd-socket',
     envVar: 'SQLPAD_SYSTEMD_SOCKET',
@@ -31,7 +26,6 @@ const configItems = [
     description: 'Acquire socket from systemd if available'
   },
   {
-    interface: 'env',
     key: 'httpsPort',
     cliFlag: 'https-port',
     envVar: 'SQLPAD_HTTPS_PORT',
@@ -39,7 +33,6 @@ const configItems = [
     description: 'Port for SQLPad to listen on.'
   },
   {
-    interface: 'env',
     key: 'dbPath',
     cliFlag: ['db', 'dbPath', 'dir'],
     envVar: 'SQLPAD_DB_PATH',
@@ -48,7 +41,6 @@ const configItems = [
       'Directory to store SQLPad embedded database content. This includes queries, users, query result cache files, etc.'
   },
   {
-    interface: 'env',
     key: 'baseUrl',
     cliFlag: 'base-url',
     envVar: 'SQLPAD_BASE_URL',
@@ -58,26 +50,21 @@ const configItems = [
       "Path to mount sqlpad app following domain. \nFor example, if '/sqlpad' is provided, queries page \nwould be located at mydomain.com/sqlpad/queries instead of mydomain.com/queries. \nUseful when subdomain is not an option."
   },
   {
-    interface: 'env',
     key: 'passphrase',
     cliFlag: 'passphrase',
     envVar: 'SQLPAD_PASSPHRASE',
-    sensitive: true,
     default: "At least the sensitive bits won't be plain text?",
     description:
       'A string of text used to encrypt sensitive values when stored on disk.'
   },
   {
-    interface: 'env',
     key: 'certPassphrase',
     cliFlag: 'cert-passphrase',
     envVar: 'CERT_PASSPHRASE',
-    sensitive: true,
     default: 'No cert',
     description: 'Passphrase for your SSL certification file'
   },
   {
-    interface: 'env',
     key: 'keyPath',
     cliFlag: ['key', 'key-path', 'key-dir'],
     envVar: 'KEY_PATH',
@@ -85,7 +72,6 @@ const configItems = [
     description: 'Absolute path to where SSL certificate key is stored'
   },
   {
-    interface: 'env',
     key: 'certPath',
     cliFlag: ['cert', 'cert-path', 'cert-dir'],
     envVar: 'CERT_PATH',
@@ -93,7 +79,6 @@ const configItems = [
     description: 'Absolute path to where SSL certificate is stored'
   },
   {
-    interface: 'env',
     key: 'admin',
     cliFlag: 'admin',
     envVar: 'SQLPAD_ADMIN',
@@ -102,7 +87,6 @@ const configItems = [
       'Email address to whitelist/give admin permissions to via command line or environment variable. Useful to preset Admin account or to reinstate admin access without access to the UI.'
   },
   {
-    interface: 'env',
     key: 'debug',
     cliFlag: 'debug',
     envVar: 'SQLPAD_DEBUG',
@@ -110,25 +94,20 @@ const configItems = [
     description: 'Add a variety of logging to console while running SQLPad'
   },
   {
-    interface: 'env',
     key: 'googleClientId',
     envVar: 'GOOGLE_CLIENT_ID',
-    sensitive: true,
     description:
       "Google Client ID used for OAuth setup. Note: authorized redirect URI for sqlpad is '[baseurl]/auth/google/callback'",
     default: ''
   },
   {
-    interface: 'env',
     key: 'googleClientSecret',
     envVar: 'GOOGLE_CLIENT_SECRET',
-    sensitive: true,
     description:
       "Google Client Secret used for OAuth setup. Note: authorized redirect URI for sqlpad is '[baseurl]/auth/google/callback'",
     default: ''
   },
   {
-    interface: 'env',
     key: 'publicUrl',
     envVar: 'PUBLIC_URL',
     cliFlag: 'public-url',
@@ -138,7 +117,6 @@ const configItems = [
     uiDependency: true
   },
   {
-    interface: 'env',
     key: 'disableUserpassAuth',
     envVar: 'DISABLE_USERPASS_AUTH',
     description:
@@ -146,7 +124,6 @@ const configItems = [
     default: false
   },
   {
-    interface: 'ui',
     key: 'allowCsvDownload',
     label: 'Allow CSV/XLSX Download',
     description: 'Set to false to disable csv or xlsx downloads.',
@@ -155,7 +132,6 @@ const configItems = [
     default: true
   },
   {
-    interface: 'ui',
     key: 'editorWordWrap',
     label: 'Editor Word Wrap',
     description: 'Set to true to enable word wrapping in SQL editor.',
@@ -164,7 +140,6 @@ const configItems = [
     default: false
   },
   {
-    interface: 'ui',
     key: 'queryResultMaxRows',
     label: 'Query Result Max Rows',
     description: 'By default query results are limited to 50,000 records.',
@@ -172,14 +147,12 @@ const configItems = [
     uiDependency: true
   },
   {
-    interface: 'ui',
     key: 'slackWebhook',
     label: 'Slack Webhook URL',
     description: 'Supply incoming Slack webhook URL to post query when saved.',
     default: ''
   },
   {
-    interface: 'ui',
     key: 'showSchemaCopyButton',
     label: 'Show Schema Copy Button',
     description:
@@ -189,7 +162,6 @@ const configItems = [
     uiDependency: true
   },
   {
-    interface: 'ui',
     key: 'tableChartLinksRequireAuth',
     label: 'Require Login for Table/Chart Links',
     description:
@@ -198,7 +170,6 @@ const configItems = [
     default: true
   },
   {
-    interface: 'ui',
     key: 'smtpFrom',
     envVar: 'SQLPAD_SMTP_FROM',
     cliFlag: 'smtp-from',
@@ -208,7 +179,6 @@ const configItems = [
     default: ''
   },
   {
-    interface: 'ui',
     key: 'smtpHost',
     envVar: 'SQLPAD_SMTP_HOST',
     cliFlag: 'smtp-host',
@@ -218,7 +188,6 @@ const configItems = [
     default: ''
   },
   {
-    interface: 'ui',
     key: 'smtpPort',
     envVar: 'SQLPAD_SMTP_PORT',
     cliFlag: 'smtp-port',
@@ -227,7 +196,6 @@ const configItems = [
     default: ''
   },
   {
-    interface: 'ui',
     key: 'smtpSecure',
     envVar: 'SQLPAD_SMTP_SECURE',
     cliFlag: 'smtp-secure',
@@ -237,7 +205,6 @@ const configItems = [
     default: true
   },
   {
-    interface: 'ui',
     key: 'smtpUser',
     envVar: 'SQLPAD_SMTP_USER',
     cliFlag: 'smtp-user',
@@ -247,17 +214,14 @@ const configItems = [
     default: ''
   },
   {
-    interface: 'ui',
     key: 'smtpPassword',
     envVar: 'SQLPAD_SMTP_PASSWORD',
     cliFlag: 'smtp-password',
     label: 'SMTP Password',
     description: 'Password for SMTP.',
-    default: '',
-    sensitive: true
+    default: ''
   },
   {
-    interface: 'ui',
     key: 'whitelistedDomains',
     label: 'Whitelisted Domains',
     envVar: 'WHITELISTED_DOMAINS',
@@ -267,7 +231,6 @@ const configItems = [
     default: ''
   },
   {
-    interface: 'ui',
     key: 'disableUpdateCheck',
     envVar: 'SQLPAD_DISABLE_UPDATE_CHECK',
     cliFlag: 'disable-update-check',
diff --git a/server/lib/config/fromDb.js b/server/lib/config/fromDb.js
deleted file mode 100644
index 4b3139b4..00000000
--- a/server/lib/config/fromDb.js
+++ /dev/null
@@ -1,27 +0,0 @@
-const definitions = require('./configItems');
-
-const uiKeys = definitions
-  .filter(definition => definition.interface === 'ui')
-  .map(definition => definition.key);
-
-/**
- * Gets config values set in ui from db
- * @param {object} db
- * @returns {Promise} configMap as a Promise
- */
-module.exports = async function getUiConfig(db) {
-  if (!db) {
-    throw new Error('db not provided');
-  }
-  const docs = await db.config.find({});
-  if (!docs || !docs.length) {
-    return {};
-  }
-  const configMap = {};
-  docs
-    .filter(doc => uiKeys.includes(doc.key))
-    .forEach(doc => {
-      configMap[doc.key] = doc.value;
-    });
-  return configMap;
-};
diff --git a/server/lib/config/index.js b/server/lib/config/index.js
index 4e447f6a..5db837d3 100644
--- a/server/lib/config/index.js
+++ b/server/lib/config/index.js
@@ -2,7 +2,6 @@ const fs = require('fs');
 const path = require('path');
 const minimist = require('minimist');
 const definitions = require('./configItems');
-const fromDb = require('./fromDb');
 const fromDefault = require('./fromDefault');
 const fromEnv = require('./fromEnv');
 const fromCli = require('./fromCli');
@@ -23,48 +22,21 @@ const cliConfig = fromCli(argv);
 const savedCliConfig = fromCli(savedArgv);
 const envConfig = fromEnv();
 
-function makeSave(db) {
-  return async function save(key, value) {
-    const definition = definitions.find(definition => definition.key === key);
-
-    if (definition.interface !== 'ui') {
-      throw new Error(
-        `Config Item ${key} must use ui interface to be saved to db`
-      );
-    }
-
-    const doc = await db.config.findOne({ key });
-    if (doc) {
-      doc.value = value;
-      doc.modifiedDate = new Date();
-      return db.config.update({ _id: doc._id }, doc, {});
-    }
-    const newConfigValue = {
-      key,
-      value,
-      createdDate: new Date(),
-      modifiedDate: new Date()
-    };
-    return db.config.insert(newConfigValue);
-  };
-}
-
-function setBy(cliConfig, savedCliConfig, envConfig, dbConfig, key) {
+function setBy(cliConfig, savedCliConfig, envConfig, key) {
   if (cliConfig[key]) {
     return 'cli';
   } else if (savedCliConfig[key]) {
     return 'saved cli';
   } else if (envConfig[key]) {
     return 'env';
-  } else if (dbConfig[key]) {
-    return 'db';
   } else {
     return 'default';
   }
 }
 
 /**
- * Get all config item values sans values from UI/db
+ * TODO FIXME XXX - this is no longer necessary as config helper is all the config.
+ * there is no more db to consider
  * @returns {object} configMap
  */
 exports.getPreDbConfig = function getPreDbConfig() {
@@ -73,16 +45,12 @@ exports.getPreDbConfig = function getPreDbConfig() {
 
 /**
  * Gets config helper using all config sources
- * @param {db} db
  * @returns {Promise} configHelper
  */
-exports.getHelper = async function getHelper(db) {
-  const dbConfig = await fromDb(db);
-
+exports.getHelper = function getHelper() {
   const all = Object.assign(
     {},
     defaultConfig,
-    dbConfig,
     envConfig,
     savedCliConfig,
     cliConfig
@@ -96,34 +64,20 @@ exports.getHelper = async function getHelper(db) {
       return all[key];
     },
     getConfigItems: () => {
-      return definitions
-        .map(definition => {
-          return Object.assign({}, definition, {
-            effectiveValue: all[definition.key],
-            effectiveValueSource: setBy(
-              cliConfig,
-              savedCliConfig,
-              envConfig,
-              dbConfig,
-              definition.key
-            ),
-            envValue: envConfig[definition.key],
-            cliValue: cliConfig[definition.key],
-            savedCliValue: savedCliConfig[definition.key],
-            dbValue: dbConfig[definition.key]
-          });
-        })
-        .map(item => {
-          if (item.sensitive && item.interface === 'env') {
-            item.effectiveValue = item.effectiveValue ? '**********' : '';
-            item.dbValue = item.dbValue ? '**********' : '';
-            item.default = item.default ? '**********' : '';
-            item.envValue = item.envValue ? '**********' : '';
-            item.cliValue = item.cliValue ? '**********' : '';
-            item.savedCliValue = item.savedCliValue ? '**********' : '';
-          }
-          return item;
+      return definitions.map(definition => {
+        return Object.assign({}, definition, {
+          effectiveValue: all[definition.key],
+          effectiveValueSource: setBy(
+            cliConfig,
+            savedCliConfig,
+            envConfig,
+            definition.key
+          ),
+          envValue: envConfig[definition.key],
+          cliValue: cliConfig[definition.key],
+          savedCliValue: savedCliConfig[definition.key]
         });
+      });
     },
     getUiConfig: () => {
       return definitions
@@ -133,7 +87,6 @@ exports.getHelper = async function getHelper(db) {
           return configMap;
         }, {});
     },
-    save: makeSave(db),
     smtpConfigured: () =>
       all.smtpHost &&
       all.smtpUser &&
diff --git a/server/lib/db.js b/server/lib/db.js
index ea2726ad..8292ac5c 100644
--- a/server/lib/db.js
+++ b/server/lib/db.js
@@ -12,8 +12,7 @@ const db = {
   }),
   queries: datastore({ filename: path.join(dbPath, 'queries.db') }),
   cache: datastore({ filename: path.join(dbPath, 'cache.db') }),
-  config: datastore({ filename: path.join(dbPath, 'config.db') }),
-  instances: ['users', 'connections', 'queries', 'cache', 'config']
+  instances: ['users', 'connections', 'queries', 'cache']
 };
 
 // Load dbs, migrate data, and apply indexes
@@ -28,7 +27,6 @@ async function init() {
   );
   await db.users.ensureIndex({ fieldName: 'email', unique: true });
   await db.cache.ensureIndex({ fieldName: 'cacheKey', unique: true });
-  await db.config.ensureIndex({ fieldName: 'key', unique: true });
   // set autocompaction
   const tenMinutes = 1000 * 60 * 10;
   db.instances.forEach(dbname => {
diff --git a/server/lib/email.js b/server/lib/email.js
index 61933267..2d0bdfc3 100644
--- a/server/lib/email.js
+++ b/server/lib/email.js
@@ -1,6 +1,5 @@
 const nodemailer = require('nodemailer');
 const configUtil = require('./config');
-const db = require('./db');
 const { baseUrl, port, publicUrl } = require('./config').getPreDbConfig();
 
 /**
@@ -37,7 +36,7 @@ function sendInvite(to) {
 }
 
 async function send(to, subject, text, html) {
-  const config = await configUtil.getHelper(db);
+  const config = configUtil.getHelper();
 
   if (!config.smtpConfigured()) {
     console.error('email.send() called without being configured');
diff --git a/server/lib/version.js b/server/lib/version.js
index d0c6d7bc..1ba36d61 100644
--- a/server/lib/version.js
+++ b/server/lib/version.js
@@ -1,7 +1,6 @@
 const packageJson = require('../package.json');
 const latestVersion = require('latest-version');
 const semverDiff = require('semver-diff');
-const db = require('./db.js');
 const configUtil = require('./config');
 
 const ONE_DAY = 1000 * 60 * 60 * 24;
@@ -27,7 +26,7 @@ function logUpdateAvailable(version) {
 
 async function checkForUpdate() {
   try {
-    const config = await configUtil.getHelper(db);
+    const config = configUtil.getHelper();
     if (config.get('disableUpdateCheck')) {
       return;
     }
diff --git a/server/middleware/passport.js b/server/middleware/passport.js
index 7dc14380..7847c837 100644
--- a/server/middleware/passport.js
+++ b/server/middleware/passport.js
@@ -4,7 +4,6 @@ const PassportGoogleStrategy = require('passport-google-oauth20').Strategy;
 const BasicStrategy = require('passport-http').BasicStrategy;
 const User = require('../models/User.js');
 const configUtil = require('../lib/config');
-const db = require('../lib/db');
 const checkWhitelist = require('../lib/check-whitelist.js');
 const {
   baseUrl,
@@ -113,10 +112,10 @@ async function passportGoogleStrategyHandler(
   }
 
   try {
-    let [openAdminRegistration, user, config] = await Promise.all([
+    const config = configUtil.getHelper();
+    let [openAdminRegistration, user] = await Promise.all([
       User.adminRegistrationOpen(),
-      User.findOneByEmail(email),
-      configUtil.getHelper(db)
+      User.findOneByEmail(email)
     ]);
 
     if (user) {
diff --git a/server/models/Query.js b/server/models/Query.js
index 4d6fd1dd..90d51969 100644
--- a/server/models/Query.js
+++ b/server/models/Query.js
@@ -88,39 +88,32 @@ Query.prototype.save = function save() {
 };
 
 Query.prototype.pushQueryToSlackIfSetup = function() {
-  return configUtil
-    .getHelper(db)
-    .then(config => {
-      const SLACK_WEBHOOK = config.get('slackWebhook');
-      if (SLACK_WEBHOOK) {
-        const PUBLIC_URL = config.get('publicUrl');
-        const BASE_URL = config.get('baseUrl');
-        const options = {
-          method: 'post',
-          body: {
-            text: `New Query <${PUBLIC_URL}${BASE_URL}/queries/${this._id}|${
-              this.name
-            }> 
+  const config = configUtil.getHelper();
+  const SLACK_WEBHOOK = config.get('slackWebhook');
+  if (SLACK_WEBHOOK) {
+    const PUBLIC_URL = config.get('publicUrl');
+    const BASE_URL = config.get('baseUrl');
+    const options = {
+      method: 'post',
+      body: {
+        text: `New Query <${PUBLIC_URL}${BASE_URL}/queries/${this._id}|${
+          this.name
+        }> 
             saved by ${this.modifiedBy} on SQLPad 
             ${'```'}
             ${this.queryText}
             ${'```'}`
-          },
-          json: true,
-          url: SLACK_WEBHOOK
-        };
-        request(options, function(err) {
-          if (err) {
-            console.error('Something went wrong while sending to Slack.');
-            console.error(err);
-          }
-        });
+      },
+      json: true,
+      url: SLACK_WEBHOOK
+    };
+    request(options, function(err) {
+      if (err) {
+        console.error('Something went wrong while sending to Slack.');
+        console.error(err);
       }
-    })
-    .catch(error => {
-      console.log('error getting config helper');
-      console.error(error);
     });
+  }
 };
 
 /*  Query methods
diff --git a/server/routes/config-items.js b/server/routes/config-items.js
deleted file mode 100644
index e2dc5af1..00000000
--- a/server/routes/config-items.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const router = require('express').Router();
-const mustBeAdmin = require('../middleware/must-be-admin.js');
-
-router.get('/api/config-items', mustBeAdmin, function(req, res) {
-  const { config } = req;
-  const configItems = config.getConfigItems() || [];
-  return res.json({
-    configItems: configItems.filter(config => config.interface === 'ui')
-  });
-});
-
-module.exports = router;
diff --git a/server/routes/config-values.js b/server/routes/config-values.js
deleted file mode 100644
index 4a36ba93..00000000
--- a/server/routes/config-values.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const router = require('express').Router();
-const mustBeAdmin = require('../middleware/must-be-admin.js');
-const sendError = require('../lib/sendError');
-
-router.post('/api/config-values/:key', mustBeAdmin, async function(req, res) {
-  try {
-    const { body, config, params } = req;
-    await config.save(params.key, body.value);
-    res.json({});
-  } catch (error) {
-    sendError(res, error, 'Problem saving config value');
-  }
-});
-
-module.exports = router;


Test patch:
diff --git a/server/test/api/config-values.js b/server/test/api/config-values.js
deleted file mode 100644
index 3e27d97c..00000000
--- a/server/test/api/config-values.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const assert = require('assert');
-const utils = require('../utils');
-
-describe('api config-item & config-values', function() {
-  before(function() {
-    return utils.resetWithUser();
-  });
-
-  it('GET api/config-items (check default)', async function() {
-    const body = await utils.get('admin', '/api/config-items');
-    const { configItems, error } = body;
-    assert(!error, 'Expect no error');
-    const item = configItems.find(i => i.key === 'allowCsvDownload');
-    assert.equal(
-      item.default,
-      item.effectiveValue,
-      'default is effectiveValue'
-    );
-  });
-
-  it('POST api/config-values (change value)', async function() {
-    const body = await utils.post(
-      'admin',
-      '/api/config-values/allowCsvDownload',
-      {
-        value: false
-      }
-    );
-    assert(!body.error, 'Expect no error');
-  });
-
-  it('GET api/config-items (validate change)', async function() {
-    const body = await utils.get('admin', '/api/config-items');
-    const { configItems, error } = body;
-    assert(!error, 'Expect no error');
-    const item = configItems.find(i => i.key === 'allowCsvDownload');
-    assert.equal(item.effectiveValue, false, 'default is effectiveValue');
-  });
-});
diff --git a/server/test/lib/config.js b/server/test/lib/config.js
index 0854170e..03fac1ff 100644
--- a/server/test/lib/config.js
+++ b/server/test/lib/config.js
@@ -1,6 +1,5 @@
 const assert = require('assert');
 const configUtil = require('../../lib/config');
-const db = require('../../lib/db');
 
 const configItems = require('../../lib/config/configItems');
 const fromDefault = require('../../lib/config/fromDefault');
@@ -47,11 +46,11 @@ describe('lib/config', function() {
   // This makes testing difficult when a sqlpad configuration is saved.
   // Loading a config should likely be explicit
   it('.get() should get a value provided by default', async function() {
-    const config = await configUtil.getHelper(db);
+    const config = configUtil.getHelper();
     assert.equal(config.get('httpsPort'), 443, 'httpsPort=443');
   });
   it('.get() should only accept key in config items', async function() {
-    const config = await configUtil.getHelper(db);
+    const config = configUtil.getHelper();
     assert.throws(() => config.get('non-existent-key'), Error);
   });
 });
diff --git a/server/test/lib/email.js b/server/test/lib/email.js
index 57bf289a..731e8249 100644
--- a/server/test/lib/email.js
+++ b/server/test/lib/email.js
@@ -1,9 +1,8 @@
 const email = require('../../lib/email.js');
 const configUtil = require('../../lib/config');
-const db = require('../../lib/db');
 
 describe('lib/email.js', async function() {
-  const config = await configUtil.getHelper(db);
+  const config = configUtil.getHelper();
   if (config.smtpConfigured() && process.env.SQLPAD_TEST_EMAIL) {
     it('should send invites', function() {
       return email.sendInvite(process.env.SQLPAD_TEST_EMAIL);
diff --git a/server/test/utils.js b/server/test/utils.js
index ed338122..c4413457 100644
--- a/server/test/utils.js
+++ b/server/test/utils.js
@@ -27,8 +27,7 @@ function reset() {
   return Promise.all([
     db.users.remove({}, { multi: true }),
     db.queries.remove({}, { multi: true }),
-    db.connections.remove({}, { multi: true }),
-    db.config.remove({}, { multi: true })
+    db.connections.remove({}, { multi: true })
   ]);
 }