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