fix(collection-manager): redundant fields after set collection fields (#2575)
* fix(collection-manager): redundant fields after set collection fields * fix: destory with individuals hook * chore: save * chore: test * chore: mutex in fields.afterDestroy * chore: yarn.lock * chore: update collections.setFields
This commit is contained in:
		
							parent
							
								
									d9bda04aa2
								
							
						
					
					
						commit
						1694eb6d73
					
				@ -37,8 +37,6 @@ import { UpdateGuard } from './update-guard';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const debug = require('debug')('noco-database');
 | 
					const debug = require('debug')('noco-database');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IRepository {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface CreateManyOptions extends BulkCreateOptions {
 | 
					interface CreateManyOptions extends BulkCreateOptions {
 | 
				
			||||||
  records: Values[];
 | 
					  records: Values[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -145,7 +143,7 @@ export interface UpdateOptions extends Omit<SequelizeUpdateOptions, 'where'> {
 | 
				
			|||||||
  context?: any;
 | 
					  context?: any;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface UpdateManyOptions extends UpdateOptions {
 | 
					interface UpdateManyOptions extends Omit<UpdateOptions, 'values'> {
 | 
				
			||||||
  records: Values[];
 | 
					  records: Values[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -221,9 +219,7 @@ interface FirstOrCreateOptions extends Transactionable {
 | 
				
			|||||||
  values?: Values;
 | 
					  values?: Values;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Repository<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes>
 | 
					export class Repository<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes> {
 | 
				
			||||||
  implements IRepository
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  database: Database;
 | 
					  database: Database;
 | 
				
			||||||
  collection: Collection;
 | 
					  collection: Collection;
 | 
				
			||||||
  model: ModelStatic<Model>;
 | 
					  model: ModelStatic<Model>;
 | 
				
			||||||
@ -242,7 +238,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
 | 
				
			|||||||
      const chunks = key.split('.');
 | 
					      const chunks = key.split('.');
 | 
				
			||||||
      return chunks
 | 
					      return chunks
 | 
				
			||||||
        .filter((chunk) => {
 | 
					        .filter((chunk) => {
 | 
				
			||||||
          return !Boolean(chunk.match(/\d+/));
 | 
					          return !chunk.match(/\d+/);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .join('.');
 | 
					        .join('.');
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,8 @@
 | 
				
			|||||||
    "@types/jsonwebtoken": "^8.5.8",
 | 
					    "@types/jsonwebtoken": "^8.5.8",
 | 
				
			||||||
    "jsonwebtoken": "^8.5.1",
 | 
					    "jsonwebtoken": "^8.5.1",
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "react": "^18.2.0",
 | 
				
			||||||
    "react-dom": "^18.2.0"
 | 
					    "react-dom": "^18.2.0",
 | 
				
			||||||
 | 
					    "async-mutex": "^0.4.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "peerDependencies": {
 | 
					  "peerDependencies": {
 | 
				
			||||||
    "@nocobase/acl": "0.x",
 | 
					    "@nocobase/acl": "0.x",
 | 
				
			||||||
 | 
				
			|||||||
@ -297,6 +297,90 @@ describe('association field acl', () => {
 | 
				
			|||||||
    ).toBeNull();
 | 
					    ).toBeNull();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should not redundant fields after field set', async () => {
 | 
				
			||||||
 | 
					    await db.getRepository('collections').create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'posts',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      context: {},
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await db.getRepository('collections.fields', 'posts').create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'title',
 | 
				
			||||||
 | 
					        type: 'string',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      context: {},
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await db.getRepository('collections.fields', 'posts').create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'content',
 | 
				
			||||||
 | 
					        type: 'string',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      context: {},
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await adminAgent.resource('roles.resources', 'new').create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'posts',
 | 
				
			||||||
 | 
					        usingActionsConfig: true,
 | 
				
			||||||
 | 
					        actions: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: 'create',
 | 
				
			||||||
 | 
					            fields: ['content', 'title'],
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      acl.can({
 | 
				
			||||||
 | 
					        role: 'new',
 | 
				
			||||||
 | 
					        resource: 'posts',
 | 
				
			||||||
 | 
					        action: 'create',
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    ).toMatchObject({
 | 
				
			||||||
 | 
					      role: 'new',
 | 
				
			||||||
 | 
					      resource: 'posts',
 | 
				
			||||||
 | 
					      action: 'create',
 | 
				
			||||||
 | 
					      params: {
 | 
				
			||||||
 | 
					        whitelist: ['content', 'title'],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await adminAgent.resource('collections').setFields({
 | 
				
			||||||
 | 
					      filterByTk: 'posts',
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: 'name',
 | 
				
			||||||
 | 
					            type: 'string',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: 'content',
 | 
				
			||||||
 | 
					            type: 'text',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      acl.can({
 | 
				
			||||||
 | 
					        role: 'new',
 | 
				
			||||||
 | 
					        resource: 'posts',
 | 
				
			||||||
 | 
					        action: 'create',
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    ).toMatchObject({
 | 
				
			||||||
 | 
					      role: 'new',
 | 
				
			||||||
 | 
					      resource: 'posts',
 | 
				
			||||||
 | 
					      action: 'create',
 | 
				
			||||||
 | 
					      params: {
 | 
				
			||||||
 | 
					        whitelist: ['content', 'name'],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should revoke association action on field deleted', async () => {
 | 
					  it('should revoke association action on field deleted', async () => {
 | 
				
			||||||
    await adminAgent.resource('roles.resources', 'new').update({
 | 
					    await adminAgent.resource('roles.resources', 'new').update({
 | 
				
			||||||
      filterByTk: 'users',
 | 
					      filterByTk: 'users',
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import { setCurrentRole } from './middlewares/setCurrentRole';
 | 
				
			|||||||
import { RoleModel } from './model/RoleModel';
 | 
					import { RoleModel } from './model/RoleModel';
 | 
				
			||||||
import { RoleResourceActionModel } from './model/RoleResourceActionModel';
 | 
					import { RoleResourceActionModel } from './model/RoleResourceActionModel';
 | 
				
			||||||
import { RoleResourceModel } from './model/RoleResourceModel';
 | 
					import { RoleResourceModel } from './model/RoleResourceModel';
 | 
				
			||||||
 | 
					import { Mutex } from 'async-mutex';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AssociationFieldAction {
 | 
					export interface AssociationFieldAction {
 | 
				
			||||||
  associationActions: string[];
 | 
					  associationActions: string[];
 | 
				
			||||||
@ -316,7 +317,10 @@ export class PluginACL extends Plugin {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mutex = new Mutex();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.app.db.on('fields.afterDestroy', async (model, options) => {
 | 
					    this.app.db.on('fields.afterDestroy', async (model, options) => {
 | 
				
			||||||
 | 
					      await mutex.runExclusive(async () => {
 | 
				
			||||||
        const collectionName = model.get('collectionName');
 | 
					        const collectionName = model.get('collectionName');
 | 
				
			||||||
        const fieldName = model.get('name');
 | 
					        const fieldName = model.get('name');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -341,6 +345,7 @@ export class PluginACL extends Plugin {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const writeRolesToACL = async (app, options) => {
 | 
					    const writeRolesToACL = async (app, options) => {
 | 
				
			||||||
      const exists = await this.app.db.collectionExistsInDb('roles');
 | 
					      const exists = await this.app.db.collectionExistsInDb('roles');
 | 
				
			||||||
 | 
				
			|||||||
@ -63,4 +63,35 @@ describe('recreate field', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    expect(response.statusCode).toBe(200);
 | 
					    expect(response.statusCode).toBe(200);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should reset fields', async () => {
 | 
				
			||||||
 | 
					    await agent.resource('collections').create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'a1',
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: 'a',
 | 
				
			||||||
 | 
					            type: 'string',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(await app.db.getRepository('fields').count()).toBe(1);
 | 
				
			||||||
 | 
					    const response = await agent.resource('collections').setFields({
 | 
				
			||||||
 | 
					      filterByTk: 'a1',
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: 'a',
 | 
				
			||||||
 | 
					            type: 'bigInt',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(response.statusCode).toBe(200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(await app.db.getRepository('fields').count()).toBe(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -21,14 +21,50 @@ export default {
 | 
				
			|||||||
        transaction,
 | 
					        transaction,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await db.getRepository('collections').update({
 | 
					      const existFields = await collection.getFields({
 | 
				
			||||||
        filterByTk,
 | 
					 | 
				
			||||||
        values: {
 | 
					 | 
				
			||||||
          fields,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        transaction,
 | 
					        transaction,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const needUpdateFields = fields
 | 
				
			||||||
 | 
					        .filter((f) => {
 | 
				
			||||||
 | 
					          return existFields.find((ef) => ef.name === f.name);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .map((f) => {
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            ...f,
 | 
				
			||||||
 | 
					            key: existFields.find((ef) => ef.name === f.name).key,
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const needDestroyFields = existFields.filter((ef) => {
 | 
				
			||||||
 | 
					        return !fields.find((f) => f.name === ef.name);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const needCreatedFields = fields.filter((f) => {
 | 
				
			||||||
 | 
					        return !existFields.find((ef) => ef.name === f.name);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (needDestroyFields.length) {
 | 
				
			||||||
 | 
					        await db.getRepository('fields').destroy({
 | 
				
			||||||
 | 
					          filterByTk: needDestroyFields.map((f) => f.key),
 | 
				
			||||||
 | 
					          transaction,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (needUpdateFields.length) {
 | 
				
			||||||
 | 
					        await db.getRepository('fields').updateMany({
 | 
				
			||||||
 | 
					          records: needUpdateFields,
 | 
				
			||||||
 | 
					          transaction,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (needCreatedFields.length) {
 | 
				
			||||||
 | 
					        await db.getRepository('collections.fields', filterByTk).create({
 | 
				
			||||||
 | 
					          values: needCreatedFields,
 | 
				
			||||||
 | 
					          transaction,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await collection.loadFields({
 | 
					      await collection.loadFields({
 | 
				
			||||||
        transaction,
 | 
					        transaction,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
				
			|||||||
@ -9003,6 +9003,13 @@ async-mutex@^0.3.2:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    tslib "^2.3.1"
 | 
					    tslib "^2.3.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async-mutex@^0.4.0:
 | 
				
			||||||
 | 
					  version "0.4.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f"
 | 
				
			||||||
 | 
					  integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    tslib "^2.4.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async-ratelimiter@^1.3.0:
 | 
					async-ratelimiter@^1.3.0:
 | 
				
			||||||
  version "1.3.8"
 | 
					  version "1.3.8"
 | 
				
			||||||
  resolved "https://registry.npmmirror.com/async-ratelimiter/-/async-ratelimiter-1.3.8.tgz#05198a322543de43d98807c96295a9d712306928"
 | 
					  resolved "https://registry.npmmirror.com/async-ratelimiter/-/async-ratelimiter-1.3.8.tgz#05198a322543de43d98807c96295a9d712306928"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user