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 { BaseFieldOptions } from './field';
|
||||
import { PasswordFieldOptions } from './password-field';
|
||||
import { ContextFieldOptions } from './context-field';
|
||||
|
||||
export * from './array-field';
|
||||
export * from './belongs-to-field';
|
||||
@ -39,6 +40,7 @@ export * from './time-field';
|
||||
export * from './uid-field';
|
||||
export * from './virtual-field';
|
||||
export * from './password-field';
|
||||
export * from './context-field';
|
||||
export * from './field';
|
||||
|
||||
export type FieldOptions =
|
||||
@ -59,6 +61,7 @@ export type FieldOptions =
|
||||
| TimeFieldOptions
|
||||
| DateFieldOptions
|
||||
| PasswordFieldOptions
|
||||
| ContextFieldOptions
|
||||
| BelongsToFieldOptions
|
||||
| HasOneFieldOptions
|
||||
| HasManyFieldOptions
|
||||
|
@ -38,6 +38,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
const createOptions = {
|
||||
...options,
|
||||
through: values[this.throughName()],
|
||||
transaction,
|
||||
};
|
||||
|
@ -145,13 +145,14 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
|
||||
for (const instance of instances) {
|
||||
await updateModelByValues(instance, values, {
|
||||
...options,
|
||||
sanitized: true,
|
||||
sourceModel: this.sourceModel,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
return instances;
|
||||
}
|
||||
|
||||
async destroy(options?: PK | DestroyOptions): Promise<Boolean> {
|
||||
|
@ -39,7 +39,7 @@ export abstract class RelationRepository {
|
||||
|
||||
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);
|
||||
|
||||
|
@ -356,7 +356,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
return instances;
|
||||
}
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
|
@ -68,7 +68,7 @@ export async function updateModelByValues(instance: Model, values: UpdateValue,
|
||||
values = guard.sanitize(values);
|
||||
}
|
||||
|
||||
await instance.update(values);
|
||||
await instance.update(values, options);
|
||||
await updateAssociations(instance, values, options);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user