feat: context field type support (#131)
* feat: context field type support * fix: missing options
This commit is contained in:
		
							parent
							
								
									54f351af9d
								
							
						
					
					
						commit
						bf2840b089
					
				
							
								
								
									
										140
									
								
								packages/database/src/__tests__/fields/context-field.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								packages/database/src/__tests__/fields/context-field.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					import { DataTypes } from 'sequelize';
 | 
				
			||||||
 | 
					import { mockDatabase } from '../';
 | 
				
			||||||
 | 
					import { Database } from '../../';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('context field', () => {
 | 
				
			||||||
 | 
					  let db: Database;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    db = mockDatabase();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    await db.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('dataType', () => {
 | 
				
			||||||
 | 
					    it('case 1, string', async () => {
 | 
				
			||||||
 | 
					      const Test = db.collection({
 | 
				
			||||||
 | 
					        name: 'tests',
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            type: 'context',
 | 
				
			||||||
 | 
					            name: 'clientIp',
 | 
				
			||||||
 | 
					            dataIndex: 'request.ip',
 | 
				
			||||||
 | 
					            dataType: 'string',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const attribute = Test.model.rawAttributes['clientIp'];
 | 
				
			||||||
 | 
					      expect(attribute.type).toBeInstanceOf(DataTypes.STRING);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('case 2, integer', async () => {
 | 
				
			||||||
 | 
					      const Test = db.collection({
 | 
				
			||||||
 | 
					        name: 'tests',
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            type: 'context',
 | 
				
			||||||
 | 
					            name: 'userId',
 | 
				
			||||||
 | 
					            dataIndex: 'state.currentUser.id',
 | 
				
			||||||
 | 
					            dataType: 'integer',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const attribute = Test.model.rawAttributes['userId'];
 | 
				
			||||||
 | 
					      expect(attribute.type).toBeInstanceOf(DataTypes.INTEGER);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('case 3, json', async () => {
 | 
				
			||||||
 | 
					      const Test = db.collection({
 | 
				
			||||||
 | 
					        name: 'tests',
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            type: 'context',
 | 
				
			||||||
 | 
					            name: 'ua',
 | 
				
			||||||
 | 
					            dataIndex: 'userAgent',
 | 
				
			||||||
 | 
					            dataType: 'json',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const attribute = Test.model.rawAttributes['ua'];
 | 
				
			||||||
 | 
					      expect(attribute.type).toBeInstanceOf(DataTypes.JSON);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('create and update', () => {
 | 
				
			||||||
 | 
					    it('case 1', async () => {
 | 
				
			||||||
 | 
					      const Test = db.collection({
 | 
				
			||||||
 | 
					        name: 'tests',
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            type: 'context',
 | 
				
			||||||
 | 
					            name: 'clientIp',
 | 
				
			||||||
 | 
					            dataIndex: 'request.ip',
 | 
				
			||||||
 | 
					            dataType: 'string',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await db.sync();
 | 
				
			||||||
 | 
					      const t1 = await Test.repository.create({
 | 
				
			||||||
 | 
					        values: {},
 | 
				
			||||||
 | 
					        context: {
 | 
				
			||||||
 | 
					          request: {
 | 
				
			||||||
 | 
					            ip: '11.22.33.44',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(t1.get('clientIp')).toBe('11.22.33.44');
 | 
				
			||||||
 | 
					      const [t2] = await Test.repository.update({
 | 
				
			||||||
 | 
					        filterByPk: t1.get('id') as any,
 | 
				
			||||||
 | 
					        values: {},
 | 
				
			||||||
 | 
					        context: {
 | 
				
			||||||
 | 
					          request: {
 | 
				
			||||||
 | 
					            ip: '11.22.33.55',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(t2.get('clientIp')).toBe('11.22.33.55');
 | 
				
			||||||
 | 
					      const t3 = await Test.repository.findOne();
 | 
				
			||||||
 | 
					      expect(t3.get('clientIp')).toBe('11.22.33.55');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('case 2, createOnly = true', async () => {
 | 
				
			||||||
 | 
					      const Test = db.collection({
 | 
				
			||||||
 | 
					        name: 'tests',
 | 
				
			||||||
 | 
					        fields: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            type: 'context',
 | 
				
			||||||
 | 
					            name: 'clientIp',
 | 
				
			||||||
 | 
					            dataIndex: 'request.ip',
 | 
				
			||||||
 | 
					            dataType: 'string',
 | 
				
			||||||
 | 
					            createOnly: true,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await db.sync();
 | 
				
			||||||
 | 
					      const t1 = await Test.repository.create({
 | 
				
			||||||
 | 
					        values: {},
 | 
				
			||||||
 | 
					        context: {
 | 
				
			||||||
 | 
					          request: {
 | 
				
			||||||
 | 
					            ip: '11.22.33.44',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(t1.get('clientIp')).toBe('11.22.33.44');
 | 
				
			||||||
 | 
					      const [t2] = await Test.repository.update({
 | 
				
			||||||
 | 
					        filterByPk: t1.get('id') as any,
 | 
				
			||||||
 | 
					        values: {},
 | 
				
			||||||
 | 
					        context: {
 | 
				
			||||||
 | 
					          request: {
 | 
				
			||||||
 | 
					            ip: '11.22.33.55',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(t2.get('clientIp')).toBe('11.22.33.44');
 | 
				
			||||||
 | 
					      const t3 = await Test.repository.findOne();
 | 
				
			||||||
 | 
					      expect(t3.get('clientIp')).toBe('11.22.33.44');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										44
									
								
								packages/database/src/fields/context-field.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								packages/database/src/fields/context-field.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import { lodash } from '@umijs/utils';
 | 
				
			||||||
 | 
					import { DataTypes, Model } from 'sequelize';
 | 
				
			||||||
 | 
					import { BaseColumnFieldOptions, Field } from './field';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ContextField extends Field {
 | 
				
			||||||
 | 
					  get dataType() {
 | 
				
			||||||
 | 
					    const type: string = this.options.dataType || 'string';
 | 
				
			||||||
 | 
					    return DataTypes[type.toUpperCase()] || DataTypes.STRING;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init() {
 | 
				
			||||||
 | 
					    const { name, dataIndex } = this.options;
 | 
				
			||||||
 | 
					    this.listener = async (model: Model, options) => {
 | 
				
			||||||
 | 
					      const { context } = options;
 | 
				
			||||||
 | 
					      model.set(name, lodash.get(context, dataIndex));
 | 
				
			||||||
 | 
					      model.changed(name, true);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bind() {
 | 
				
			||||||
 | 
					    super.bind();
 | 
				
			||||||
 | 
					    const { createOnly } = this.options;
 | 
				
			||||||
 | 
					    this.on('beforeCreate', this.listener);
 | 
				
			||||||
 | 
					    if (!createOnly) {
 | 
				
			||||||
 | 
					      this.on('beforeUpdate', this.listener);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unbind() {
 | 
				
			||||||
 | 
					    super.unbind();
 | 
				
			||||||
 | 
					    const { createOnly } = this.options;
 | 
				
			||||||
 | 
					    this.off('beforeCreate', this.listener);
 | 
				
			||||||
 | 
					    if (!createOnly) {
 | 
				
			||||||
 | 
					      this.off('beforeUpdate', this.listener);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ContextFieldOptions extends BaseColumnFieldOptions {
 | 
				
			||||||
 | 
					  type: 'context';
 | 
				
			||||||
 | 
					  dataIndex: string;
 | 
				
			||||||
 | 
					  dataType?: string;
 | 
				
			||||||
 | 
					  createOnly?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -21,6 +21,7 @@ import { DateFieldOptions } from './date-field';
 | 
				
			|||||||
import { ArrayFieldOptions } from './array-field';
 | 
					import { ArrayFieldOptions } from './array-field';
 | 
				
			||||||
import { BaseFieldOptions } from './field';
 | 
					import { BaseFieldOptions } from './field';
 | 
				
			||||||
import { PasswordFieldOptions } from './password-field';
 | 
					import { PasswordFieldOptions } from './password-field';
 | 
				
			||||||
 | 
					import { ContextFieldOptions } from './context-field';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export * from './array-field';
 | 
					export * from './array-field';
 | 
				
			||||||
export * from './belongs-to-field';
 | 
					export * from './belongs-to-field';
 | 
				
			||||||
@ -39,6 +40,7 @@ export * from './time-field';
 | 
				
			|||||||
export * from './uid-field';
 | 
					export * from './uid-field';
 | 
				
			||||||
export * from './virtual-field';
 | 
					export * from './virtual-field';
 | 
				
			||||||
export * from './password-field';
 | 
					export * from './password-field';
 | 
				
			||||||
 | 
					export * from './context-field';
 | 
				
			||||||
export * from './field';
 | 
					export * from './field';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type FieldOptions =
 | 
					export type FieldOptions =
 | 
				
			||||||
@ -59,6 +61,7 @@ export type FieldOptions =
 | 
				
			|||||||
  | TimeFieldOptions
 | 
					  | TimeFieldOptions
 | 
				
			||||||
  | DateFieldOptions
 | 
					  | DateFieldOptions
 | 
				
			||||||
  | PasswordFieldOptions
 | 
					  | PasswordFieldOptions
 | 
				
			||||||
 | 
					  | ContextFieldOptions
 | 
				
			||||||
  | BelongsToFieldOptions
 | 
					  | BelongsToFieldOptions
 | 
				
			||||||
  | HasOneFieldOptions
 | 
					  | HasOneFieldOptions
 | 
				
			||||||
  | HasManyFieldOptions
 | 
					  | HasManyFieldOptions
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
    const sourceModel = await this.getSourceModel(transaction);
 | 
					    const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const createOptions = {
 | 
					    const createOptions = {
 | 
				
			||||||
 | 
					      ...options,
 | 
				
			||||||
      through: values[this.throughName()],
 | 
					      through: values[this.throughName()],
 | 
				
			||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
				
			|||||||
@ -145,13 +145,14 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (const instance of instances) {
 | 
					    for (const instance of instances) {
 | 
				
			||||||
      await updateModelByValues(instance, values, {
 | 
					      await updateModelByValues(instance, values, {
 | 
				
			||||||
 | 
					        ...options,
 | 
				
			||||||
        sanitized: true,
 | 
					        sanitized: true,
 | 
				
			||||||
        sourceModel: this.sourceModel,
 | 
					        sourceModel: this.sourceModel,
 | 
				
			||||||
        transaction,
 | 
					        transaction,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true;
 | 
					    return instances;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async destroy(options?: PK | DestroyOptions): Promise<Boolean> {
 | 
					  async destroy(options?: PK | DestroyOptions): Promise<Boolean> {
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ export abstract class RelationRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const sourceModel = await this.getSourceModel();
 | 
					    const sourceModel = await this.getSourceModel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const instance = await sourceModel[createAccessor](guard.sanitize(options.values));
 | 
					    const instance = await sourceModel[createAccessor](guard.sanitize(options.values), options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await updateAssociations(instance, values, options);
 | 
					    await updateAssociations(instance, values, options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -356,7 +356,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true;
 | 
					    return instances;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @transaction((args, transaction) => {
 | 
					  @transaction((args, transaction) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -68,7 +68,7 @@ export async function updateModelByValues(instance: Model, values: UpdateValue,
 | 
				
			|||||||
    values = guard.sanitize(values);
 | 
					    values = guard.sanitize(values);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await instance.update(values);
 | 
					  await instance.update(values, options);
 | 
				
			||||||
  await updateAssociations(instance, values, options);
 | 
					  await updateAssociations(instance, values, options);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user