Skip to content

Commit 6a05393

Browse files
authored
Merge pull request #40 from trieb-work/feat-combined-uids
feat: add combined uid feature
2 parents 3678c8e + f28d80d commit 6a05393

File tree

4 files changed

+41
-25
lines changed

4 files changed

+41
-25
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,15 @@ This is the query string of the type. Each type in Strapi has its own query stri
278278
279279
#### UID
280280

281-
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.
281+
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.
282282

283283
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.
284284

285+
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']`).
286+
285287
###### Key: `uid`
286288

287-
> `required:` YES | `type:` string
289+
> `required:` YES | `type:` string | string[]
288290
289291
#### JSON fields
290292

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "strapi-plugin-config-sync",
3-
"version": "1.0.0-beta.4",
3+
"version": "1.0.0-beta.5",
44
"description": "CLI & GUI for syncing config data across environments.",
55
"strapi": {
66
"displayName": "Config Sync",

server/config/type.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const { isEmpty } = require('lodash');
2-
const { logMessage, sanitizeConfig, dynamicSort, noLimit } = require('../utils');
2+
const { logMessage, sanitizeConfig, dynamicSort, noLimit, getCombinedUid, getCombinedUidWhereFilter, getUidParamsFromName } = require('../utils');
33
const difference = require('../utils/getArrayDiff');
44

55
const ConfigType = class ConfigType {
@@ -12,13 +12,17 @@ const ConfigType = class ConfigType {
1212
strapi.log.error(logMessage(`No query string found for the '${configName}' config type.`));
1313
process.exit(0);
1414
}
15-
if (!uid) {
16-
strapi.log.error(logMessage(`No uid found for the '${configName}' config type.`));
15+
// uid could be a single key or an array for a combined uid. So the type of uid is either string or string[]
16+
if (typeof uid === "string") {
17+
this.uidKeys = [uid];
18+
} else if (Array.isArray(uid)) {
19+
this.uidKeys = uid.sort();
20+
} else {
21+
strapi.log.error(logMessage(`Wrong uid config for the '${configName}' config type.`));
1722
process.exit(0);
1823
}
1924
this.queryString = queryString;
2025
this.configPrefix = configName;
21-
this.uid = uid;
2226
this.jsonFields = jsonFields || [];
2327
this.relations = relations || [];
2428
}
@@ -30,29 +34,25 @@ const ConfigType = class ConfigType {
3034
* @param {string} configContent - The JSON content of the config file.
3135
* @returns {void}
3236
*/
33-
importSingle = async (configName, configContent) => {
37+
importSingle = async (configName, configContent) => {
3438
// Check if the config should be excluded.
3539
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${configName}`.startsWith(option)));
3640
if (shouldExclude) return;
3741

3842
const queryAPI = strapi.query(this.queryString);
39-
43+
const uidParams = getUidParamsFromName(this.uidKeys, configName);
44+
const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, uidParams);
4045
let existingConfig = await queryAPI
4146
.findOne({
42-
where: { [this.uid]: configName },
43-
populate: this.relations.map(({ relationName }) => relationName),
44-
});
45-
46-
if (existingConfig && configContent === null) {
47-
const entity = await queryAPI.findOne({
48-
where: { [this.uid]: configName },
47+
where: combinedUidWhereFilter,
4948
populate: this.relations.map(({ relationName }) => relationName),
5049
});
5150

51+
if (existingConfig && configContent === null) { // Config exists in DB but no configfile content --> delete config from DB
5252
await Promise.all(this.relations.map(async ({ queryString, parentName }) => {
5353
const relations = await noLimit(strapi.query(queryString), {
5454
where: {
55-
[parentName]: entity.id,
55+
[parentName]: existingConfig.id,
5656
},
5757
});
5858

@@ -64,13 +64,13 @@ const ConfigType = class ConfigType {
6464
}));
6565

6666
await queryAPI.delete({
67-
where: { id: entity.id },
67+
where: { id: existingConfig.id },
6868
});
6969

7070
return;
7171
}
7272

73-
if (!existingConfig) {
73+
if (!existingConfig) { // Config does not exist in DB --> create config in DB
7474
// Format JSON fields.
7575
const query = { ...configContent };
7676
this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field]));
@@ -88,15 +88,15 @@ const ConfigType = class ConfigType {
8888
await relationQueryApi.create({ data: relationQuery });
8989
}));
9090
}));
91-
} else {
91+
} else { // Config does exist in DB --> update config in DB
9292
// Format JSON fields.
9393
configContent = sanitizeConfig(configContent);
9494
const query = { ...configContent };
9595
this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field]));
9696

9797
// Update entity.
9898
this.relations.map(({ relationName }) => delete query[relationName]);
99-
const entity = await queryAPI.update({ where: { [this.uid]: configName }, data: query });
99+
const entity = await queryAPI.update({ where: combinedUidWhereFilter, data: query });
100100

101101
// Delete/create relations.
102102
await Promise.all(this.relations.map(async ({ queryString, relationName, parentName, relationSortField }) => {
@@ -146,7 +146,8 @@ const ConfigType = class ConfigType {
146146
) {
147147
await strapi.plugin('config-sync').service('main').deleteConfigFile(configName);
148148
} else {
149-
await strapi.plugin('config-sync').service('main').writeConfigFile(this.configPrefix, currentConfig[this.uid], currentConfig);
149+
const combinedUid = getCombinedUid(this.uidKeys, currentConfig);
150+
await strapi.plugin('config-sync').service('main').writeConfigFile(this.configPrefix, combinedUid, currentConfig);
150151
}
151152
}
152153

@@ -160,14 +161,16 @@ const ConfigType = class ConfigType {
160161
const configs = {};
161162

162163
await Promise.all(Object.values(AllConfig).map(async (config) => {
164+
const combinedUid = getCombinedUid(this.uidKeys, config);
165+
const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, config);
163166
// Check if the config should be excluded.
164-
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${config[this.uid]}`.startsWith(option)));
167+
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${combinedUid}`.startsWith(option)));
165168
if (shouldExclude) return;
166169

167170
const formattedConfig = { ...sanitizeConfig(config) };
168171
await Promise.all(this.relations.map(async ({ queryString, relationName, relationSortField, parentName }) => {
169172
const relations = await noLimit(strapi.query(queryString), {
170-
where: { [parentName]: { [this.uid]: config[this.uid] } },
173+
where: { [parentName]: combinedUidWhereFilter },
171174
});
172175

173176
relations.map((relation) => sanitizeConfig(relation));
@@ -176,7 +179,7 @@ const ConfigType = class ConfigType {
176179
}));
177180

178181
this.jsonFields.map((field) => formattedConfig[field] = JSON.parse(config[field]));
179-
configs[`${this.configPrefix}.${config[this.uid]}`] = formattedConfig;
182+
configs[`${this.configPrefix}.${combinedUid}`] = formattedConfig;
180183
}));
181184

182185

server/utils/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
'use strict';
22

3+
const COMBINED_UID_JOINSTR = '.combine-uid.';
4+
5+
const escapeUid = (uid) => typeof uid === "string" ? uid.replace(/\.combine-uid\./g, '.combine-uid-escape.') : uid;
6+
const unEscapeUid = (uid) => typeof uid === "string" ? uid.replace(/\.combine-uid-escape\./g, '.combine-uid.') : uid;
7+
const getCombinedUid = (uidKeys, params) => uidKeys.map((uidKey) => escapeUid(params[uidKey])).join(COMBINED_UID_JOINSTR);
8+
const getCombinedUidWhereFilter = (uidKeys, params) => uidKeys.reduce(((akku, uidKey) => ({ ...akku, [uidKey]: params[uidKey] })), {});
9+
const getUidParamsFromName = (uidKeys, configName) => configName.split(COMBINED_UID_JOINSTR).map(unEscapeUid).reduce((akku, param, i) => ({ ...akku, [uidKeys[i]]: param }), {});
10+
311
const getCoreStore = () => {
412
return strapi.store({ type: 'plugin', name: 'config-sync' });
513
};
@@ -83,6 +91,9 @@ const noLimit = async (query, parameters, limit = 100) => {
8391
};
8492

8593
module.exports = {
94+
getCombinedUid,
95+
getCombinedUidWhereFilter,
96+
getUidParamsFromName,
8697
getService,
8798
getCoreStore,
8899
logMessage,

0 commit comments

Comments
 (0)