feat: integrate relation syncing in default config type (permission relations)

pull/25/head
Boaz Poolman 2021-11-02 00:33:13 +01:00
parent db9745ef81
commit eff6cb50eb
6 changed files with 168 additions and 183 deletions

View File

@ -1,5 +1,7 @@
const { logMessage, sanitizeConfig } = require('../utils'); const { logMessage, sanitizeConfig, dynamicSort } = require('../utils');
const difference = require('../utils/getObjectDiff'); const difference = require('../utils/getObjectDiff');
const arrayDifference = require('../utils/getArrayDiff');
const ConfigType = class ConfigType { const ConfigType = class ConfigType {
constructor(queryString, configPrefix, uid, jsonFields, relations) { constructor(queryString, configPrefix, uid, jsonFields, relations) {
@ -10,7 +12,7 @@ const ConfigType = class ConfigType {
this.configPrefix = configPrefix; this.configPrefix = configPrefix;
this.uid = uid; this.uid = uid;
this.jsonFields = jsonFields || []; 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} configName - The name of the config file.
* @param {string} configContent - The JSON content of the config file. * @param {string} configContent - The JSON content of the config file.
* @returns {void} * @returns {void}
*/ */
importSingle = async (configName, configContent) => { importSingle = async (configName, configContent) => {
// Check if the config should be excluded. // Check if the config should be excluded.
const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${configName}`); const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${configName}`);
if (shouldExclude) return; if (shouldExclude) return;
const queryAPI = strapi.query(this.queryString); const queryAPI = strapi.query(this.queryString);
const configExists = await queryAPI let existingConfig = await queryAPI
.findOne({ where: { [this.uid]: configName } }); .findOne({
where: { [this.uid]: configName },
populate: this.relations.map(({ relationName }) => relationName),
});
if (configExists && configContent === null) { if (existingConfig && configContent === null) {
await queryAPI.delete({ where: { [this.uid]: configName } }); 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; return;
} }
if (!configExists) { if (!existingConfig) {
// Format JSON fields.
const query = { ...configContent }; const query = { ...configContent };
this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field])); 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 { } else {
// Format JSON fields.
configContent = sanitizeConfig(configContent);
const query = { ...configContent }; const query = { ...configContent };
this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field])); 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. * @returns {object} Object with key value pairs of configs.
*/ */
getAllFromDatabase = async () => { getAllFromDatabase = async () => {
const AllConfig = await strapi.query(this.queryString).findMany({ _limit: -1 }); const AllConfig = await strapi.query(this.queryString).findMany({ limit: 0 });
const configs = {}; const configs = {};
Object.values(AllConfig).map((config) => { await Promise.all(Object.values(AllConfig).map(async (config) => {
// Check if the config should be excluded. // Check if the config should be excluded.
const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${config[this.uid]}`); const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${this.configPrefix}.${config[this.uid]}`);
if (shouldExclude) return; 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 }; relations.map((relation) => sanitizeConfig(relation));
this.jsonFields.map((field) => formattedObject[field] = JSON.parse(config[field])); 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; return configs;
} }

View File

@ -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;

View File

@ -1,14 +1,23 @@
'use strict'; 'use strict';
const ConfigType = require("../services/type"); const ConfigType = require("../services/type");
const UserRoleConfigType = require("./user-role");
const AdminRoleConfigType = require("./admin-role");
module.exports = { module.exports = {
'i18n-locale': new ConfigType('plugin::i18n.locale', 'i18n-locale', 'code'), 'i18n-locale': new ConfigType('plugin::i18n.locale', 'i18n-locale', 'code'),
'core-store': new ConfigType('strapi::core-store', 'core-store', 'key', ['value']), 'core-store': new ConfigType('strapi::core-store', 'core-store', 'key', ['value']),
'user-role': new UserRoleConfigType('plugin::users-permissions.role', 'user-role', 'type'), 'user-role': new ConfigType(
'admin-role': new AdminRoleConfigType( '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',
'admin-role', 'admin-role',
'code', 'code',
@ -16,6 +25,8 @@ module.exports = {
[{ [{
queryString: 'admin::permission', queryString: 'admin::permission',
relationName: 'permissions', relationName: 'permissions',
parentName: 'role',
relationSortField: 'action',
}] }]
), ),
}; };

View File

@ -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;

View File

@ -0,0 +1,7 @@
const difference = (arrayOne, arrayTwo, compareKey) => {
return arrayOne.filter(({ [compareKey]: id1 }) => {
return !arrayTwo.some(({ [compareKey]: id2 }) => id2 === id1);
});
};
module.exports = difference;

View File

@ -10,12 +10,58 @@ const getService = (name) => {
const logMessage = (msg = '') => `[strapi-plugin-config-sync]: ${msg}`; 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.id; delete config.id;
delete config.updatedAt; delete config.updatedAt;
delete config.createdAt; 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; return config;
}; };
@ -24,4 +70,6 @@ module.exports = {
getCoreStore, getCoreStore,
logMessage, logMessage,
sanitizeConfig, sanitizeConfig,
sortByKeys,
dynamicSort,
}; };