From d5f68dbbc53ed5288c6c122b7f68d996b888140a Mon Sep 17 00:00:00 2001 From: Rish Date: Tue, 30 Jun 2020 17:34:56 +0530 Subject: [PATCH] Updated serializers/model layer validation using settings type refs https://github.com/TryGhost/Ghost/issues/10318 - Updates `boolean` serialization in v2/canary serializers to apply only for `boolean` type settings - Updates `boolean` transformation in model layer `format`/`parse` to check on `boolean` type setting - Removes error thrown on Read-only setting for settings edit endpoint - Updates v2/canary input serializers to remove any Read-only settings (using RO flag) to avoid edits - Added type/group mappings in the importer when pre-migration settings table import data is present - Updates tests --- .../utils/serializers/input/settings.js | 18 ++++- .../canary/utils/validators/input/settings.js | 12 +--- .../settings-filter-type-group-mapper.js} | 0 .../input/utils/settings-key-group-mapper.js | 57 ++++++++++++++++ .../input/utils/settings-key-type-mapper.js | 66 +++++++++++++++++++ .../v2/utils/serializers/input/settings.js | 28 +++++--- .../input/utils/settings-type-group-mapper.js | 45 ------------- core/server/models/settings.js | 27 ++++++-- ...settings-filter-type-group-mapper_spec.js} | 2 +- test/unit/models/settings_spec.js | 17 ++--- .../utils/fixtures/export/default_export.json | 56 ++++++++-------- 11 files changed, 216 insertions(+), 112 deletions(-) rename core/server/api/{canary/utils/serializers/input/utils/settings-type-group-mapper.js => shared/serializers/input/utils/settings-filter-type-group-mapper.js} (100%) create mode 100644 core/server/api/shared/serializers/input/utils/settings-key-group-mapper.js create mode 100644 core/server/api/shared/serializers/input/utils/settings-key-type-mapper.js delete mode 100644 core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js rename test/unit/api/canary/utils/serializers/input/utils/{settings-type-group-mapper_spec.js => settings-filter-type-group-mapper_spec.js} (87%) diff --git a/core/server/api/canary/utils/serializers/input/settings.js b/core/server/api/canary/utils/serializers/input/settings.js index ad52a599ec..3225d89244 100644 --- a/core/server/api/canary/utils/serializers/input/settings.js +++ b/core/server/api/canary/utils/serializers/input/settings.js @@ -1,6 +1,7 @@ const _ = require('lodash'); const url = require('./utils/url'); -const typeGroupMapper = require('./utils/settings-type-group-mapper'); +const typeGroupMapper = require('../../../../shared/serializers/input/utils/settings-filter-type-group-mapper'); +const settingsCache = require('../../../../../services/settings/cache'); module.exports = { browse(apiConfig, frame) { @@ -38,11 +39,22 @@ module.exports = { if (_.isString(frame.data)) { frame.data = {settings: [{key: frame.data, value: frame.options}]}; } + const settings = settingsCache.getAll(); + + // Ignore and drop all values with Read-only flag + frame.data.settings = frame.data.settings.filter((setting) => { + const settingFlagsStr = settings[setting.key] ? settings[setting.key].flags : ''; + const settingFlagsArr = settingFlagsStr ? settingFlagsStr.split(',') : []; + return !settingFlagsArr.includes('RO'); + }); frame.data.settings.forEach((setting) => { // CASE: transform objects/arrays into string (we store stringified objects in the db) // @TODO: This belongs into the model layer. We should stringify before saving and parse when fetching from db. // @TODO: Fix when dropping v0.1 + const settingType = settings[setting.key] ? settings[setting.key].type : ''; + + //TODO: Needs to be removed once we get rid of all `object` type settings if (_.isObject(setting.value)) { setting.value = JSON.stringify(setting.value); } @@ -50,12 +62,12 @@ module.exports = { // @TODO: handle these transformations in a centralised API place (these rules should apply for ALL resources) // CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail - if (setting.value === '0' || setting.value === '1') { + if (settingType === 'boolean' && (setting.value === '0' || setting.value === '1')) { setting.value = !!+setting.value; } // CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail - if (setting.value === 'false' || setting.value === 'true') { + if (settingType === 'boolean' && (setting.value === 'false' || setting.value === 'true')) { setting.value = setting.value === 'true'; } diff --git a/core/server/api/canary/utils/validators/input/settings.js b/core/server/api/canary/utils/validators/input/settings.js index d008cf93ef..49bcad744e 100644 --- a/core/server/api/canary/utils/validators/input/settings.js +++ b/core/server/api/canary/utils/validators/input/settings.js @@ -1,7 +1,7 @@ const Promise = require('bluebird'); const _ = require('lodash'); const {i18n} = require('../../../../../lib/common'); -const {BadRequestError, NotFoundError} = require('@tryghost/errors'); +const {NotFoundError} = require('@tryghost/errors'); module.exports = { read(apiConfig, frame) { @@ -19,15 +19,7 @@ module.exports = { const errors = []; _.each(frame.data.settings, (setting) => { - if (setting.key === 'active_theme') { - // @NOTE: active theme has to be changed via theme endpoints - errors.push( - new BadRequestError({ - message: i18n.t('errors.api.settings.activeThemeSetViaAPI.error'), - help: i18n.t('errors.api.settings.activeThemeSetViaAPI.help') - }) - ); - } else if (setting.key === 'ghost_head' || setting.key === 'ghost_foot') { + if (setting.key === 'ghost_head' || setting.key === 'ghost_foot') { // @NOTE: was removed https://github.com/TryGhost/Ghost/issues/10373 errors.push(new NotFoundError({ message: i18n.t('errors.api.settings.problemFindingSetting', { diff --git a/core/server/api/canary/utils/serializers/input/utils/settings-type-group-mapper.js b/core/server/api/shared/serializers/input/utils/settings-filter-type-group-mapper.js similarity index 100% rename from core/server/api/canary/utils/serializers/input/utils/settings-type-group-mapper.js rename to core/server/api/shared/serializers/input/utils/settings-filter-type-group-mapper.js diff --git a/core/server/api/shared/serializers/input/utils/settings-key-group-mapper.js b/core/server/api/shared/serializers/input/utils/settings-key-group-mapper.js new file mode 100644 index 0000000000..333c1bdca9 --- /dev/null +++ b/core/server/api/shared/serializers/input/utils/settings-key-group-mapper.js @@ -0,0 +1,57 @@ +// NOTE: mapping is based on maping present in migration - 3.22/04-populate-settings-groups-and-flags +const keyGroupMapping = { + members_public_key: 'core', + members_private_key: 'core', + members_email_auth_secret: 'core', + db_hash: 'core', + next_update_check: 'core', + notifications: 'core', + session_secret: 'core', + theme_session_secret: 'core', + ghost_public_key: 'core', + ghost_private_key: 'core', + title: 'site', + description: 'site', + logo: 'site', + cover_image: 'site', + icon: 'site', + accent_color: 'site', + lang: 'site', + timezone: 'site', + codeinjection_head: 'site', + codeinjection_foot: 'site', + facebook: 'site', + twitter: 'site', + navigation: 'site', + secondary_navigation: 'site', + meta_title: 'site', + meta_description: 'site', + og_image: 'site', + og_title: 'site', + og_description: 'site', + twitter_image: 'site', + twitter_title: 'site', + twitter_description: 'site', + active_theme: 'theme', + is_private: 'private', + password: 'private', + public_hash: 'private', + amp: 'amp', + labs: 'labs', + slack: 'slack', + unsplash: 'unsplash', + shared_views: 'views', + bulk_email_settings: 'email', + default_content_visibility: 'members', + members_subscription_settings: 'members', + stripe_connect_integration: 'members', + portal_name: 'portal', + portal_button: 'portal', + portal_plans: 'portal' +}; + +const mapKeyToGroup = (key) => { + return keyGroupMapping[key]; +}; + +module.exports = mapKeyToGroup; diff --git a/core/server/api/shared/serializers/input/utils/settings-key-type-mapper.js b/core/server/api/shared/serializers/input/utils/settings-key-type-mapper.js new file mode 100644 index 0000000000..a71d807af6 --- /dev/null +++ b/core/server/api/shared/serializers/input/utils/settings-key-type-mapper.js @@ -0,0 +1,66 @@ +// NOTE: mapping is based on maping present in migration - 3.22/07-update-type-for-settings +const keyTypeMapping = { + db_hash: 'string', + session_secret: 'string', + theme_session_secret: 'string', + ghost_public_key: 'string', + ghost_private_key: 'string', + title: 'string', + description: 'string', + logo: 'string', + cover_image: 'string', + icon: 'string', + accent_color: 'string', + lang: 'string', + timezone: 'string', + codeinjection_head: 'string', + codeinjection_foot: 'string', + facebook: 'string', + twitter: 'string', + meta_title: 'string', + meta_description: 'string', + og_image: 'string', + og_title: 'string', + og_description: 'string', + twitter_image: 'string', + twitter_title: 'string', + twitter_description: 'string', + active_theme: 'string', + password: 'string', + public_hash: 'string', + members_public_key: 'string', + members_private_key: 'string', + members_email_auth_secret: 'string', + default_content_visibility: 'string', + members_from_address: 'string', + stripe_product_name: 'string', + stripe_secret_key: 'string', + stripe_publishable_key: 'string', + stripe_connect_publishable_key: 'string', + stripe_connect_secret_key: 'string', + stripe_connect_display_name: 'string', + stripe_connect_account_id: 'string', + notifications: 'array', + navigation: 'array', + secondary_navigation: 'array', + slack: 'array', + shared_views: 'array', + portal_plans: 'array', + stripe_plans: 'array', + next_update_check: 'number', + amp: 'boolean', + is_private: 'boolean', + members_allow_free_signup: 'boolean', + portal_name: 'boolean', + portal_button: 'boolean', + stripe_connect_livemode: 'boolean', + labs: 'object', + unsplash: 'object', + bulk_email_settings: 'object' +}; + +const mapKeyToType = (key) => { + return keyTypeMapping[key]; +}; + +module.exports = mapKeyToType; diff --git a/core/server/api/v2/utils/serializers/input/settings.js b/core/server/api/v2/utils/serializers/input/settings.js index 529da4dfee..591e008985 100644 --- a/core/server/api/v2/utils/serializers/input/settings.js +++ b/core/server/api/v2/utils/serializers/input/settings.js @@ -1,6 +1,7 @@ const _ = require('lodash'); const url = require('./utils/url'); -const typeGroupMapper = require('./utils/settings-type-group-mapper'); +const typeGroupMapper = require('../../../../shared/serializers/input/utils/settings-filter-type-group-mapper'); +const settingsCache = require('../../../../../services/settings/cache'); module.exports = { browse(apiConfig, frame) { @@ -35,23 +36,30 @@ module.exports = { frame.data = {settings: [{key: frame.data, value: frame.options}]}; } + const settings = settingsCache.getAll(); + + // Ignore and drop all values with Read-only flag + frame.data.settings = frame.data.settings.filter((setting) => { + const settingFlagsStr = settings[setting.key] ? settings[setting.key].flags : ''; + const settingFlagsArr = settingFlagsStr ? settingFlagsStr.split(',') : []; + return !settingFlagsArr.includes('RO'); + }); + frame.data.settings.forEach((setting) => { - // CASE: transform objects/arrays into string (we store stringified objects in the db) - // @TODO: This belongs into the model layer. We should stringify before saving and parse when fetching from db. - // @TODO: Fix when dropping v0.1 + const settingType = settings[setting.key] ? settings[setting.key].type : ''; + + // TODO: Needs to be removed once we get rid of all `object` type settings if (_.isObject(setting.value)) { setting.value = JSON.stringify(setting.value); } - // @TODO: handle these transformations in a centralised API place (these rules should apply for ALL resources) - - // CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail - if (setting.value === '0' || setting.value === '1') { + // CASE: Ensure we won't forward strings for booleans, otherwise model events or model interactions can fail + if (settingType === 'boolean' && (setting.value === '0' || setting.value === '1')) { setting.value = !!+setting.value; } - // CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail - if (setting.value === 'false' || setting.value === 'true') { + // CASE: Ensure we won't forward strings for booleans, otherwise model events or model interactions can fail + if (settingType === 'boolean' && (setting.value === 'false' || setting.value === 'true')) { setting.value = setting.value === 'true'; } diff --git a/core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js b/core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js deleted file mode 100644 index 4534377a21..0000000000 --- a/core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js +++ /dev/null @@ -1,45 +0,0 @@ -const typeGroupMapping = { - core: [ - 'core' - ], - blog: [ - 'site', - 'amp', - 'labs', - 'slack', - 'unsplash', - 'views' - ], - theme: [ - 'theme' - ], - members: [ - 'members' - ], - private: [ - 'private' - ], - portal: [ - 'portal' - ], - bulk_email: [ - 'email' - ], - site: [ - 'site' - ] -}; - -const mapTypeToGroup = (typeOptions) => { - const types = typeOptions.split(','); - - const mappedTypes = types.map((type) => { - const sanitizedType = type ? type.trim() : null; - - return typeGroupMapping[sanitizedType]; - }).filter(type => !!type); - - return mappedTypes.join(','); -}; - -module.exports = mapTypeToGroup; diff --git a/core/server/models/settings.js b/core/server/models/settings.js index 1c9b90496e..74543000de 100644 --- a/core/server/models/settings.js +++ b/core/server/models/settings.js @@ -133,10 +133,22 @@ Settings = ghostBookshelf.Model.extend({ format() { const attrs = ghostBookshelf.Model.prototype.format.apply(this, arguments); + const settingType = attrs.type; - // @NOTE: type TEXT will transform boolean to "0" - if (_.isBoolean(attrs.value)) { - attrs.value = attrs.value.toString(); + if (settingType === 'boolean') { + // CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail + if (attrs.value === '0' || attrs.value === '1') { + attrs.value = !!+attrs.value; + } + + // CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail + if (attrs.value === 'false' || attrs.value === 'true') { + attrs.value = JSON.parse(attrs.value); + } + + if (_.isBoolean(attrs.value)) { + attrs.value = attrs.value.toString(); + } } return attrs; @@ -145,13 +157,14 @@ Settings = ghostBookshelf.Model.extend({ parse() { const attrs = ghostBookshelf.Model.prototype.parse.apply(this, arguments); - // transform "0" to false - // transform "false" to false - if (attrs.value === '0' || attrs.value === '1') { + // transform "0" to false for boolean type + const settingType = attrs.type; + if (settingType === 'boolean' && (attrs.value === '0' || attrs.value === '1')) { attrs.value = !!+attrs.value; } - if (attrs.value === 'false' || attrs.value === 'true') { + // transform "false" to false for boolean type + if (settingType === 'boolean' && (attrs.value === 'false' || attrs.value === 'true')) { attrs.value = JSON.parse(attrs.value); } diff --git a/test/unit/api/canary/utils/serializers/input/utils/settings-type-group-mapper_spec.js b/test/unit/api/canary/utils/serializers/input/utils/settings-filter-type-group-mapper_spec.js similarity index 87% rename from test/unit/api/canary/utils/serializers/input/utils/settings-type-group-mapper_spec.js rename to test/unit/api/canary/utils/serializers/input/utils/settings-filter-type-group-mapper_spec.js index d41dc8b9ea..361cb0cb18 100644 --- a/test/unit/api/canary/utils/serializers/input/utils/settings-type-group-mapper_spec.js +++ b/test/unit/api/canary/utils/serializers/input/utils/settings-filter-type-group-mapper_spec.js @@ -1,5 +1,5 @@ const should = require('should'); -const mapper = require('../../../../../../../../core//server/api/canary/utils/serializers/input/utils/settings-type-group-mapper'); +const mapper = require('../../../../../../../../core/server/api/shared/serializers/input/utils/settings-filter-type-group-mapper'); describe('Unit: canary/utils/serializers/input/utils/settings-type-group-mapper', function () { describe('browse', function () { diff --git a/test/unit/models/settings_spec.js b/test/unit/models/settings_spec.js index fa5c386fe4..1f6bca1245 100644 --- a/test/unit/models/settings_spec.js +++ b/test/unit/models/settings_spec.js @@ -48,9 +48,10 @@ describe('Unit: models/settings', function () { ][step - 1](); }); - return models.Settings.edit({ + return models.Settings.add({ key: 'description', - value: 'added value' + value: 'added value', + type: 'string' }) .then(() => { eventSpy.calledTwice.should.be.true(); @@ -188,22 +189,22 @@ describe('Unit: models/settings', function () { it('ensure correct parsing when fetching from db', function () { const setting = models.Settings.forge(); - let returns = setting.parse({key: 'is_private', value: 'false'}); + let returns = setting.parse({key: 'is_private', value: 'false', type: 'boolean'}); should.equal(returns.value, false); - returns = setting.parse({key: 'is_private', value: false}); + returns = setting.parse({key: 'is_private', value: false, type: 'boolean'}); should.equal(returns.value, false); - returns = setting.parse({key: 'is_private', value: true}); + returns = setting.parse({key: 'is_private', value: true, type: 'boolean'}); should.equal(returns.value, true); - returns = setting.parse({key: 'is_private', value: 'true'}); + returns = setting.parse({key: 'is_private', value: 'true', type: 'boolean'}); should.equal(returns.value, true); - returns = setting.parse({key: 'is_private', value: '0'}); + returns = setting.parse({key: 'is_private', value: '0', type: 'boolean'}); should.equal(returns.value, false); - returns = setting.parse({key: 'is_private', value: '1'}); + returns = setting.parse({key: 'is_private', value: '1', type: 'boolean'}); should.equal(returns.value, true); returns = setting.parse({key: 'something', value: 'null'}); diff --git a/test/utils/fixtures/export/default_export.json b/test/utils/fixtures/export/default_export.json index e2f2d03eb9..8330d7e3b6 100644 --- a/test/utils/fixtures/export/default_export.json +++ b/test/utils/fixtures/export/default_export.json @@ -2208,7 +2208,7 @@ "id": "5c2ca6e0e015a6761618240d", "key": "db_hash", "value": "04a3a271-634b-44d8-842e-49286b38a162", - "type": "core", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2218,7 +2218,7 @@ "id": "5c2ca6e0e015a6761618240e", "key": "next_update_check", "value": null, - "type": "core", + "type": "number", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2228,7 +2228,7 @@ "id": "5c2ca6e0e015a6761618240f", "key": "notifications", "value": "[]", - "type": "core", + "type": "array", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2238,7 +2238,7 @@ "id": "5c2ca6e0e015a67616182410", "key": "session_secret", "value": "e255c011755a47cba690cb5089e2c05f488ab0e6d682d44809f622e25864e980", - "type": "core", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2248,7 +2248,7 @@ "id": "5c2ca6e0e015a67616182411", "key": "title", "value": "Ghost", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2258,7 +2258,7 @@ "id": "5c2ca6e0e015a67616182412", "key": "description", "value": "The professional publishing platform", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2268,7 +2268,7 @@ "id": "5c2ca6e0e015a67616182413", "key": "logo", "value": "https://static.ghost.org/v1.0.0/images/ghost-logo.svg", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2278,7 +2278,7 @@ "id": "5c2ca6e0e015a67616182414", "key": "cover_image", "value": "https://static.ghost.org/v1.0.0/images/blog-cover.jpg", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2288,7 +2288,7 @@ "id": "5c2ca6e0e015a67616182415", "key": "icon", "value": "", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2298,7 +2298,7 @@ "id": "5c2ca6e0e015a67616182416", "key": "lang", "value": "en", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2308,7 +2308,7 @@ "id": "5c2ca6e0e015a67616182417", "key": "timezone", "value": "Etc/UTC", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2318,7 +2318,7 @@ "id": "5c2ca6e0e015a6761618241a", "key": "amp", "value": "true", - "type": "amp", + "type": "boolean", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2328,7 +2328,7 @@ "id": "5c2ca6e0e015a6761618241b", "key": "ghost_head", "value": "", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2338,7 +2338,7 @@ "id": "5c2ca6e0e015a6761618241c", "key": "ghost_foot", "value": "", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2348,7 +2348,7 @@ "id": "5c2ca6e0e015a6761618241d", "key": "facebook", "value": "ghost", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2358,7 +2358,7 @@ "id": "5c2ca6e0e015a6761618241e", "key": "twitter", "value": "tryghost", - "type": "site", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2368,7 +2368,7 @@ "id": "5c2ca6e0e015a6761618241f", "key": "labs", "value": "{\"publicAPI\": true}", - "type": "labs", + "type": "object", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2378,7 +2378,7 @@ "id": "5c2ca6e0e015a67616182420", "key": "navigation", "value": "[{\"label\":\"Home\", \"url\":\"/\"},{\"label\":\"Tag\", \"url\":\"/tag/getting-started/\"}, {\"label\":\"Author\", \"url\":\"/author/ghost/\"},{\"label\":\"Help\", \"url\":\"https://ghost.org/docs/\"}]", - "type": "site", + "type": "array", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2388,7 +2388,7 @@ "id": "5c2ca6e0e015a67616182421", "key": "slack", "value": "[{\"url\":\"\", \"username\":\"Ghost\"}]", - "type": "slack", + "type": "array", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2398,7 +2398,7 @@ "id": "5c2ca6e0e015a67616182422", "key": "unsplash", "value": "{\"isActive\": true}", - "type": "unsplash", + "type": "object", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2408,7 +2408,7 @@ "id": "5c2ca6e0e015a67616182423", "key": "active_theme", "value": "casper", - "type": "theme", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2418,7 +2418,7 @@ "id": "5c2ca6e0e015a67616182424", "key": "active_apps", "value": "[]", - "type": "app", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2428,7 +2428,7 @@ "id": "5c2ca6e0e015a67616182425", "key": "installed_apps", "value": "[]", - "type": "app", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2438,7 +2438,7 @@ "id": "5c2ca6e0e015a67616182426", "key": "is_private", "value": "false", - "type": "private", + "type": "boolean", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2448,7 +2448,7 @@ "id": "5c2ca6e0e015a67616182427", "key": "password", "value": "", - "type": "private", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2458,7 +2458,7 @@ "id": "5c2ca6e0e015a67616182428", "key": "public_hash", "value": "a7cb7ad9a319203bc4f1d1a90c4116", - "type": "private", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2468,7 +2468,7 @@ "id": "5c2ca6e0e015a67616182429", "key": "members_public_key", "value": "-----BEGIN RSA PUBLIC KEY-----\nMIGJAoGBAJ/6gCIMJyUZsR+lwV/ObX1mYSt7MFcy7LcPeDvepSruyabB9Z98Tit6Npfr79cc\nvxt95S8oUxXqRKNQgrDyJG6NJLMx9rcU2OMmdLAG5wJVlBz0D1eOwIaGKDzjZL9B42QIJPsy\nFQiNntWGYgiIPjpUT2u4rnsV0ATo9TB/bk3xAgMBAAE=\n-----END RSA PUBLIC KEY-----\n", - "type": "core", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z", @@ -2478,7 +2478,7 @@ "id": "5c2ca6e0e015a6761618242a", "key": "members_private_key", "value": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQCf+oAiDCclGbEfpcFfzm19ZmErezBXMuy3D3g73qUq7smmwfWffE4rejaX\n6+/XHL8bfeUvKFMV6kSjUIKw8iRujSSzMfa3FNjjJnSwBucCVZQc9A9XjsCGhig842S/QeNk\nCCT7MhUIjZ7VhmIIiD46VE9ruK57FdAE6PUwf25N8QIDAQABAoGAZm70Gljju6qusg/lOJ4p\nlzC1qSywsDTIQxKhrtwJr+rDrYXl6x+hwc74I+CLapZae5Tp6X8NbCvblSKY/AmfbzEw4QnQ\nybQC5WVC/VPl6jWYIodpwUM7RdUNYqWMiY07XnSwL2ejXszW3MO3NPGqh1irp0U4RwutaRfV\n4D6xDMECQQDh4PdiYJetxrruQBg3u5E4VaX/M1xzRGqo4jCdaO3e4+M1WymcPGmHJC3cypzg\n0dEdkhD9KH1AziSEjEeu/9kZAkEAtU/V+nAsdxsUby6LMBIB+3y6DcQwwygobfwkD2A+3qVQ\nTHxNwh6kL2Xif9U9d4+w1XQE8kwmZsKoQ9z8PC2+mQJAHuGi8NBD7H4/EFOy++uo7wrGpx1e\nhmPUMUK7Ysn1u4NsjN7p0XJw+wj3PDh3OkV1UZWmvPXMKhAE7ho/sq1IAQJATURloyGUwXln\n3u3N4UF7WMpRm7ZFNZXyjNSMJYVVpZp7uuyqUpSuUYiw2ttsI3y31m9oAD4Vi2tfO/R8BcVU\n2QJBAM4tqJaEy4jQ4aa9XEfU0/LU1TwZbeHZJ8Y+pDarHfYiHShQ2JKlMDAeh/DNqD0VEpeI\nSeO5Hxbv/56NVT+pNQE=\n-----END RSA PRIVATE KEY-----\n", - "type": "core", + "type": "string", "created_at": "2019-01-02T11:56:16.000Z", "created_by": "1", "updated_at": "2019-01-02T11:56:16.000Z",