@@ -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