diff --git a/server/services/type.js b/server/services/type.js index fd6e57e..ebf382d 100644 --- a/server/services/type.js +++ b/server/services/type.js @@ -1,5 +1,7 @@ -const { logMessage, sanitizeConfig } = require('../utils'); +const { logMessage, sanitizeConfig, dynamicSort } = require('../utils'); const difference = require('../utils/getObjectDiff'); +const arrayDifference = require('../utils/getArrayDiff'); + const ConfigType = class ConfigType { constructor(queryString, configPrefix, uid, jsonFields, relations) { @@ -10,7 +12,7 @@ const ConfigType = class ConfigType { this.configPrefix = configPrefix; this.uid = uid; this.jsonFields = jsonFields || []; - this.relations = relations || {}; + this.relations = relations || []; } /** @@ -55,60 +57,136 @@ const ConfigType = class ConfigType { } /** - * Import a single core-store config file into the db. + * 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) => { + importSingle = async (configName, configContent) => { // Check if the config should be excluded. const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${configName}`); if (shouldExclude) return; const queryAPI = strapi.query(this.queryString); - const configExists = await queryAPI - .findOne({ where: { [this.uid]: configName } }); + let existingConfig = await queryAPI + .findOne({ + where: { [this.uid]: configName }, + populate: this.relations.map(({ relationName }) => relationName), + }); - if (configExists && configContent === null) { - await queryAPI.delete({ where: { [this.uid]: configName } }); + if (existingConfig && configContent === null) { + const entity = await queryAPI.findOne({ + where: { [this.uid]: configName }, + populate: this.relations.map(({ relationName }) => relationName), + }); + + await Promise.all(this.relations.map(async ({ queryString, parentName }) => { + const relations = await strapi.query(queryString).findMany({ + where: { + [parentName]: entity.id, + }, + }); + + await Promise.all(relations.map(async (relation) => { + await strapi.query(queryString).delete({ + where: { id: relation.id }, + }); + })); + })); + + await queryAPI.delete({ + where: { id: entity.id }, + }); return; } - if (!configExists) { + if (!existingConfig) { + // Format JSON fields. const query = { ...configContent }; this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field])); - await queryAPI.create({ data: query }); + + // Create entity. + this.relations.map(({ relationName }) => delete query[relationName]); + const newEntity = await queryAPI.create({ data: query }); + + // Create relation entities. + this.relations.map(async ({ queryString, relationName, parentName }) => { + const relationQueryApi = strapi.query(queryString); + + configContent[relationName].map(async (relationEntity) => { + const relationQuery = { ...relationEntity, [parentName]: newEntity }; + await relationQueryApi.create({ data: relationQuery }); + }); + }); } else { + // Format JSON fields. + configContent = sanitizeConfig(configContent); const query = { ...configContent }; this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field])); - await queryAPI.update({ where: { [this.uid]: configName }, data: { ...query } }); + + // Update entity. + this.relations.map(({ relationName }) => delete query[relationName]); + const entity = await queryAPI.update({ where: { [this.uid]: configName }, data: query }); + + // Delete/create relations. + this.relations.map(async ({ queryString, relationName, parentName, relationSortField }) => { + const relationQueryApi = strapi.query(queryString); + existingConfig = sanitizeConfig(existingConfig, relationName, relationSortField); + configContent = sanitizeConfig(configContent, relationName, relationSortField); + + const configToAdd = arrayDifference(configContent[relationName], existingConfig[relationName], relationSortField); + const configToDelete = arrayDifference(existingConfig[relationName], configContent[relationName], relationSortField); + + configToDelete.map(async (config) => { + await relationQueryApi.delete({ + where: { + [relationSortField]: config[relationSortField], + [parentName]: entity.id, + }, + }); + }); + + configToAdd.map(async (config) => { + await relationQueryApi.create({ + data: { ...config, [parentName]: entity.id }, + }); + }); + }); } } /** - * Get all core-store config from the db. + * Get all role-permissions config from the db. * * @returns {object} Object with key value pairs of configs. */ getAllFromDatabase = async () => { - const AllConfig = await strapi.query(this.queryString).findMany({ _limit: -1 }); + const AllConfig = await strapi.query(this.queryString).findMany({ limit: 0 }); const configs = {}; - Object.values(AllConfig).map((config) => { + await Promise.all(Object.values(AllConfig).map(async (config) => { // Check if the config should be excluded. const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${config[this.uid]}`); if (shouldExclude) return; - config = sanitizeConfig(config); + const formattedConfig = { ...sanitizeConfig(config) }; + await Promise.all(this.relations.map(async ({ queryString, relationName, relationSortField, parentName }) => { + const relations = await strapi.query(queryString).findMany({ + where: { [parentName]: { [this.uid]: config[this.uid] } }, + }); - const formattedObject = { ...config }; - this.jsonFields.map((field) => formattedObject[field] = JSON.parse(config[field])); + relations.map((relation) => sanitizeConfig(relation)); + relations.sort(dynamicSort(relationSortField)); + formattedConfig[relationName] = relations; + })); + + this.jsonFields.map((field) => formattedConfig[field] = JSON.parse(config[field])); + configs[`${this.configPrefix}.${config[this.uid]}`] = formattedConfig; + })); - configs[`${this.configPrefix}.${config[this.uid]}`] = formattedObject; - }); return configs; } diff --git a/server/types/admin-role.js b/server/types/admin-role.js deleted file mode 100644 index d1188aa..0000000 --- a/server/types/admin-role.js +++ /dev/null @@ -1,83 +0,0 @@ -const { sanitizeConfig } = require('../utils'); -const ConfigType = require("../services/type"); - -const AdminRolePermissionsConfigType = class AdminRolePermissionsConfigType extends ConfigType { - /** - * 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) => { - // Check if the config should be excluded. - const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${configName}`); - if (shouldExclude) return; - - const queryAPI = strapi.query(this.queryString); - - const existingConfig = await queryAPI - .findOne({ where: { [this.uid]: configName } }); - - if (existingConfig && configContent === null) { - await queryAPI.delete({ - where: { [this.uid]: configName }, - populate: this.relations.map(({ relationName }) => relationName), - }); - - return; - } - - if (!existingConfig) { - // Format JSON fields. - const query = { ...configContent }; - this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field])); - - this.relations.map(({ queryString }) => { - const queryAPI = strapi.query(queryString); - - // Compare relations - // Make changes to the db - }); - - await queryAPI.create({ data: query }); - } else { - const query = { ...configContent }; - this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field])); - await queryAPI.update({ where: { [this.uid]: configName }, data: { ...query } }); - } - } - - /** - * Get all role-permissions config from the db. - * - * @returns {object} Object with key value pairs of configs. - */ - getAllFromDatabase = async () => { - const AllConfig = await strapi.query(this.queryString).findMany({ limit: 0 }); - const configs = {}; - - await Promise.all(Object.values(AllConfig).map(async (config) => { - // Check if the config should be excluded. - const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${config[this.uid]}`); - if (shouldExclude) return; - - config = sanitizeConfig(config); - - const existingPermissions = await strapi.admin.services.permission.findMany({ - where: { role: { code: config.code } }, - }); - - existingPermissions.map((permission) => sanitizeConfig(permission)); - - const formattedObject = { ...config, permissions: existingPermissions }; - this.jsonFields.map((field) => formattedObject[field] = JSON.parse(config[field])); - - configs[`${this.configPrefix}.${config[this.uid]}`] = formattedObject; - })); - - return configs; - } -}; - -module.exports = AdminRolePermissionsConfigType; diff --git a/server/types/index.js b/server/types/index.js index 6bd89d3..fa90a9c 100644 --- a/server/types/index.js +++ b/server/types/index.js @@ -1,14 +1,23 @@ 'use strict'; const ConfigType = require("../services/type"); -const UserRoleConfigType = require("./user-role"); -const AdminRoleConfigType = require("./admin-role"); module.exports = { 'i18n-locale': new ConfigType('plugin::i18n.locale', 'i18n-locale', 'code'), 'core-store': new ConfigType('strapi::core-store', 'core-store', 'key', ['value']), - 'user-role': new UserRoleConfigType('plugin::users-permissions.role', 'user-role', 'type'), - 'admin-role': new AdminRoleConfigType( + 'user-role': new ConfigType( + 'plugin::users-permissions.role', + 'user-role', + 'type', + [], + [{ + queryString: 'plugin::users-permissions.permission', + relationName: 'permissions', + parentName: 'role', + relationSortField: 'action', + }] + ), + 'admin-role': new ConfigType( 'admin::role', 'admin-role', 'code', @@ -16,6 +25,8 @@ module.exports = { [{ queryString: 'admin::permission', relationName: 'permissions', + parentName: 'role', + relationSortField: 'action', }] ), }; diff --git a/server/types/user-role.js b/server/types/user-role.js deleted file mode 100644 index e781d3d..0000000 --- a/server/types/user-role.js +++ /dev/null @@ -1,76 +0,0 @@ -const { sanitizeConfig } = require('../utils'); -const ConfigType = require("../services/type"); - -const UserRolePermissionsConfigType = class UserRolePermissionsConfigType extends ConfigType { - /** - * 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) => { - // Check if the config should be excluded. - const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${configName}`); - if (shouldExclude) return; - - const roleService = strapi.plugin('users-permissions').service('role'); - - const role = await strapi - .query(this.queryString) - .findOne({ where: { type: configName } }); - - if (role && configContent === null) { - const publicRole = await strapi.query(this.queryString).findOne({ where: { type: 'public' } }); - const publicRoleID = publicRole.id; - - await roleService.deleteRole(role.id, publicRoleID); - - return; - } - - const users = role ? role.users : []; - configContent.users = users; - - if (!role) { - await roleService.createRole(configContent); - } else { - await roleService.updateRole(role.id, configContent); - } - } - - /** - * Get all role-permissions config from the db. - * - * @returns {object} Object with key value pairs of configs. - */ - getAllFromDatabase = async () => { - const UPService = strapi.plugin('users-permissions').service('users-permissions'); - const roleService = strapi.plugin('users-permissions').service('role'); - - const [roles, plugins] = await Promise.all([ - roleService.getRoles(), - UPService.getPlugins(), - ]); - - const rolesWithPermissions = await Promise.all( - roles.map(async (role) => roleService.getRole(role.id, plugins)) - ); - - const configs = {}; - - rolesWithPermissions.map(({ id, ...config }) => { - // Check if the config should be excluded. - const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${config.type}`); - if (shouldExclude) return; - - config = sanitizeConfig(config); - - configs[`${this.configPrefix}.${config.type}`] = config; - }); - - return configs; - } -}; - -module.exports = UserRolePermissionsConfigType; diff --git a/server/utils/getArrayDiff.js b/server/utils/getArrayDiff.js new file mode 100644 index 0000000..5dd2b38 --- /dev/null +++ b/server/utils/getArrayDiff.js @@ -0,0 +1,7 @@ +const difference = (arrayOne, arrayTwo, compareKey) => { + return arrayOne.filter(({ [compareKey]: id1 }) => { + return !arrayTwo.some(({ [compareKey]: id2 }) => id2 === id1); + }); +}; + +module.exports = difference; diff --git a/server/utils/index.js b/server/utils/index.js index 998dad1..7468744 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -10,12 +10,58 @@ const getService = (name) => { const logMessage = (msg = '') => `[strapi-plugin-config-sync]: ${msg}`; -const sanitizeConfig = (config) => { +const sortByKeys = (unordered) => { + return Object.keys(unordered).sort().reduce((obj, key) => { + obj[key] = unordered[key]; + return obj; + }, + {} + ); +}; + +const dynamicSort = (property) => { + let sortOrder = 1; + + if (property[0] === "-") { + sortOrder = -1; + property = property.substr(1); + } + + return (a, b) => { + if (sortOrder === -1) { + return b[property].localeCompare(a[property]); + } else { + return a[property].localeCompare(b[property]); + } + }; +}; + +const sanitizeConfig = (config, relation, relationSortField) => { delete config._id; delete config.id; delete config.updatedAt; delete config.createdAt; + if (relation) { + const formattedRelations = []; + + config[relation].map((relationEntity) => { + delete relationEntity._id; + delete relationEntity.id; + delete relationEntity.updatedAt; + delete relationEntity.createdAt; + relationEntity = sortByKeys(relationEntity); + + formattedRelations.push(relationEntity); + }); + + if (relationSortField) { + formattedRelations.sort(dynamicSort(relationSortField)); + } + + config[relation] = formattedRelations; + } + return config; }; @@ -24,4 +70,6 @@ module.exports = { getCoreStore, logMessage, sanitizeConfig, + sortByKeys, + dynamicSort, };