refactor(v4): Base migration

pull/25/head
Boaz Poolman 2021-10-14 14:37:00 +02:00
parent 9028adde12
commit 8c3c2e7e3f
20 changed files with 265 additions and 182 deletions

View File

@ -1,53 +1,62 @@
import React from 'react';
import { prefixPluginTranslations } from '@strapi/helper-plugin';
import pluginPkg from '../../package.json';
import pluginId from './helpers/pluginId';
import App from './containers/App';
import Initializer from './containers/Initializer';
import trads from './translations';
// import pluginPermissions from './permissions';
// import getTrad from './helpers/getTrad';
function Comp(props) {
return <App {...props} />;
}
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const { icon, name } = pluginPkg.strapi;
export default strapi => {
const pluginDescription =
pluginPkg.strapi.description || pluginPkg.description;
export default {
register(app) {
app.registerPlugin({
description: pluginDescription,
icon,
id: pluginId,
isReady: true,
isRequired: pluginPkg.strapi.required || false,
name,
});
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Config Sync',
},
Component: async () => {
const component = await import(
/* webpackChunkName: "config-sync-settings-page" */ './containers/App'
);
const plugin = {
icon,
name,
destination: `/plugins/${pluginId}`,
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
isReady: false,
layout: null,
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: Comp,
name: pluginPkg.strapi.name,
preventComponentRendering: false,
trads,
menu: {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`, // Endpoint of the link
icon,
name,
label: {
id: `${pluginId}.plugin.name`, // Refers to a i18n
defaultMessage: 'Config Sync',
},
},
],
},
};
return component;
},
permissions: [], // TODO: Add permission to view settings page.
});
},
bootstrap(app) {},
async registerTrads({ locales }) {
const importedTrads = await Promise.all(
locales.map((locale) => {
return import(
/* webpackChunkName: "config-sync-translation-[request]" */ `./translations/${locale}.json`
)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);
return strapi.registerPlugin(plugin);
return Promise.resolve(importedTrads);
},
};

View File

@ -1,13 +0,0 @@
{
"destination": "extensions/config-sync/files/",
"minify": false,
"importOnBootstrap": false,
"include": [
"core-store",
"role-permissions",
"i18n-locale"
],
"exclude": [
"core-store.plugin_users-permissions_grant"
]
}

View File

@ -1,28 +0,0 @@
{
"routes": [
{
"method": "GET",
"path": "/export",
"handler": "config.exportAll",
"config": {
"policies": []
}
},
{
"method": "GET",
"path": "/import",
"handler": "config.importAll",
"config": {
"policies": []
}
},
{
"method": "GET",
"path": "/diff",
"handler": "config.getDiff",
"config": {
"policies": []
}
}
]
}

18
server/config.js Normal file
View File

@ -0,0 +1,18 @@
'use strict';
module.exports = {
default: {
destination: "extensions/config-sync/files/",
minify: false,
importOnBootstrap: false,
include: [
"core-store",
"role-permissions",
"i18n-locale",
],
exclude: [
"core-store.plugin_users-permissions_grant",
],
},
validator() {},
};

View File

@ -18,7 +18,7 @@ module.exports = {
await strapi.plugins['config-sync'].services.main.exportAllConfig();
ctx.send({
message: `Config was successfully exported to ${strapi.plugins['config-sync'].config.destination}.`
message: `Config was successfully exported to ${strapi.plugins['config-sync'].config.destination}.`,
});
},
@ -32,16 +32,16 @@ module.exports = {
// Check for existance of the config file destination dir.
if (!fs.existsSync(strapi.plugins['config-sync'].config.destination)) {
ctx.send({
message: 'No config files were found.'
message: 'No config files were found.',
});
return;
}
await strapi.plugins['config-sync'].services.main.importAllConfig();
ctx.send({
message: 'Config was successfully imported.'
message: 'Config was successfully imported.',
});
},
@ -49,13 +49,16 @@ module.exports = {
* Get config diff between filesystem & db.
*
* @param {object} ctx - Request context object.
* @returns Object with key value pairs of config.
* @returns {object} formattedDiff - The formatted diff object.
* @returns {object} formattedDiff.fileConfig - The config as found in the filesystem.
* @returns {object} formattedDiff.databaseConfig - The config as found in the database.
* @returns {object} formattedDiff.diff - The diff between the file config and databse config.
*/
getDiff: async (ctx) => {
// Check for existance of the config file destination dir.
if (!fs.existsSync(strapi.plugins['config-sync'].config.destination)) {
ctx.send({
message: 'No config files were found.'
message: 'No config files were found.',
});
return;
@ -64,9 +67,9 @@ module.exports = {
const formattedDiff = {
fileConfig: {},
databaseConfig: {},
diff: {}
diff: {},
};
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles();
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase();

View File

@ -0,0 +1,7 @@
'use strict';
const config = require('./config');
module.exports = {
config: config,
};

31
server/routes/admin.js Normal file
View File

@ -0,0 +1,31 @@
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: "GET",
path: "/export",
handler: "config.exportAll",
config: {
policies: [],
},
},
{
method: "GET",
path: "/import",
handler: "config.importAll",
config: {
policies: [],
},
},
{
method: "GET",
path: "/diff",
handler: "config.getDiff",
config: {
policies: [],
},
},
],
};

7
server/routes/index.js Normal file
View File

@ -0,0 +1,7 @@
'use strict';
const adminRoutes = require('./admin');
module.exports = {
admin: adminRoutes,
};

View File

@ -18,9 +18,9 @@ module.exports = {
const formattedDiff = {
fileConfig: {},
databaseConfig: {},
diff: {}
diff: {},
};
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles(configPrefix);
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase(configPrefix);
const diff = difference(databaseConfig, fileConfig);
@ -30,7 +30,7 @@ module.exports = {
Object.keys(diff).map((changedConfigName) => {
formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];
formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];
})
});
await Promise.all(Object.entries(diff).map(async ([configName, config]) => {
// Check if the config should be excluded.
@ -40,8 +40,8 @@ module.exports = {
const currentConfig = formattedDiff.databaseConfig[configName];
if (
!currentConfig &&
formattedDiff.fileConfig[configName]
!currentConfig
&& formattedDiff.fileConfig[configName]
) {
await strapi.plugins['config-sync'].services.main.deleteConfigFile(configName);
} else {
@ -89,9 +89,9 @@ module.exports = {
*/
getAllFromDatabase: async () => {
const coreStore = await strapi.query(coreStoreQueryString).find({ _limit: -1 });
let configs = {};
const configs = {};
Object.values(coreStore).map( ({ id, value, key, ...config }) => {
Object.values(coreStore).map(({ id, value, key, ...config }) => {
// Check if the config should be excluded.
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${key}`);
if (shouldExclude) return;

View File

@ -1,10 +1,11 @@
'use strict';
const i18nQueryString = 'i18n_locales';
const { sanitizeEntity } = require('@strapi/utils');
// const i18nQueryString = 'i18n_locales';
const configPrefix = 'i18n-locale'; // Should be the same as the filename.
const difference = require('../utils/getObjectDiff');
const { sanitizeEntity } = require('strapi-utils');
/**
* Import/Export for i18n-locale configs.
@ -20,9 +21,9 @@ module.exports = {
const formattedDiff = {
fileConfig: {},
databaseConfig: {},
diff: {}
diff: {},
};
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles(configPrefix);
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase(configPrefix);
const diff = difference(databaseConfig, fileConfig);
@ -32,7 +33,7 @@ module.exports = {
Object.keys(diff).map((changedConfigName) => {
formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];
formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];
})
});
await Promise.all(Object.entries(diff).map(async ([configName, config]) => {
// Check if the config should be excluded.
@ -42,8 +43,8 @@ module.exports = {
const currentConfig = formattedDiff.databaseConfig[configName];
if (
!currentConfig &&
formattedDiff.fileConfig[configName]
!currentConfig
&& formattedDiff.fileConfig[configName]
) {
await strapi.plugins['config-sync'].services.main.deleteConfigFile(configName);
} else {
@ -64,9 +65,8 @@ module.exports = {
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${configName}`);
if (shouldExclude) return;
const service =
strapi.plugins['i18n'].services.locales;
const service = strapi.plugins['i18n'].services.locales;
const locale = await service.findByCode(configName);
if (locale && configContent === null) {
@ -87,19 +87,18 @@ module.exports = {
* @returns {object} Object with code value pairs of configs.
*/
getAllFromDatabase: async () => {
const service =
strapi.plugins['i18n'].services.locales;
const service = strapi.plugins['i18n'].services.locales;
const locales = await service.find({ _limit: -1 });
let configs = {};
const configs = {};
const sanitizedLocalesArray = locales.map(locale =>
const sanitizedLocalesArray = locales.map((locale) => {
sanitizeEntity(locale, {
model: strapi.plugins['i18n'].models.locale,
})
);
});
});
Object.values(sanitizedLocalesArray).map( ({ id, code, ...config }) => {
Object.values(sanitizedLocalesArray).map(({ id, code, ...config }) => {
// Check if the config should be excluded.
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${code}`);
if (shouldExclude) return;

15
server/services/index.js Normal file
View File

@ -0,0 +1,15 @@
'use strict';
const main = require('./main');
const coreStore = require('./core-store');
const i18nLocale = require('./i18n-locale');
const rolePermissions = require('./role-permissions');
const webhooks = require('./webhooks');
module.exports = {
main,
'role-permissions': rolePermissions,
'i18n-locale': i18nLocale,
'core-store': coreStore,
webhooks,
};

View File

@ -23,10 +23,9 @@ module.exports = {
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);
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 });
@ -67,7 +66,7 @@ module.exports = {
*/
readConfigFile: async (configType, configName) => {
const readFile = util.promisify(fs.readFile);
return await readFile(`${strapi.plugins['config-sync'].config.destination}${configType}.${configName}.json`)
return readFile(`${strapi.plugins['config-sync'].config.destination}${configType}.${configName}.json`)
.then((data) => {
return JSON.parse(data);
})
@ -80,6 +79,7 @@ module.exports = {
/**
* Get all the config JSON from the filesystem.
*
* @param {string} configType - Type of config to gather. Leave empty to get all config.
* @returns {object} Object with key value pairs of configs.
*/
getAllConfigFromFiles: async (configType = null) => {
@ -90,16 +90,16 @@ module.exports = {
const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination);
const getConfigs = async () => {
let fileConfigs = {};
const fileConfigs = {};
await Promise.all(configFiles.map(async (file) => {
const type = file.split('.')[0].replace('##', '::'); // Grab the first part of the filename.
const name = file.split(/\.(.+)/)[1].split('.').slice(0, -1).join('.').replace('##', '::'); // Grab the rest of the filename minus the file extension.
if (
configType && configType !== type ||
!strapi.plugins['config-sync'].config.include.includes(type) ||
strapi.plugins['config-sync'].config.exclude.includes(`${type}.${name}`)
configType && configType !== type
|| !strapi.plugins['config-sync'].config.include.includes(type)
|| strapi.plugins['config-sync'].config.exclude.includes(`${type}.${name}`)
) {
return;
}
@ -111,18 +111,19 @@ module.exports = {
return fileConfigs;
};
return await getConfigs();
return getConfigs();
},
/**
* Get all the config JSON from the database.
*
* @param {string} configType - Type of config to gather. Leave empty to get all config.
* @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;
@ -133,14 +134,15 @@ module.exports = {
}));
return databaseConfigs;
}
};
return await getConfigs();
return getConfigs();
},
/**
* Import all config files into the db.
*
* @param {string} configType - Type of config to impor. Leave empty to import all config.
* @returns {void}
*/
importAllConfig: async (configType = null) => {
@ -164,6 +166,7 @@ module.exports = {
/**
* Export all config files.
*
* @param {string} configType - Type of config to export. Leave empty to export all config.
* @returns {void}
*/
exportAllConfig: async (configType = null) => {
@ -201,6 +204,6 @@ module.exports = {
* @returns {void}
*/
exportSingleConfig: async (configType, configName) => {
},
};

View File

@ -1,6 +1,6 @@
'use strict';
const { sanitizeEntity } = require('strapi-utils');
const { sanitizeEntity } = require('@strapi/utils');
const difference = require('../utils/getObjectDiff');
const configPrefix = 'role-permissions'; // Should be the same as the filename.
@ -19,9 +19,9 @@ module.exports = {
const formattedDiff = {
fileConfig: {},
databaseConfig: {},
diff: {}
diff: {},
};
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles(configPrefix);
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase(configPrefix);
const diff = difference(databaseConfig, fileConfig);
@ -31,7 +31,7 @@ module.exports = {
Object.keys(diff).map((changedConfigName) => {
formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];
formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];
})
});
await Promise.all(Object.entries(diff).map(async ([configName, config]) => {
// Check if the config should be excluded.
@ -41,8 +41,8 @@ module.exports = {
const currentConfig = formattedDiff.databaseConfig[configName];
if (
!currentConfig &&
formattedDiff.fileConfig[configName]
!currentConfig
&& formattedDiff.fileConfig[configName]
) {
await strapi.plugins['config-sync'].services.main.deleteConfigFile(configName);
} else {
@ -64,9 +64,8 @@ module.exports = {
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${configName}`);
if (shouldExclude) return;
const service =
strapi.plugins['users-permissions'].services.userspermissions;
const service = strapi.plugins['users-permissions'].services.userspermissions;
const role = await strapi
.query('role', 'users-permissions')
.findOne({ type: configName });
@ -96,8 +95,7 @@ module.exports = {
* @returns {object} Object with key value pairs of configs.
*/
getAllFromDatabase: async () => {
const service =
strapi.plugins['users-permissions'].services.userspermissions;
const service = strapi.plugins['users-permissions'].services.userspermissions;
const [roles, plugins] = await Promise.all([
service.getRoles(),
@ -105,22 +103,22 @@ module.exports = {
]);
const rolesWithPermissions = await Promise.all(
roles.map(async role => service.getRole(role.id, plugins))
roles.map(async (role) => service.getRole(role.id, plugins))
);
const sanitizedRolesArray = rolesWithPermissions.map(role =>
const sanitizedRolesArray = rolesWithPermissions.map((role) => {
sanitizeEntity(role, {
model: strapi.plugins['users-permissions'].models.role,
})
);
});
});
let configs = {};
const configs = {};
Object.values(sanitizedRolesArray).map(({ id, ...config }) => {
// Check if the config should be excluded.
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${config.type}`);
if (shouldExclude) return;
// Do not export the _id field, as it is immutable
delete config._id;

View File

@ -0,0 +1,30 @@
'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) => {
let arrayIndexCounter = 0;
const newObjChange = transform(newObj, (result, value, key) => {
if (!isEqual(value, origObj[key])) {
const resultKey = isArray(origObj) ? arrayIndexCounter++ : key;
result[resultKey] = (isObject(value) && isObject(origObj[key])) ? difference(value, origObj[key]) : value;
}
});
const origObjChange = transform(origObj, (result, value, key) => {
if (!isEqual(value, newObj[key])) {
const resultKey = isArray(newObj) ? arrayIndexCounter++ : key;
result[resultKey] = (isObject(value) && isObject(newObj[key])) ? difference(value, newObj[key]) : value;
}
});
return Object.assign(newObjChange, origObjChange);
};
module.exports = difference;

17
server/utils/index.js Normal file
View File

@ -0,0 +1,17 @@
'use strict';
const getCoreStore = () => {
return strapi.store({ type: 'plugin', name: 'config-sync' });
};
const getService = (name) => {
return strapi.plugin('config-sync').service(name);
};
const logMessage = (msg = '') => `[strapi-plugin-config-sync]: ${msg}`;
module.exports = {
getService,
getCoreStore,
logMessage,
};

3
strapi-admin.js Normal file
View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('./admin/src').default;

17
strapi-server.js Normal file
View File

@ -0,0 +1,17 @@
'use strict';
const bootstrap = require('./server/bootstrap');
const services = require('./server/services');
const routes = require('./server/routes');
const config = require('./server/config');
const controllers = require('./server/controllers');
module.exports = () => {
return {
bootstrap,
routes,
config,
controllers,
services,
};
};

View File

@ -1,33 +0,0 @@
'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
const newObjChange = 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
}
});
const origObjChange = transform(origObj, function (result, value, key) {
if (!isEqual(value, newObj[key])) {
let resultKey = isArray(newObj) ? arrayIndexCounter++ : key
result[resultKey] = (isObject(value) && isObject(newObj[key])) ? changes(value, newObj[key]) : value
}
})
return Object.assign(newObjChange, origObjChange);
}
return changes(newObj, origObj)
}
module.exports = difference;