Merge pull request #40 from trieb-work/feat-combined-uids

feat: add combined uid feature
pull/43/head 1.0.0-beta.5
Boaz Poolman 2022-01-19 17:49:50 +01:00 committed by GitHub
commit 6a05393c11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 25 deletions

View File

@ -278,13 +278,15 @@ This is the query string of the type. Each type in Strapi has its own query stri
#### UID #### UID
The UID represents a field on the registered type. The value of this field will act as a unique identifier to identify the entries across environments. Therefor it should be unique and preferably un-editable after initial creation. The UID represents a field on the registered type. The value of this field will act as a unique identifier to identify the entries across environments. Therefore it should be unique and preferably un-editable after initial creation.
Mind that you can not use an auto-incremental value like the `id` as auto-increment does not play nice when you try to match entries across different databases. Mind that you can not use an auto-incremental value like the `id` as auto-increment does not play nice when you try to match entries across different databases.
If you do not have a single unique value, you can also pass in a array of keys for a combined uid key. This is for example the case for all content types which use i18n features (An example config would be `uid: ['productId', 'locale']`).
###### Key: `uid` ###### Key: `uid`
> `required:` YES | `type:` string > `required:` YES | `type:` string | string[]
#### JSON fields #### JSON fields

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-config-sync", "name": "strapi-plugin-config-sync",
"version": "1.0.0-beta.4", "version": "1.0.0-beta.5",
"description": "CLI & GUI for syncing config data across environments.", "description": "CLI & GUI for syncing config data across environments.",
"strapi": { "strapi": {
"displayName": "Config Sync", "displayName": "Config Sync",

View File

@ -1,5 +1,5 @@
const { isEmpty } = require('lodash'); const { isEmpty } = require('lodash');
const { logMessage, sanitizeConfig, dynamicSort, noLimit } = require('../utils'); const { logMessage, sanitizeConfig, dynamicSort, noLimit, getCombinedUid, getCombinedUidWhereFilter, getUidParamsFromName } = require('../utils');
const difference = require('../utils/getArrayDiff'); const difference = require('../utils/getArrayDiff');
const ConfigType = class ConfigType { const ConfigType = class ConfigType {
@ -12,13 +12,17 @@ const ConfigType = class ConfigType {
strapi.log.error(logMessage(`No query string found for the '${configName}' config type.`)); strapi.log.error(logMessage(`No query string found for the '${configName}' config type.`));
process.exit(0); process.exit(0);
} }
if (!uid) { // uid could be a single key or an array for a combined uid. So the type of uid is either string or string[]
strapi.log.error(logMessage(`No uid found for the '${configName}' config type.`)); if (typeof uid === "string") {
this.uidKeys = [uid];
} else if (Array.isArray(uid)) {
this.uidKeys = uid.sort();
} else {
strapi.log.error(logMessage(`Wrong uid config for the '${configName}' config type.`));
process.exit(0); process.exit(0);
} }
this.queryString = queryString; this.queryString = queryString;
this.configPrefix = configName; this.configPrefix = configName;
this.uid = uid;
this.jsonFields = jsonFields || []; this.jsonFields = jsonFields || [];
this.relations = relations || []; this.relations = relations || [];
} }
@ -30,29 +34,25 @@ const ConfigType = class ConfigType {
* @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 = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${configName}`.startsWith(option))); const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${configName}`.startsWith(option)));
if (shouldExclude) return; if (shouldExclude) return;
const queryAPI = strapi.query(this.queryString); const queryAPI = strapi.query(this.queryString);
const uidParams = getUidParamsFromName(this.uidKeys, configName);
const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, uidParams);
let existingConfig = await queryAPI let existingConfig = await queryAPI
.findOne({ .findOne({
where: { [this.uid]: configName }, where: combinedUidWhereFilter,
populate: this.relations.map(({ relationName }) => relationName),
});
if (existingConfig && configContent === null) {
const entity = await queryAPI.findOne({
where: { [this.uid]: configName },
populate: this.relations.map(({ relationName }) => relationName), populate: this.relations.map(({ relationName }) => relationName),
}); });
if (existingConfig && configContent === null) { // Config exists in DB but no configfile content --> delete config from DB
await Promise.all(this.relations.map(async ({ queryString, parentName }) => { await Promise.all(this.relations.map(async ({ queryString, parentName }) => {
const relations = await noLimit(strapi.query(queryString), { const relations = await noLimit(strapi.query(queryString), {
where: { where: {
[parentName]: entity.id, [parentName]: existingConfig.id,
}, },
}); });
@ -64,13 +64,13 @@ const ConfigType = class ConfigType {
})); }));
await queryAPI.delete({ await queryAPI.delete({
where: { id: entity.id }, where: { id: existingConfig.id },
}); });
return; return;
} }
if (!existingConfig) { if (!existingConfig) { // Config does not exist in DB --> create config in DB
// Format JSON fields. // 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]));
@ -88,7 +88,7 @@ const ConfigType = class ConfigType {
await relationQueryApi.create({ data: relationQuery }); await relationQueryApi.create({ data: relationQuery });
})); }));
})); }));
} else { } else { // Config does exist in DB --> update config in DB
// Format JSON fields. // Format JSON fields.
configContent = sanitizeConfig(configContent); configContent = sanitizeConfig(configContent);
const query = { ...configContent }; const query = { ...configContent };
@ -96,7 +96,7 @@ const ConfigType = class ConfigType {
// Update entity. // Update entity.
this.relations.map(({ relationName }) => delete query[relationName]); this.relations.map(({ relationName }) => delete query[relationName]);
const entity = await queryAPI.update({ where: { [this.uid]: configName }, data: query }); const entity = await queryAPI.update({ where: combinedUidWhereFilter, data: query });
// Delete/create relations. // Delete/create relations.
await Promise.all(this.relations.map(async ({ queryString, relationName, parentName, relationSortField }) => { await Promise.all(this.relations.map(async ({ queryString, relationName, parentName, relationSortField }) => {
@ -146,7 +146,8 @@ const ConfigType = class ConfigType {
) { ) {
await strapi.plugin('config-sync').service('main').deleteConfigFile(configName); await strapi.plugin('config-sync').service('main').deleteConfigFile(configName);
} else { } else {
await strapi.plugin('config-sync').service('main').writeConfigFile(this.configPrefix, currentConfig[this.uid], currentConfig); const combinedUid = getCombinedUid(this.uidKeys, currentConfig);
await strapi.plugin('config-sync').service('main').writeConfigFile(this.configPrefix, combinedUid, currentConfig);
} }
} }
@ -160,14 +161,16 @@ const ConfigType = class ConfigType {
const configs = {}; const configs = {};
await Promise.all(Object.values(AllConfig).map(async (config) => { await Promise.all(Object.values(AllConfig).map(async (config) => {
const combinedUid = getCombinedUid(this.uidKeys, config);
const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, config);
// Check if the config should be excluded. // Check if the config should be excluded.
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${config[this.uid]}`.startsWith(option))); const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${combinedUid}`.startsWith(option)));
if (shouldExclude) return; if (shouldExclude) return;
const formattedConfig = { ...sanitizeConfig(config) }; const formattedConfig = { ...sanitizeConfig(config) };
await Promise.all(this.relations.map(async ({ queryString, relationName, relationSortField, parentName }) => { await Promise.all(this.relations.map(async ({ queryString, relationName, relationSortField, parentName }) => {
const relations = await noLimit(strapi.query(queryString), { const relations = await noLimit(strapi.query(queryString), {
where: { [parentName]: { [this.uid]: config[this.uid] } }, where: { [parentName]: combinedUidWhereFilter },
}); });
relations.map((relation) => sanitizeConfig(relation)); relations.map((relation) => sanitizeConfig(relation));
@ -176,7 +179,7 @@ const ConfigType = class ConfigType {
})); }));
this.jsonFields.map((field) => formattedConfig[field] = JSON.parse(config[field])); this.jsonFields.map((field) => formattedConfig[field] = JSON.parse(config[field]));
configs[`${this.configPrefix}.${config[this.uid]}`] = formattedConfig; configs[`${this.configPrefix}.${combinedUid}`] = formattedConfig;
})); }));

View File

@ -1,5 +1,13 @@
'use strict'; 'use strict';
const COMBINED_UID_JOINSTR = '.combine-uid.';
const escapeUid = (uid) => typeof uid === "string" ? uid.replace(/\.combine-uid\./g, '.combine-uid-escape.') : uid;
const unEscapeUid = (uid) => typeof uid === "string" ? uid.replace(/\.combine-uid-escape\./g, '.combine-uid.') : uid;
const getCombinedUid = (uidKeys, params) => uidKeys.map((uidKey) => escapeUid(params[uidKey])).join(COMBINED_UID_JOINSTR);
const getCombinedUidWhereFilter = (uidKeys, params) => uidKeys.reduce(((akku, uidKey) => ({ ...akku, [uidKey]: params[uidKey] })), {});
const getUidParamsFromName = (uidKeys, configName) => configName.split(COMBINED_UID_JOINSTR).map(unEscapeUid).reduce((akku, param, i) => ({ ...akku, [uidKeys[i]]: param }), {});
const getCoreStore = () => { const getCoreStore = () => {
return strapi.store({ type: 'plugin', name: 'config-sync' }); return strapi.store({ type: 'plugin', name: 'config-sync' });
}; };
@ -83,6 +91,9 @@ const noLimit = async (query, parameters, limit = 100) => {
}; };
module.exports = { module.exports = {
getCombinedUid,
getCombinedUidWhereFilter,
getUidParamsFromName,
getService, getService,
getCoreStore, getCoreStore,
logMessage, logMessage,