From 4f0cce1b54e4978b5a63f560ca70472c4f6090b9 Mon Sep 17 00:00:00 2001 From: Boaz Poolman Date: Wed, 24 Mar 2021 16:22:03 +0100 Subject: [PATCH 1/8] Add import/export services for roles, permissions & webhooks. --- config/config.json | 5 ++ config/functions/bootstrap.js | 8 +-- config/routes.json | 4 +- controllers/config.js | 19 ++--- services/config.js | 78 --------------------- services/core-store.js | 65 ++++++++++++++++++ services/main.js | 126 ++++++++++++++++++++++++++++++++++ services/role-permissions.js | 85 +++++++++++++++++++++++ services/webhooks.js | 62 +++++++++++++++++ 9 files changed, 352 insertions(+), 100 deletions(-) delete mode 100644 services/config.js create mode 100644 services/core-store.js create mode 100644 services/main.js create mode 100644 services/role-permissions.js create mode 100644 services/webhooks.js diff --git a/config/config.json b/config/config.json index f0a37b5..e42c20e 100644 --- a/config/config.json +++ b/config/config.json @@ -2,5 +2,10 @@ "destination": "extensions/config-sync/files/", "minify": false, "importOnBootstrap": false, + "include": [ + "core-store", + "role-permissions", + "webhooks" + ], "exclude": [] } diff --git a/config/functions/bootstrap.js b/config/functions/bootstrap.js index 9324aff..d8b9dee 100644 --- a/config/functions/bootstrap.js +++ b/config/functions/bootstrap.js @@ -12,14 +12,10 @@ const fs = require('fs'); * See more details here: https://strapi.io/documentation/v3.x/concepts/configurations.html#bootstrap */ -module.exports = () => { +module.exports = async () => { if (strapi.plugins['config-sync'].config.importOnBootstrap) { if (fs.existsSync(strapi.plugins['config-sync'].config.destination)) { - const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination); - - configFiles.map((file) => { - strapi.plugins['config-sync'].services.config.importFromFile(file.slice(0, -5)); - }); + await strapi.plugins['config-sync'].services.main.importAllConfig(); } } }; diff --git a/config/routes.json b/config/routes.json index 7379637..fc7c0ea 100644 --- a/config/routes.json +++ b/config/routes.json @@ -3,7 +3,7 @@ { "method": "GET", "path": "/export", - "handler": "config.export", + "handler": "config.exportAll", "config": { "policies": [] } @@ -11,7 +11,7 @@ { "method": "GET", "path": "/import", - "handler": "config.import", + "handler": "config.importAll", "config": { "policies": [] } diff --git a/controllers/config.js b/controllers/config.js index 745315c..4636f53 100644 --- a/controllers/config.js +++ b/controllers/config.js @@ -13,13 +13,8 @@ module.exports = { * @param {object} ctx - Request context object. * @returns {void} */ - export: async (ctx) => { - const coreStoreAPI = strapi.query('core_store'); - const coreStore = await coreStoreAPI.find({ _limit: -1 }); - - Object.values(coreStore).map(async ({ key, value }) => { - await strapi.plugins['config-sync'].services.config.writeConfigFile(key, value); - }); + exportAll: async (ctx) => { + await strapi.plugins['config-sync'].services.main.exportAllConfig(); ctx.send({ message: `Config was successfully exported to ${strapi.plugins['config-sync'].config.destination}.` @@ -32,7 +27,7 @@ module.exports = { * @param {object} ctx - Request context object. * @returns {void} */ - import: async (ctx) => { + importAll: async (ctx) => { // Check for existance of the config file destination dir. if (!fs.existsSync(strapi.plugins['config-sync'].config.destination)) { ctx.send({ @@ -42,11 +37,7 @@ module.exports = { return; } - const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination); - - configFiles.map((file) => { - strapi.plugins['config-sync'].services.config.importFromFile(file.slice(0, -5)); - }); + await strapi.plugins['config-sync'].services.main.importAllConfig(); ctx.send({ message: 'Config was successfully imported.' @@ -75,7 +66,7 @@ module.exports = { const getConfigs = async () => { return Promise.all(configFiles.map(async (file) => { const formattedConfigName = file.slice(0, -5); // remove the .json extension. - const fileContents = await strapi.plugins['config-sync'].services.config.readConfigFile(formattedConfigName); + const fileContents = await strapi.plugins['config-sync'].services.main.readConfigFile(formattedConfigName); formattedConfigs[formattedConfigName] = fileContents; })); }; diff --git a/services/config.js b/services/config.js deleted file mode 100644 index ab1cc1c..0000000 --- a/services/config.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const util = require('util'); - -/** - * Main services for config import/export. - */ - -module.exports = { - /** - * Write a single config file. - * - * @param {string} configName - The name of the config file. - * @param {string} fileContents - The JSON content of the config file. - * @returns {void} - */ - writeConfigFile: async (configName, fileContents) => { - // Check if the config should be excluded. - const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(configName); - if (shouldExclude) return; - - // Check if the JSON content should be minified. - const json = - !strapi.plugins['config-sync'].config.minify ? - JSON.stringify(JSON.parse(fileContents), null, 2) - : fileContents; - - if (!fs.existsSync(strapi.plugins['config-sync'].config.destination)) { - fs.mkdirSync(strapi.plugins['config-sync'].config.destination, { recursive: true }); - } - - const writeFile = util.promisify(fs.writeFile); - await writeFile(`${strapi.plugins['config-sync'].config.destination}${configName}.json`, json); - }, - - /** - * Read from a config file. - * - * @param {string} configName - The name of the config file. - * @returns {object} The JSON content of the config file. - */ - readConfigFile: async (configName) => { - const readFile = util.promisify(fs.readFile); - return await readFile(`${strapi.plugins['config-sync'].config.destination}${configName}.json`) - .then((data) => { - return JSON.parse(data); - }) - .catch(() => { - return null; - }); - }, - - /** - * Import a config file into the db. - * - * @param {string} configName - The name of the config file. - * @returns {void} - */ - importFromFile: async (configName) => { - // Check if the config should be excluded. - const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(configName); - if (shouldExclude) return; - - const coreStoreAPI = strapi.query('core_store'); - const fileContents = await strapi.plugins['config-sync'].services.config.readConfigFile(configName); - - const configExists = await strapi - .query('core_store') - .findOne({ key: configName }); - - if (!configExists) { - await coreStoreAPI.create({ key: configName, value: fileContents }); - } else { - await coreStoreAPI.update({ key: configName }, { value: fileContents }); - } - } -}; diff --git a/services/core-store.js b/services/core-store.js new file mode 100644 index 0000000..da97c3a --- /dev/null +++ b/services/core-store.js @@ -0,0 +1,65 @@ +'use strict'; + +const coreStoreQueryString = 'core_store'; +const configPrefix = 'core-store'; // Should be the same as the filename. + +/** + * Import/Export for core-store configs. + */ + +module.exports = { + /** + * Export all core-store config to files. + * + * @returns {void} + */ + exportAll: async () => { + const coreStore = await strapi.query(coreStoreQueryString).find({ _limit: -1 }); + + await Promise.all(Object.values(coreStore).map(async ({ id, ...config }) => { + config.value = JSON.parse(config.value); + await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, config.key, config); + })); + }, + + /** + * Import a single core-store config file into the db. + * + * @param {string} configName - The name of the config file. + * @param {string} configContent - The JSON content of the config file. + * @returns {void} + */ + importSingle: async (configName, configContent) => { + const { value, ...fileContent } = configContent; + const coreStoreAPI = strapi.query(coreStoreQueryString); + + const configExists = await coreStoreAPI + .findOne({ key: configName, environment: fileContent.environment }); + + if (!configExists) { + await coreStoreAPI.create({ value: JSON.stringify(value), ...fileContent }); + } else { + await coreStoreAPI.update({ key: configName }, { value: JSON.stringify(value), ...fileContent }); + } + }, + + /** + * Import all core-store config files into the db. + * + * @returns {void} + */ + importAll: async () => { + // The main.importAllConfig service will loop the core-store.importSingle service. + await strapi.plugins['config-sync'].services.main.importAllConfig(configPrefix); + }, + + /** + * Export a single core-store config to a file. + * + * @param {string} configName - The name of the config file. + * @returns {void} + */ + exportSingle: async (configName) => { + // @TODO: write export for a single core-store config. + }, +}; diff --git a/services/main.js b/services/main.js new file mode 100644 index 0000000..5954662 --- /dev/null +++ b/services/main.js @@ -0,0 +1,126 @@ +'use strict'; + +const fs = require('fs'); +const util = require('util'); + +/** + * Main services for config import/export. + */ + +module.exports = { + /** + * Write a single config file. + * + * @param {string} configType - The type of the config. + * @param {string} configName - The name of the config file. + * @param {string} fileContents - The JSON content of the config file. + * @returns {void} + */ + writeConfigFile: async (configType, configName, fileContents) => { + // Check if the config should be excluded. + const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configType}.${configName}`); + if (shouldExclude) return; + + // Check if the JSON content should be minified. + const json = + !strapi.plugins['config-sync'].config.minify ? + JSON.stringify(fileContents, null, 2) + : JSON.stringify(fileContents); + + if (!fs.existsSync(strapi.plugins['config-sync'].config.destination)) { + fs.mkdirSync(strapi.plugins['config-sync'].config.destination, { recursive: true }); + } + + const writeFile = util.promisify(fs.writeFile); + await writeFile(`${strapi.plugins['config-sync'].config.destination}${configType}.${configName}.json`, json) + .then(() => { + // @TODO: + // Add logging for successfull config export. + }) + .catch(() => { + // @TODO: + // Add logging for failed config export. + }); + }, + + /** + * Read from a config file. + * + * @param {string} configType - The type of config. + * @param {string} configName - The name of the config file. + * @returns {object} The JSON content of the config file. + */ + readConfigFile: async (configType, configName) => { + const readFile = util.promisify(fs.readFile); + return await readFile(`${strapi.plugins['config-sync'].config.destination}${configType}.${configName}.json`) + .then((data) => { + return JSON.parse(data); + }) + .catch(() => { + return null; + }); + }, + + /** + * Import all config files into the db. + * + * @returns {void} + */ + importAllConfig: async (configType = null) => { + const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination); + + configFiles.map((file) => { + const type = file.split('.')[0]; // Grab the first part of the filename. + const name = file.split(/\.(.+)/)[1].split('.').slice(0, -1).join('.'); // Grab the rest of the filename minus the file extension. + + if (configType && configType !== type) { + return; + } + + strapi.plugins['config-sync'].services.main.importSingleConfig(type, name); + }); + }, + + /** + * Export all config files. + * + * @returns {void} + */ + exportAllConfig: async (configType = null) => { + await Promise.all(strapi.plugins['config-sync'].config.include.map(async (type) => { + if (configType && configType !== type) { + return; + } + + await strapi.plugins['config-sync'].services[type].exportAll(); + })); + }, + + /** + * Import a single config file into the db. + * + * @param {string} configType - The type of config. + * @param {string} configName - The name of the config file. + * @returns {void} + */ + importSingleConfig: async (configType, configName) => { + // Check if the config should be excluded. + const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configType}.${configName}`); + if (shouldExclude) return; + + const fileContents = await strapi.plugins['config-sync'].services.main.readConfigFile(configType, configName); + + await strapi.plugins['config-sync'].services[configType].importSingle(configName, fileContents); + }, + + /** + * Export a single config file. + * + * @param {string} configType - The type of config. + * @param {string} configName - The name of the config file. + * @returns {void} + */ + exportSingleConfig: async (configType, configName) => { + + }, +}; diff --git a/services/role-permissions.js b/services/role-permissions.js new file mode 100644 index 0000000..598e488 --- /dev/null +++ b/services/role-permissions.js @@ -0,0 +1,85 @@ +'use strict'; + +const { sanitizeEntity } = require('strapi-utils'); + +const configPrefix = 'role-permissions'; // Should be the same as the filename. + +/** + * Import/Export for role-permissions configs. + */ + +module.exports = { + /** + * Export all role-permissions config to files. + * + * @returns {void} + */ + exportAll: async () => { + const service = + strapi.plugins['users-permissions'].services.userspermissions; + + const [roles, plugins] = await Promise.all([ + service.getRoles(), + service.getPlugins(), + ]); + + const rolesWithPermissions = await Promise.all( + roles.map(async role => service.getRole(role.id, plugins)) + ); + + const sanitizedRolesArray = rolesWithPermissions.map(role => + sanitizeEntity(role, { + model: strapi.plugins['users-permissions'].models.role, + }) + ); + + await Promise.all(sanitizedRolesArray.map(async (config) => { + await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, config.type, config); + })); + }, + + /** + * Import a single role-permissions config file into the db. + * + * @param {string} configName - The name of the config file. + * @param {string} configContent - The JSON content of the config file. + * @returns {void} + */ + importSingle: async (configName, configContent) => { + const service = + strapi.plugins['users-permissions'].services.userspermissions; + + const role = await strapi + .query('role', 'users-permissions') + .findOne({ type: configName }); + + const users = role ? role.users : []; + configContent.users = users; + + if (!role) { + await service.createRole(configContent); + } else { + await service.updateRole(role.id, configContent); + } + }, + + /** + * Import all role-permissions config files into the db. + * + * @returns {void} + */ + importAll: async () => { + // The main.importAllConfig service will loop the role-permissions.importSingle service. + await strapi.plugins['config-sync'].services.main.importAllConfig(configPrefix); + }, + + /** + * Export a single role-permissions config to a file. + * + * @param {string} configName - The name of the config file. + * @returns {void} + */ + exportSingle: async (configName) => { + // @TODO: write export for a single role-permissions config. + }, +}; diff --git a/services/webhooks.js b/services/webhooks.js new file mode 100644 index 0000000..082e1f1 --- /dev/null +++ b/services/webhooks.js @@ -0,0 +1,62 @@ +'use strict'; + +/** + * Import/Export for webhook configs. + */ + +const webhookQueryString = 'strapi_webhooks'; +const configPrefix = 'webhooks'; // Should be the same as the filename. + +module.exports = { + /** + * Export all webhooks to config files. + * + * @returns {void} + */ + exportAll: async () => { + const webhooks = await strapi.query(webhookQueryString).find({ _limit: -1 }); + + await Promise.all(Object.values(webhooks).map(async (config) => { + await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, config.id, config); + })); + }, + + /** + * Import a single webhook config file into the db. + * + * @param {string} configName - The name of the config file. + * @param {string} configContent - The JSON content of the config file. + * @returns {void} + */ + importSingle: async (configName, configContent) => { + const webhookAPI = strapi.query(webhookQueryString); + + const configExists = await webhookAPI.findOne({ id: configName }); + + if (!configExists) { + await webhookAPI.create(configContent); + } else { + await webhookAPI.update({ id: configName }, configContent); + } + }, + + /** + * Import all webhook config files into the db. + * + * @returns {void} + */ + importAll: async () => { + // The main.importAllConfig service will loop the webhooks.importSingle service. + await strapi.plugins['config-sync'].services.main.importAllConfig(configPrefix); + }, + + /** + * Export a single webhook into a config file. + * + * @param {string} configName - The name of the config file. + * @returns {void} + */ + exportSingle: async (configName) => { + // @TODO: write export for a single webhook config. + }, +}; From 8a705e91b98ec6857d1d1ecd87ff78289112dbc1 Mon Sep 17 00:00:00 2001 From: Boaz Poolman Date: Wed, 24 Mar 2021 18:36:16 +0100 Subject: [PATCH 2/8] Add getAllFromDatabase service for each config type --- services/core-store.js | 16 ++++++++++++++++ services/role-permissions.js | 33 +++++++++++++++++++++++++++++++++ services/webhooks.js | 16 ++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/services/core-store.js b/services/core-store.js index da97c3a..e363651 100644 --- a/services/core-store.js +++ b/services/core-store.js @@ -43,6 +43,22 @@ module.exports = { } }, + /** + * Get all core-store config from the db. + * + * @returns {object} Object with key value pairs of configs. + */ + getAllFromDatabase: async () => { + const coreStore = await strapi.query(coreStoreQueryString).find({ _limit: -1 }); + let configs = {}; + + Object.values(coreStore).map( ({ id, value, ...config }) => { + configs[`${configPrefix}.${config.key}`] = { value: JSON.parse(value), ...config }; + }); + + return configs; + }, + /** * Import all core-store config files into the db. * diff --git a/services/role-permissions.js b/services/role-permissions.js index 598e488..18234b4 100644 --- a/services/role-permissions.js +++ b/services/role-permissions.js @@ -63,6 +63,39 @@ module.exports = { } }, + /** + * Get all role-permissions config from the db. + * + * @returns {object} Object with key value pairs of configs. + */ + getAllFromDatabase: async () => { + const service = + strapi.plugins['users-permissions'].services.userspermissions; + + const [roles, plugins] = await Promise.all([ + service.getRoles(), + service.getPlugins(), + ]); + + const rolesWithPermissions = await Promise.all( + roles.map(async role => service.getRole(role.id, plugins)) + ); + + const sanitizedRolesArray = rolesWithPermissions.map(role => + sanitizeEntity(role, { + model: strapi.plugins['users-permissions'].models.role, + }) + ); + + let configs = {}; + + Object.values(sanitizedRolesArray).map((config) => { + configs[`${configPrefix}.${config.type}`] = config; + }); + + return configs; + }, + /** * Import all role-permissions config files into the db. * diff --git a/services/webhooks.js b/services/webhooks.js index 082e1f1..e8068a5 100644 --- a/services/webhooks.js +++ b/services/webhooks.js @@ -40,6 +40,22 @@ module.exports = { } }, + /** + * Get all webhook config from the db. + * + * @returns {object} Object with key value pairs of configs. + */ + getAllFromDatabase: async () => { + const webhooks = await strapi.query(webhookQueryString).find({ _limit: -1 }); + let configs = {}; + + Object.values(webhooks).map( (config) => { + configs[`${configPrefix}.${config.id}`] = config; + }); + + return configs; + }, + /** * Import all webhook config files into the db. * From 850c18ce5833174c3c3d2cb50cc146dae356cc1c Mon Sep 17 00:00:00 2001 From: Boaz Poolman Date: Wed, 24 Mar 2021 18:37:14 +0100 Subject: [PATCH 3/8] Add main getConfig services --- services/main.js | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/services/main.js b/services/main.js index 5954662..2a95e3d 100644 --- a/services/main.js +++ b/services/main.js @@ -61,6 +61,55 @@ module.exports = { }); }, + + /** + * Get all the config JSON from the filesystem. + * + * @returns {object} Object with key value pairs of configs. + */ + getAllConfigFromFiles: async () => { + const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination); + + const getConfigs = async () => { + let fileConfigs = {}; + + await Promise.all(configFiles.map(async (file) => { + const type = file.split('.')[0]; // Grab the first part of the filename. + const name = file.split(/\.(.+)/)[1].split('.').slice(0, -1).join('.'); // Grab the rest of the filename minus the file extension. + const fileContents = await strapi.plugins['config-sync'].services.main.readConfigFile(type, name); + fileConfigs[`${type}.${name}`] = fileContents; + })); + + return fileConfigs; + }; + + return await getConfigs(); + }, + + /** + * Get all the config JSON from the database. + * + * @returns {object} Object with key value pairs of configs. + */ + getAllConfigFromDatabase: async (configType = null) => { + const getConfigs = async () => { + let databaseConfigs = {}; + + await Promise.all(strapi.plugins['config-sync'].config.include.map(async (type) => { + if (configType && configType !== type) { + return; + } + + const config = await strapi.plugins['config-sync'].services[type].getAllFromDatabase(); + databaseConfigs = Object.assign(config, databaseConfigs); + })); + + return databaseConfigs; + } + + return await getConfigs(); + }, + /** * Import all config files into the db. * From 8ca6317b742763e3d3e3b294618b5ff7b1cd6dd3 Mon Sep 17 00:00:00 2001 From: Boaz Poolman Date: Wed, 24 Mar 2021 18:38:36 +0100 Subject: [PATCH 4/8] Refactor get config API calls to show the diff --- config/routes.json | 12 ++-------- controllers/config.js | 53 ++++++++++++++++-------------------------- utils/getObjectDiff.js | 23 ++++++++++++++++++ 3 files changed, 45 insertions(+), 43 deletions(-) create mode 100644 utils/getObjectDiff.js diff --git a/config/routes.json b/config/routes.json index fc7c0ea..eaa1a97 100644 --- a/config/routes.json +++ b/config/routes.json @@ -18,16 +18,8 @@ }, { "method": "GET", - "path": "/all/from-files", - "handler": "config.getConfigsFromFiles", - "config": { - "policies": [] - } - }, - { - "method": "GET", - "path": "/all/from-database", - "handler": "config.getConfigsFromDatabase", + "path": "/diff", + "handler": "config.getDiff", "config": { "policies": [] } diff --git a/controllers/config.js b/controllers/config.js index 4636f53..e5d212a 100644 --- a/controllers/config.js +++ b/controllers/config.js @@ -1,6 +1,7 @@ 'use strict'; const fs = require('fs'); +const difference = require('../utils/getObjectDiff'); /** * Main controllers for config import/export. @@ -45,12 +46,12 @@ module.exports = { }, /** - * Get all configs as defined in your filesystem. + * Get config diff between filesystem & db. * * @param {object} ctx - Request context object. - * @returns {object} Object with key value pairs of configs. + * @returns Object with key value pairs of config. */ - getConfigsFromFiles: async (ctx) => { + getDiff: async (ctx) => { // Check for existance of the config file destination dir. if (!fs.existsSync(strapi.plugins['config-sync'].config.destination)) { ctx.send({ @@ -59,38 +60,24 @@ module.exports = { return; } - - const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination); - let formattedConfigs = {}; - const getConfigs = async () => { - return Promise.all(configFiles.map(async (file) => { - const formattedConfigName = file.slice(0, -5); // remove the .json extension. - const fileContents = await strapi.plugins['config-sync'].services.main.readConfigFile(formattedConfigName); - formattedConfigs[formattedConfigName] = fileContents; - })); + const formattedDiff = { + fileConfig: {}, + databaseConfig: {}, + diff: {} }; - - await getConfigs(); - - ctx.send(formattedConfigs); - }, - - /** - * Get all configs as defined in your database. - * - * @param {object} ctx - Request context object. - * @returns {object} Object with key value pairs of configs. - */ - getConfigsFromDatabase: async (ctx) => { - const coreStoreAPI = strapi.query('core_store'); - const coreStore = await coreStoreAPI.find({ _limit: -1 }); - let formattedConfigs = {}; - Object.values(coreStore).map(async ({ key, value }) => { - formattedConfigs[key] = JSON.parse(value); - }); + const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles(); + const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase(); - ctx.send(formattedConfigs); - } + const diff = difference(fileConfig, databaseConfig); + formattedDiff.diff = diff; + + Object.keys(diff).map((changedConfigName) => { + formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName]; + formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName]; + }) + + return formattedDiff; + }, }; diff --git a/utils/getObjectDiff.js b/utils/getObjectDiff.js new file mode 100644 index 0000000..ca101e9 --- /dev/null +++ b/utils/getObjectDiff.js @@ -0,0 +1,23 @@ +'use strict'; +const { transform, isEqual, isArray, isObject } = require('lodash'); + +/** + * Find difference between two objects + * @param {object} origObj - Source object to compare newObj against + * @param {object} newObj - New object with potential changes + * @return {object} differences + */ +const difference = (origObj, newObj) => { + function changes(newObj, origObj) { + let arrayIndexCounter = 0 + return transform(newObj, function (result, value, key) { + if (!isEqual(value, origObj[key])) { + let resultKey = isArray(origObj) ? arrayIndexCounter++ : key + result[resultKey] = (isObject(value) && isObject(origObj[key])) ? changes(value, origObj[key]) : value + } + }) + } + return changes(newObj, origObj) +} + +module.exports = difference; \ No newline at end of file From 3986ade2c8e3de71a8c939fdc9bdf2bb1d411227 Mon Sep 17 00:00:00 2001 From: Boaz Poolman Date: Wed, 24 Mar 2021 18:41:03 +0100 Subject: [PATCH 5/8] Update frontend --- admin/src/components/ActionButtons/index.js | 4 +-- admin/src/components/ConfigList/index.js | 30 +++++++++++++-------- admin/src/containers/ConfigPage/index.js | 14 ++++------ admin/src/helpers/getObjectDiff.js | 22 --------------- admin/src/state/actions/Config.js | 25 +++++------------ admin/src/state/reducers/Config/index.js | 12 +++------ 6 files changed, 37 insertions(+), 70 deletions(-) delete mode 100644 admin/src/helpers/getObjectDiff.js diff --git a/admin/src/components/ActionButtons/index.js b/admin/src/components/ActionButtons/index.js index ced5c85..a4b043f 100644 --- a/admin/src/components/ActionButtons/index.js +++ b/admin/src/components/ActionButtons/index.js @@ -23,8 +23,8 @@ const ActionButtons = ({ diff }) => { return ( -