Skip to content

Commit e4d8b63

Browse files
authored
fix oversights in add missing schema fields (#4736)
1 parent 0316646 commit e4d8b63

File tree

1 file changed

+46
-5
lines changed

1 file changed

+46
-5
lines changed

modules/@apostrophecms/migration/lib/addMissingSchemaFields.js

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,48 @@ module.exports = (self) => {
3535
}, async doc => {
3636
const changes = {};
3737
await self.addMissingSchemaFieldsFor(doc, schema, '', changes);
38+
if (doc.type === '@apostrophecms/user') {
39+
// In an abundance of caution we should leave permissions-related
40+
// properties alone. The role will only be missing if transitioning
41+
// from advanced permissions back to regular and there is a right
42+
// way to do that (via a task) so we should not interfere. There
43+
// should be no default for the password, but don't chance it;
44+
// also leave advanced permission group data alone just for good measure
45+
delete changes.password;
46+
delete changes.role;
47+
delete changes.groupsIds;
48+
delete changes.groupsFields;
49+
}
3850
if (Object.keys(changes).length > 0) {
3951
updates++;
52+
const seen = new Set();
53+
const keep = {};
54+
for (const key of Object.keys(changes)) {
55+
// MongoDB won't tolerate $set for both
56+
// a parent key and a subkey, which makes sense
57+
// because we set the entire value for the
58+
// parent key anyway. Filter out subkeys
59+
// when the parent key is also being set
60+
const components = key.split('.');
61+
let path = '';
62+
let preempted = false;
63+
for (const component of components) {
64+
path = path ? `${path}.${component}` : component;
65+
if (seen.has(path)) {
66+
preempted = true;
67+
break;
68+
}
69+
}
70+
if (preempted) {
71+
continue;
72+
}
73+
keep[key] = changes[key];
74+
seen.add(key);
75+
}
4076
return self.apos.doc.db.updateOne({
4177
_id: doc._id
4278
}, {
43-
$set: changes
79+
$set: keep
4480
});
4581
}
4682
});
@@ -54,8 +90,10 @@ module.exports = (self) => {
5490
addMissingSchemaFieldsFor(doc, schema, dotPath, changes) {
5591
for (const field of schema) {
5692
const newDotPath = dotPath ? `${dotPath}.${field.name}` : field.name;
57-
// Supply the default
58-
if (doc[field.name] === undefined) {
93+
// Supply the default if a field is undefined, and also in the
94+
// edge case where due to a past bug an object field was falsy
95+
if ((doc[field.name] === undefined) ||
96+
((field.type === 'object') && !doc[field.name])) {
5997
// Only undefined should fall back here
6098
const def = klona((field.def === undefined) ? self.apos.schema.fieldTypes[field.type]?.def : field.def);
6199
if (def !== undefined) {
@@ -91,7 +129,10 @@ module.exports = (self) => {
91129
}
92130
} else if (field.type === 'relationship') {
93131
for (const [ key, item ] of Object.entries(doc[field.fieldsStorage] || {})) {
94-
const itemPath = `${newDotPath}.${field.fieldsStorage}.${key}`;
132+
// Careful, newDotPath contains the relationship name, we are storing
133+
// in the fieldsStorage property for this relationship
134+
const storageDotPath = dotPath ? `${dotPath}.${field.fieldsStorage}` : field.fieldsStorage;
135+
const itemPath = `${storageDotPath}.${key}`;
95136
self.addMissingSchemaFieldsFor(item, field.schema, itemPath, changes);
96137
}
97138
}
@@ -101,7 +142,7 @@ module.exports = (self) => {
101142
getPropLists() {
102143
const schema = {};
103144
for (const [ name, module ] of Object.entries(self.apos.doc.managers)) {
104-
if (!module.__meta.name) {
145+
if (!module.__meta?.name) {
105146
// Just a placeholder for a type present in the
106147
// database but not found in the project code
107148
continue;

0 commit comments

Comments
 (0)