Add import/export services for roles, permissions & webhooks.

pull/1/head
Boaz Poolman 2021-03-24 16:22:03 +01:00
parent c85fa56223
commit 4f0cce1b54
9 changed files with 352 additions and 100 deletions

View File

@ -2,5 +2,10 @@
"destination": "extensions/config-sync/files/",
"minify": false,
"importOnBootstrap": false,
"include": [
"core-store",
"role-permissions",
"webhooks"
],
"exclude": []
}

View File

@ -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();
}
}
};

View File

@ -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": []
}

View File

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

View File

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

65
services/core-store.js Normal file
View File

@ -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.
},
};

126
services/main.js Normal file
View File

@ -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) => {
},
};

View File

@ -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.
},
};

62
services/webhooks.js Normal file
View File

@ -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.
},
};