feat: context field type support (#131)

* feat: context field type support

* fix: missing options
This commit is contained in:
chenos 2021-12-07 21:19:28 +08:00 committed by GitHub
parent 54f351af9d
commit bf2840b089
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 193 additions and 4 deletions

View 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');
});
});
});

View 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;
}

View File

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

View File

@ -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,
}; };

View File

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

View File

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

View File

@ -356,7 +356,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
}); });
} }
return true; return instances;
} }
@transaction((args, transaction) => { @transaction((args, transaction) => {

View File

@ -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);
} }