feat: filter by target key (#146)
* feat: filter by target key * fix: repository test * change type name * chore: test * change PrimaryKey type to TargetKey * rename filterTargetKey * rename variables * change option parser constructor * add option parser targetKey * change filter parser constructor * fix: custom model Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
79ba391aee
commit
2bf09bf9bb
@ -1,5 +1,6 @@
|
||||
import { mockDatabase } from './index';
|
||||
import path from 'path';
|
||||
import { Model } from '..';
|
||||
|
||||
describe('database', () => {
|
||||
test('import', async () => {
|
||||
@ -165,4 +166,30 @@ describe('database', () => {
|
||||
|
||||
expect(listener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('custom model', async () => {
|
||||
class CustomModel extends Model {
|
||||
customMethod() {
|
||||
this.setDataValue('abc', 'abc');
|
||||
}
|
||||
}
|
||||
|
||||
const db = mockDatabase();
|
||||
|
||||
db.registerModels({
|
||||
CustomModel,
|
||||
});
|
||||
|
||||
const Test = db.collection({
|
||||
name: 'tests',
|
||||
model: 'CustomModel',
|
||||
});
|
||||
|
||||
await Test.sync();
|
||||
|
||||
const test = await Test.model.create<any>();
|
||||
test.customMethod();
|
||||
expect(test.get('abc')).toBe('abc');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -87,7 +87,7 @@ describe('context field', () => {
|
||||
});
|
||||
expect(t1.get('clientIp')).toBe('11.22.33.44');
|
||||
const [t2] = await Test.repository.update({
|
||||
filterByPk: t1.get('id') as any,
|
||||
filterByTk: t1.get('id') as any,
|
||||
values: {},
|
||||
context: {
|
||||
request: {
|
||||
@ -124,7 +124,7 @@ describe('context field', () => {
|
||||
});
|
||||
expect(t1.get('clientIp')).toBe('11.22.33.44');
|
||||
const [t2] = await Test.repository.update({
|
||||
filterByPk: t1.get('id') as any,
|
||||
filterByTk: t1.get('id') as any,
|
||||
values: {},
|
||||
context: {
|
||||
request: {
|
||||
|
@ -12,9 +12,14 @@ test('filter item by string', async () => {
|
||||
|
||||
await database.sync();
|
||||
|
||||
const filterParser = new FilterParser(UserCollection.model, UserCollection.context.database, {
|
||||
name: 'hello',
|
||||
});
|
||||
const filterParser = new FilterParser(
|
||||
{
|
||||
name: 'hello',
|
||||
},
|
||||
{
|
||||
collection: UserCollection,
|
||||
},
|
||||
);
|
||||
|
||||
const filterParams = filterParser.toSequelizeParams();
|
||||
|
||||
@ -74,11 +79,9 @@ describe('filter by related', () => {
|
||||
'posts.title.$iLike': '%hello%',
|
||||
};
|
||||
|
||||
const filterParser = new FilterParser(
|
||||
db.getCollection('users').model,
|
||||
db.getCollection('users').context.database,
|
||||
filter,
|
||||
);
|
||||
const filterParser = new FilterParser(filter, {
|
||||
collection: db.getCollection('users'),
|
||||
});
|
||||
|
||||
const filterParams = filterParser.toSequelizeParams();
|
||||
|
||||
@ -91,11 +94,9 @@ describe('filter by related', () => {
|
||||
'posts.comments.content.$iLike': '%hello%',
|
||||
};
|
||||
|
||||
const filterParser = new FilterParser(
|
||||
db.getCollection('users').model,
|
||||
db.getCollection('users').context.database,
|
||||
filter,
|
||||
);
|
||||
const filterParser = new FilterParser(filter, {
|
||||
collection: db.getCollection('users'),
|
||||
});
|
||||
|
||||
const filterParams = filterParser.toSequelizeParams();
|
||||
|
||||
|
@ -13,9 +13,7 @@ describe('array field operator', function () {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
db = mockDatabase({
|
||||
logging: console.log,
|
||||
});
|
||||
db = mockDatabase({});
|
||||
|
||||
Test = db.collection({
|
||||
name: 'test',
|
||||
@ -66,7 +64,7 @@ describe('array field operator', function () {
|
||||
expect(result.get('id')).toEqual(p1.get('id'));
|
||||
|
||||
await Post.repository.update({
|
||||
filterByPk: <any>p1.get('id'),
|
||||
filterByTk: <any>p1.get('id'),
|
||||
values: {
|
||||
tags: ['t3', 't2'],
|
||||
},
|
||||
|
@ -60,7 +60,10 @@ describe('option parser', () => {
|
||||
fields: ['id', 'name', 'tags.id', 'tags.name'],
|
||||
};
|
||||
|
||||
const parser = new OptionsParser(Post.model, Post.context.database, options);
|
||||
const parser = new OptionsParser(options, {
|
||||
collection: Post,
|
||||
});
|
||||
|
||||
const params = parser.toSequelizeParams();
|
||||
|
||||
expect(params).toEqual({
|
||||
@ -78,7 +81,9 @@ describe('option parser', () => {
|
||||
sort: ['id'],
|
||||
};
|
||||
|
||||
let parser = new OptionsParser(User.model, User.context.database, options);
|
||||
let parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
let params = parser.toSequelizeParams();
|
||||
expect(params['order']).toEqual([['id', 'ASC']]);
|
||||
|
||||
@ -86,7 +91,9 @@ describe('option parser', () => {
|
||||
sort: ['id', '-posts.title', 'posts.comments.createdAt'],
|
||||
};
|
||||
|
||||
parser = new OptionsParser(User.model, User.context.database, options);
|
||||
parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
params = parser.toSequelizeParams();
|
||||
expect(params['order']).toEqual([
|
||||
['id', 'ASC'],
|
||||
@ -100,7 +107,9 @@ describe('option parser', () => {
|
||||
fields: ['id', 'posts'],
|
||||
};
|
||||
// 转换为 attributes: ['id'], include: [{association: 'posts'}]
|
||||
let parser = new OptionsParser(User.model, User.context.database, options);
|
||||
let parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
let params = parser.toSequelizeParams();
|
||||
|
||||
expect(params['attributes']).toContain('id');
|
||||
@ -111,7 +120,9 @@ describe('option parser', () => {
|
||||
appends: ['posts'],
|
||||
};
|
||||
|
||||
parser = new OptionsParser(User.model, User.context.database, options);
|
||||
parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
params = parser.toSequelizeParams();
|
||||
expect(params['attributes']['include']).toEqual([]);
|
||||
expect(params['include'][0]['association']).toEqual('posts');
|
||||
@ -121,7 +132,9 @@ describe('option parser', () => {
|
||||
fields: ['id', 'posts.title'],
|
||||
};
|
||||
|
||||
parser = new OptionsParser(User.model, User.context.database, options);
|
||||
parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
params = parser.toSequelizeParams();
|
||||
expect(params['attributes']).toContain('id');
|
||||
expect(params['include'][0]['association']).toEqual('posts');
|
||||
@ -132,7 +145,9 @@ describe('option parser', () => {
|
||||
fields: ['id', 'posts', 'posts.comments.content'],
|
||||
};
|
||||
|
||||
parser = new OptionsParser(User.model, User.context.database, options);
|
||||
parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
params = parser.toSequelizeParams();
|
||||
expect(params['attributes']).toContain('id');
|
||||
expect(params['include'][0]['association']).toEqual('posts');
|
||||
@ -143,7 +158,9 @@ describe('option parser', () => {
|
||||
options = {
|
||||
except: ['id'],
|
||||
};
|
||||
parser = new OptionsParser(User.model, User.context.database, options);
|
||||
parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
params = parser.toSequelizeParams();
|
||||
expect(params['attributes']['exclude']).toContain('id');
|
||||
|
||||
@ -153,7 +170,9 @@ describe('option parser', () => {
|
||||
except: ['posts.id'],
|
||||
};
|
||||
|
||||
parser = new OptionsParser(User.model, User.context.database, options);
|
||||
parser = new OptionsParser(options, {
|
||||
collection: User,
|
||||
});
|
||||
params = parser.toSequelizeParams();
|
||||
|
||||
expect(params['include'][0]['attributes']['exclude']).toContain('id');
|
||||
|
@ -1,6 +1,113 @@
|
||||
import { mockDatabase } from '../index';
|
||||
import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository';
|
||||
import Database from '../../database';
|
||||
import { Collection, HasManyRepository } from '@nocobase/database';
|
||||
|
||||
describe('belongs to many with target key', function () {
|
||||
let db: Database;
|
||||
let Tag: Collection;
|
||||
let Post: Collection;
|
||||
beforeEach(async () => {
|
||||
db = mockDatabase();
|
||||
|
||||
Post = db.collection({
|
||||
name: 'posts',
|
||||
filterTargetKey: 'title',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{ type: 'string', name: 'title', primaryKey: true },
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'tags',
|
||||
sourceKey: 'title',
|
||||
foreignKey: 'postTitle',
|
||||
targetKey: 'name',
|
||||
otherKey: 'tagName',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Tag = db.collection({
|
||||
name: 'tags',
|
||||
filterTargetKey: 'name',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{ type: 'string', name: 'name', primaryKey: true },
|
||||
{ type: 'string', name: 'status' },
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync({ force: true });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('destroy by target key', async () => {
|
||||
const t1 = await Tag.repository.create({
|
||||
values: {
|
||||
name: 't1',
|
||||
},
|
||||
});
|
||||
|
||||
const t2 = await Tag.repository.create({
|
||||
values: {
|
||||
name: 't2',
|
||||
},
|
||||
});
|
||||
|
||||
const p1 = await Post.repository.create({
|
||||
values: { title: 'p1' },
|
||||
});
|
||||
|
||||
const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.get('title') as string);
|
||||
|
||||
await PostTagRepository.set([t1.get('name') as string, t2.get('name')]);
|
||||
|
||||
await PostTagRepository.destroy();
|
||||
|
||||
const [_, count] = await PostTagRepository.findAndCount();
|
||||
expect(count).toEqual(0);
|
||||
});
|
||||
|
||||
test('destroy with target key and filter', async () => {
|
||||
let t1 = await Tag.repository.create({
|
||||
values: {
|
||||
name: 't1',
|
||||
status: 'published',
|
||||
},
|
||||
});
|
||||
|
||||
const t2 = await Tag.repository.create({
|
||||
values: {
|
||||
name: 't2',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
const p1 = await Post.repository.create({
|
||||
values: { title: 'p1' },
|
||||
});
|
||||
|
||||
const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.get('title') as string);
|
||||
|
||||
await PostTagRepository.set([t1.get('name') as string, t2.get('name') as string]);
|
||||
|
||||
let [_, count] = await PostTagRepository.findAndCount();
|
||||
expect(count).toEqual(2);
|
||||
|
||||
await PostTagRepository.destroy({
|
||||
filterByTk: t1.get('name') as string,
|
||||
filter: {
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
[_, count] = await PostTagRepository.findAndCount();
|
||||
expect(count).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('belongs to many', () => {
|
||||
let db: Database;
|
||||
@ -51,6 +158,7 @@ describe('belongs to many', () => {
|
||||
],
|
||||
});
|
||||
|
||||
await db.sequelize.getQueryInterface().dropAllTables();
|
||||
await db.sync({ force: true });
|
||||
});
|
||||
|
||||
@ -384,7 +492,7 @@ describe('belongs to many', () => {
|
||||
await PostTagRepository.set([t1.id, t2.id]);
|
||||
|
||||
const findByPkResult = await PostTagRepository.findOne({
|
||||
filterByPk: t2.id,
|
||||
filterByTk: t2.id,
|
||||
});
|
||||
|
||||
expect(findByPkResult.name).toEqual('t2');
|
||||
@ -480,7 +588,7 @@ describe('belongs to many', () => {
|
||||
expect(count).toEqual(2);
|
||||
|
||||
await PostTagRepository.destroy({
|
||||
filterByPk: t1.get('id') as number,
|
||||
filterByTk: t1.get('id') as number,
|
||||
filter: {
|
||||
status: 'draft',
|
||||
},
|
||||
@ -511,10 +619,11 @@ describe('belongs to many', () => {
|
||||
|
||||
await PostTagRepository.set([t1.id, t2.id]);
|
||||
|
||||
expect(await PostTagRepository.count()).toEqual(2);
|
||||
|
||||
await PostTagRepository.destroy(t2.id);
|
||||
|
||||
const result = await PostTagRepository.findAndCount();
|
||||
expect(result[1]).toEqual(1);
|
||||
expect(await PostTagRepository.count()).toEqual(1);
|
||||
});
|
||||
|
||||
test('transaction', async () => {
|
||||
|
@ -2,6 +2,116 @@ import { mockDatabase } from '../index';
|
||||
import { HasManyRepository } from '../../relation-repository/hasmany-repository';
|
||||
import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository';
|
||||
|
||||
describe('has many with target key', function () {
|
||||
test('target key with filterTargetKey', async () => {
|
||||
const db = mockDatabase();
|
||||
const User = db.collection<{ id: number; name: string }, { name: string }>({
|
||||
name: 'users',
|
||||
filterTargetKey: 'name',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{ type: 'string', name: 'name', unique: true },
|
||||
{ type: 'integer', name: 'age' },
|
||||
{ type: 'hasMany', name: 'posts', sourceKey: 'name', foreignKey: 'userName', targetKey: 'title' },
|
||||
],
|
||||
});
|
||||
|
||||
const Post = db.collection({
|
||||
name: 'posts',
|
||||
filterTargetKey: 'title',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{ type: 'string', name: 'title', unique: true },
|
||||
{ type: 'string', name: 'status' },
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user',
|
||||
targetKey: 'name',
|
||||
foreignKey: 'userName',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('destroy by target key and filter', async () => {
|
||||
const db = mockDatabase();
|
||||
const User = db.collection<{ id: number; name: string }, { name: string }>({
|
||||
name: 'users',
|
||||
filterTargetKey: 'name',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{ type: 'string', name: 'name', unique: true },
|
||||
{ type: 'integer', name: 'age' },
|
||||
{ type: 'hasMany', name: 'posts', sourceKey: 'name', foreignKey: 'userName', targetKey: 'title' },
|
||||
],
|
||||
});
|
||||
|
||||
const Post = db.collection({
|
||||
name: 'posts',
|
||||
filterTargetKey: 'title',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{ type: 'string', name: 'title', unique: true },
|
||||
{ type: 'string', name: 'status' },
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user',
|
||||
targetKey: 'name',
|
||||
foreignKey: 'userName',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync({ force: true });
|
||||
|
||||
const u1 = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
const UserPostRepository = new HasManyRepository(User, 'posts', u1.get('name') as string);
|
||||
|
||||
const p1 = await UserPostRepository.create({
|
||||
values: {
|
||||
title: 't1',
|
||||
status: 'published',
|
||||
},
|
||||
});
|
||||
|
||||
const p2 = await UserPostRepository.create({
|
||||
values: {
|
||||
title: 't2',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
await UserPostRepository.destroy({
|
||||
filterByTk: p1.title,
|
||||
filter: { status: 'draft' },
|
||||
});
|
||||
|
||||
expect(await UserPostRepository.count()).toEqual(2);
|
||||
|
||||
await UserPostRepository.destroy({
|
||||
filterByTk: p1.title,
|
||||
filter: {
|
||||
status: 'published',
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
await UserPostRepository.findOne({
|
||||
filterByTk: p1.title,
|
||||
}),
|
||||
).toBeNull();
|
||||
|
||||
expect(
|
||||
await UserPostRepository.findOne({
|
||||
filterByTk: p2.id,
|
||||
}),
|
||||
).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('has many repository', () => {
|
||||
let db;
|
||||
let User;
|
||||
@ -190,7 +300,7 @@ describe('has many repository', () => {
|
||||
});
|
||||
|
||||
await UserPostRepository.destroy({
|
||||
filterByPk: p1.id,
|
||||
filterByTk: p1.id,
|
||||
filter: {
|
||||
status: 'draft',
|
||||
},
|
||||
@ -199,7 +309,7 @@ describe('has many repository', () => {
|
||||
expect(await UserPostRepository.count()).toEqual(2);
|
||||
|
||||
await UserPostRepository.destroy({
|
||||
filterByPk: p1.id,
|
||||
filterByTk: p1.id,
|
||||
filter: {
|
||||
status: 'published',
|
||||
},
|
||||
@ -207,13 +317,13 @@ describe('has many repository', () => {
|
||||
|
||||
expect(
|
||||
await UserPostRepository.findOne({
|
||||
filterByPk: p1.id,
|
||||
filterByTk: p1.id,
|
||||
}),
|
||||
).toBeNull();
|
||||
|
||||
expect(
|
||||
await UserPostRepository.findOne({
|
||||
filterByPk: p2.id,
|
||||
filterByTk: p2.id,
|
||||
}),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
@ -2,6 +2,45 @@ import { Collection } from '../collection';
|
||||
import { Database } from '../database';
|
||||
import { mockDatabase } from './';
|
||||
|
||||
describe('find by targetKey', function () {
|
||||
it('can filter by target key', async () => {
|
||||
const db = mockDatabase({});
|
||||
|
||||
const User = db.collection({
|
||||
name: 'users',
|
||||
filterTargetKey: 'name',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
unique: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
|
||||
await User.repository.create({
|
||||
values: {
|
||||
name: 'user1',
|
||||
},
|
||||
});
|
||||
|
||||
await User.repository.create({
|
||||
values: {
|
||||
name: 'user2',
|
||||
},
|
||||
});
|
||||
|
||||
const user2 = await User.repository.findOne({
|
||||
filterByTk: 'user2',
|
||||
});
|
||||
|
||||
expect(user2.get('name')).toEqual('user2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('repository.find', () => {
|
||||
let db: Database;
|
||||
let User: Collection;
|
||||
@ -116,7 +155,7 @@ describe('repository.find', () => {
|
||||
});
|
||||
|
||||
const result = await Test.repository.findOne({
|
||||
filterByPk: <number>t1.get('id'),
|
||||
filterByTk: <number>t1.get('id'),
|
||||
filter: {
|
||||
status: 'published',
|
||||
},
|
||||
@ -236,7 +275,7 @@ describe('repository.update', () => {
|
||||
name: 'user1',
|
||||
});
|
||||
await User.repository.update({
|
||||
filterByPk: user.id,
|
||||
filterByTk: user.id,
|
||||
values: {
|
||||
name: 'user11',
|
||||
posts: [{ name: 'post1' }],
|
||||
@ -263,28 +302,25 @@ describe('repository.update', () => {
|
||||
it('update2', async () => {
|
||||
const user = await User.model.create<any>({
|
||||
name: 'user1',
|
||||
posts: [{ name: 'post1' }],
|
||||
});
|
||||
|
||||
const user2 = await User.model.create<any>({
|
||||
name: 'user2',
|
||||
});
|
||||
|
||||
await User.repository.update({
|
||||
filterByPk: user.id,
|
||||
filterByTk: user.id,
|
||||
values: {
|
||||
name: 'user11',
|
||||
posts: [{ name: 'post1' }],
|
||||
},
|
||||
});
|
||||
|
||||
const updated = await User.model.findByPk(user.id);
|
||||
expect(updated).toMatchObject({
|
||||
name: 'user11',
|
||||
});
|
||||
const post = await Post.model.findOne({
|
||||
where: {
|
||||
name: 'post1',
|
||||
},
|
||||
});
|
||||
expect(post).toMatchObject({
|
||||
name: 'post1',
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
expect(updated.get('name')).toEqual('user11');
|
||||
|
||||
const u2 = await User.model.findByPk(user2.id);
|
||||
expect(u2.get('name')).toEqual('user2');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,80 @@
|
||||
import { mockDatabase } from '../index';
|
||||
import { Collection } from '../../collection';
|
||||
import { Database } from '@nocobase/database';
|
||||
|
||||
describe('destroy with targetKey', function () {
|
||||
let db: Database;
|
||||
let User: Collection;
|
||||
let u1;
|
||||
let u2;
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
db = mockDatabase();
|
||||
|
||||
User = db.collection({
|
||||
name: 'users',
|
||||
autoGenId: false,
|
||||
fields: [
|
||||
{ type: 'string', name: 'name', primaryKey: true },
|
||||
{ type: 'string', name: 'status' },
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync({
|
||||
force: true,
|
||||
});
|
||||
|
||||
u1 = await User.repository.create({
|
||||
values: {
|
||||
name: 'u1',
|
||||
status: 'published',
|
||||
},
|
||||
});
|
||||
|
||||
u2 = await User.repository.create({
|
||||
values: {
|
||||
name: 'u2',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy all', async () => {
|
||||
expect(await User.repository.count()).toEqual(2);
|
||||
await User.repository.destroy({ truncate: true });
|
||||
expect(await User.repository.count()).toEqual(0);
|
||||
});
|
||||
|
||||
it('should destroy by target key', async () => {
|
||||
await User.repository.destroy({
|
||||
filterByTk: 'u2',
|
||||
});
|
||||
|
||||
expect(await User.repository.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should destroy by target key and filter', async () => {
|
||||
await User.repository.destroy({
|
||||
filterByTk: 'u1',
|
||||
filter: {
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
expect(await User.repository.count()).toEqual(2);
|
||||
|
||||
await User.repository.destroy({
|
||||
filterByTk: 'u2',
|
||||
filter: {
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
expect(await User.repository.count()).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
let db;
|
||||
@ -36,7 +111,7 @@ describe('destroy', () => {
|
||||
});
|
||||
|
||||
await Post.repository.destroy({
|
||||
filterByPk: p1.get('id') as number,
|
||||
filterByTk: p1.get('id') as number,
|
||||
filter: {
|
||||
status: 'draft',
|
||||
},
|
||||
|
@ -15,6 +15,7 @@ export type RepositoryType = typeof Repository;
|
||||
export interface CollectionOptions extends Omit<ModelOptions, 'name'> {
|
||||
name: string;
|
||||
tableName?: string;
|
||||
filterTargetKey?: string;
|
||||
fields?: FieldOptions[];
|
||||
model?: string | ModelCtor<Model>;
|
||||
repository?: string | RepositoryType;
|
||||
@ -37,6 +38,10 @@ export class Collection<
|
||||
model: ModelCtor<Model<TModelAttributes, TCreationAttributes>>;
|
||||
repository: Repository<TModelAttributes, TCreationAttributes>;
|
||||
|
||||
get filterTargetKey() {
|
||||
return lodash.get(this.options, 'filterTargetKey', this.model.primaryKeyAttribute);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.options.name;
|
||||
}
|
||||
@ -54,7 +59,7 @@ export class Collection<
|
||||
private sequelizeModelOptions() {
|
||||
const { name, tableName } = this.options;
|
||||
return {
|
||||
..._.omit(this.options, ['name', 'fields']),
|
||||
..._.omit(this.options, ['name', 'fields', 'model']),
|
||||
modelName: name,
|
||||
sequelize: this.context.database.sequelize,
|
||||
tableName: tableName || name,
|
||||
|
@ -19,6 +19,10 @@ export abstract class RelationField extends Field {
|
||||
return this.options.sourceKey;
|
||||
}
|
||||
|
||||
get targetKey() {
|
||||
return this.options.targetKey || this.TargetModel.primaryKeyAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* get target model from database by it's name
|
||||
* @constructor
|
||||
|
@ -3,20 +3,30 @@ import _ from 'lodash';
|
||||
import { flatten, unflatten } from 'flat';
|
||||
import { Database } from './database';
|
||||
import lodash from 'lodash';
|
||||
import { Collection } from './collection';
|
||||
|
||||
const debug = require('debug')('noco-database');
|
||||
|
||||
type FilterType = any;
|
||||
|
||||
interface FilterParserContext {
|
||||
collection: Collection;
|
||||
}
|
||||
|
||||
export default class FilterParser {
|
||||
collection: Collection;
|
||||
database: Database;
|
||||
model: ModelCtor<Model>;
|
||||
filter: FilterType;
|
||||
context: FilterParserContext;
|
||||
|
||||
constructor(model: ModelCtor<any>, database: Database, filter: FilterType) {
|
||||
this.model = model;
|
||||
constructor(filter: FilterType, context: FilterParserContext) {
|
||||
const { collection } = context;
|
||||
this.collection = collection;
|
||||
this.context = context;
|
||||
this.model = collection.model;
|
||||
this.filter = this.prepareFilter(filter);
|
||||
this.database = database;
|
||||
this.database = collection.context.database;
|
||||
}
|
||||
|
||||
prepareFilter(filter: FilterType) {
|
||||
|
@ -2,20 +2,32 @@ import { Appends, Except, FindOptions } from './repository';
|
||||
import FilterParser from './filter-parser';
|
||||
import { FindAttributeOptions, ModelCtor, Op } from 'sequelize';
|
||||
import { Database } from './database';
|
||||
import { Collection } from './collection';
|
||||
|
||||
const debug = require('debug')('noco-database');
|
||||
|
||||
interface OptionsParserContext {
|
||||
collection: Collection;
|
||||
targetKey?: string;
|
||||
}
|
||||
|
||||
export class OptionsParser {
|
||||
options: FindOptions;
|
||||
database: Database;
|
||||
collection: Collection;
|
||||
model: ModelCtor<any>;
|
||||
filterParser: FilterParser;
|
||||
context: OptionsParserContext;
|
||||
|
||||
constructor(model: ModelCtor<any>, database: Database, options: FindOptions) {
|
||||
this.model = model;
|
||||
constructor(options: FindOptions, context: OptionsParserContext) {
|
||||
const { collection } = context;
|
||||
|
||||
this.collection = collection;
|
||||
this.model = collection.model;
|
||||
this.options = options;
|
||||
this.database = database;
|
||||
this.filterParser = new FilterParser(model, this.database, options?.filter);
|
||||
this.database = collection.context.database;
|
||||
this.filterParser = new FilterParser(options?.filter, { collection });
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
isAssociation(key: string) {
|
||||
@ -26,27 +38,15 @@ export class OptionsParser {
|
||||
return this.isAssociation(path.split('.')[0]);
|
||||
}
|
||||
|
||||
parseFilterByPk() {
|
||||
if (this.options?.filterByPk) {
|
||||
return {
|
||||
where: {
|
||||
[this.model.primaryKeyAttribute]: this.options.filterByPk,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
toSequelizeParams() {
|
||||
const queryParams = this.filterParser.toSequelizeParams();
|
||||
|
||||
if (this.options?.filterByPk) {
|
||||
if (this.options?.filterByTk) {
|
||||
queryParams.where = {
|
||||
[Op.and]: [
|
||||
queryParams.where,
|
||||
{
|
||||
[this.model.primaryKeyAttribute]: this.options.filterByPk,
|
||||
[this.context.targetKey || this.collection.filterTargetKey]: this.options.filterByTk,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BelongsToMany, Model, Op, Transaction } from 'sequelize';
|
||||
import { updateThroughTableValue } from '../update-associations';
|
||||
import { FindAndCountOptions, FindOneOptions, MultipleRelationRepository } from './multiple-relation-repository';
|
||||
import { CreateOptions, DestroyOptions, FindOptions, PrimaryKey, UpdateOptions } from '../repository';
|
||||
import { CreateOptions, DestroyOptions, FindOptions, TargetKey, UpdateOptions } from '../repository';
|
||||
import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
|
||||
import lodash from 'lodash';
|
||||
import { transaction } from './relation-repository';
|
||||
@ -19,12 +19,12 @@ interface IBelongsToManyRepository<M extends Model> {
|
||||
// 删除
|
||||
destroy(options?: number | string | number[] | string[] | DestroyOptions): Promise<Boolean>;
|
||||
// 建立关联
|
||||
set(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
|
||||
set(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
|
||||
// 附加关联,存在中间表数据
|
||||
add(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
|
||||
add(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
|
||||
// 移除关联
|
||||
remove(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
|
||||
toggle(options: PrimaryKey | { pk?: PrimaryKey; transaction?: Transaction }): Promise<void>;
|
||||
remove(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
|
||||
toggle(options: TargetKey | { pk?: TargetKey; transaction?: Transaction }): Promise<void>;
|
||||
}
|
||||
|
||||
export class BelongsToManyRepository extends MultipleRelationRepository implements IBelongsToManyRepository<any> {
|
||||
@ -48,22 +48,22 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
return {
|
||||
filterByPk: args[0],
|
||||
filterByTk: args[0],
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async destroy(options?: PrimaryKey | PrimaryKey[] | DestroyOptions): Promise<Boolean> {
|
||||
async destroy(options?: TargetKey | TargetKey[] | DestroyOptions): Promise<Boolean> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
const association = <BelongsToMany>this.association;
|
||||
|
||||
const instancesToIds = (instances) => {
|
||||
return instances.map((instance) => instance.get(this.target.primaryKeyAttribute));
|
||||
return instances.map((instance) => instance.get(this.targetKey()));
|
||||
};
|
||||
|
||||
// Through Table
|
||||
const throughTableWhere: Array<any> = [
|
||||
{
|
||||
[association.foreignKey]: this.sourceId,
|
||||
[association.foreignKey]: this.sourceKeyValue,
|
||||
},
|
||||
];
|
||||
|
||||
@ -78,12 +78,12 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
ids = instancesToIds(instances);
|
||||
}
|
||||
|
||||
if (options && options['filterByPk']) {
|
||||
const instances = (<any>this.association).toInstanceArray(options['filterByPk']);
|
||||
if (options && options['filterByTk']) {
|
||||
const instances = (<any>this.association).toInstanceArray(options['filterByTk']);
|
||||
ids = ids ? lodash.intersection(ids, instancesToIds(instances)) : instancesToIds(instances);
|
||||
}
|
||||
|
||||
if (options && !options['filterByPk'] && !options['filter']) {
|
||||
if (options && !options['filterByTk'] && !options['filter']) {
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
const instances = await sourceModel[this.accessors().get]({
|
||||
@ -105,9 +105,9 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
transaction,
|
||||
});
|
||||
|
||||
await this.target.destroy({
|
||||
await this.targetModel.destroy({
|
||||
where: {
|
||||
[this.target.primaryKeyAttribute]: {
|
||||
[this.targetKey()]: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
@ -119,14 +119,9 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
|
||||
protected async setTargets(
|
||||
call: 'add' | 'set',
|
||||
options:
|
||||
| PrimaryKey
|
||||
| PrimaryKey[]
|
||||
| PrimaryKeyWithThroughValues
|
||||
| PrimaryKeyWithThroughValues[]
|
||||
| AssociatedOptions,
|
||||
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
|
||||
) {
|
||||
let handleKeys: PrimaryKey[] | PrimaryKeyWithThroughValues[];
|
||||
let handleKeys: TargetKey[] | PrimaryKeyWithThroughValues[];
|
||||
|
||||
const transaction = await this.getTransaction(options, false);
|
||||
|
||||
@ -135,12 +130,12 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
}
|
||||
|
||||
if (lodash.isString(options) || lodash.isNumber(options)) {
|
||||
handleKeys = [<PrimaryKey>options];
|
||||
handleKeys = [<TargetKey>options];
|
||||
} // if it is type primaryKeyWithThroughValues
|
||||
else if (lodash.isArray(options) && options.length == 2 && lodash.isPlainObject(options[0][1])) {
|
||||
handleKeys = [<PrimaryKeyWithThroughValues>options];
|
||||
} else {
|
||||
handleKeys = <PrimaryKey[] | PrimaryKeyWithThroughValues[]>options;
|
||||
handleKeys = <TargetKey[] | PrimaryKeyWithThroughValues[]>options;
|
||||
}
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
@ -160,7 +155,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
|
||||
for (const [id, throughValues] of Object.entries(setObj)) {
|
||||
if (typeof throughValues === 'object') {
|
||||
const instance = await this.target.findByPk(id, {
|
||||
const instance = await this.targetModel.findByPk(id, {
|
||||
transaction,
|
||||
});
|
||||
await updateThroughTableValue(instance, this.throughName(), throughValues, sourceModel, transaction);
|
||||
@ -175,12 +170,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
};
|
||||
})
|
||||
async add(
|
||||
options:
|
||||
| PrimaryKey
|
||||
| PrimaryKey[]
|
||||
| PrimaryKeyWithThroughValues
|
||||
| PrimaryKeyWithThroughValues[]
|
||||
| AssociatedOptions,
|
||||
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
|
||||
): Promise<void> {
|
||||
await this.setTargets('add', options);
|
||||
}
|
||||
@ -192,12 +182,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
};
|
||||
})
|
||||
async set(
|
||||
options:
|
||||
| PrimaryKey
|
||||
| PrimaryKey[]
|
||||
| PrimaryKeyWithThroughValues
|
||||
| PrimaryKeyWithThroughValues[]
|
||||
| AssociatedOptions,
|
||||
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
|
||||
): Promise<void> {
|
||||
await this.setTargets('set', options);
|
||||
}
|
||||
@ -208,7 +193,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async toggle(options: PrimaryKey | { pk?: PrimaryKey; transaction?: Transaction }): Promise<void> {
|
||||
async toggle(options: TargetKey | { pk?: TargetKey; transaction?: Transaction }): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
const has = await sourceModel[this.accessors().hasSingle](options['pk'], {
|
||||
|
@ -6,8 +6,9 @@ import {
|
||||
FindOneOptions,
|
||||
MultipleRelationRepository,
|
||||
} from './multiple-relation-repository';
|
||||
import { CreateOptions, DestroyOptions, FindOptions, PK, PrimaryKey, UpdateOptions } from '../repository';
|
||||
import { CreateOptions, DestroyOptions, FindOptions, TK, TargetKey, UpdateOptions } from '../repository';
|
||||
import { transaction } from './relation-repository';
|
||||
import lodash from 'lodash';
|
||||
|
||||
interface IHasManyRepository<M extends Model> {
|
||||
find(options?: FindOptions): Promise<M>;
|
||||
@ -18,30 +19,30 @@ interface IHasManyRepository<M extends Model> {
|
||||
// 更新
|
||||
update(options?: UpdateOptions): Promise<M>;
|
||||
// 删除
|
||||
destroy(options?: PK | DestroyOptions): Promise<Boolean>;
|
||||
destroy(options?: TK | DestroyOptions): Promise<Boolean>;
|
||||
// 建立关联
|
||||
set(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
|
||||
set(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
|
||||
// 附加关联
|
||||
add(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
|
||||
add(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
|
||||
// 移除关联
|
||||
remove(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
|
||||
remove(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
|
||||
}
|
||||
|
||||
export class HasManyRepository extends MultipleRelationRepository implements IHasManyRepository<any> {
|
||||
@transaction((args, transaction) => {
|
||||
return {
|
||||
filterByPk: args[0],
|
||||
filterByTk: args[0],
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async destroy(options?: PK | DestroyOptions): Promise<Boolean> {
|
||||
async destroy(options?: TK | DestroyOptions): Promise<Boolean> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
const where = [
|
||||
{
|
||||
[this.association.foreignKey]: sourceModel.get(this.source.model.primaryKeyAttribute),
|
||||
[this.association.foreignKey]: sourceModel.get((this.association as any).sourceKey),
|
||||
},
|
||||
];
|
||||
|
||||
@ -54,21 +55,18 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
|
||||
|
||||
where.push(filterResult.where);
|
||||
}
|
||||
if (options && options['filterByPk']) {
|
||||
if (typeof options === 'object' && options['filterByPk']) {
|
||||
options = options['filterByPk'];
|
||||
|
||||
if (options && options['filterByTk']) {
|
||||
if (typeof options === 'object' && options['filterByTk']) {
|
||||
options = options['filterByTk'];
|
||||
}
|
||||
|
||||
const targetInstances = (<any>this.association).toInstanceArray(options);
|
||||
|
||||
where.push({
|
||||
[this.target.primaryKeyAttribute]: targetInstances.map((targetInstance) =>
|
||||
targetInstance.get(this.target.primaryKeyAttribute),
|
||||
),
|
||||
[this.targetKey()]: options,
|
||||
});
|
||||
}
|
||||
|
||||
await this.target.destroy({
|
||||
await this.targetModel.destroy({
|
||||
where: {
|
||||
[Op.and]: where,
|
||||
},
|
||||
@ -95,7 +93,7 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async set(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void> {
|
||||
async set(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
@ -111,7 +109,7 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async add(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void> {
|
||||
async add(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { RelationRepository, transaction } from './relation-repository';
|
||||
import { omit } from 'lodash';
|
||||
import lodash, { omit } from 'lodash';
|
||||
import { MultiAssociationAccessors, Op, Sequelize, Transaction } from 'sequelize';
|
||||
import { UpdateGuard } from '../update-guard';
|
||||
import { updateModelByValues } from '../update-associations';
|
||||
@ -8,20 +8,20 @@ import {
|
||||
CountOptions,
|
||||
DestroyOptions,
|
||||
Filter,
|
||||
FilterByPK,
|
||||
FilterByTk,
|
||||
FindOptions,
|
||||
PK,
|
||||
PrimaryKey,
|
||||
TK,
|
||||
TargetKey,
|
||||
TransactionAble,
|
||||
UpdateOptions,
|
||||
} from '../repository';
|
||||
|
||||
export interface FindAndCountOptions extends CommonFindOptions {}
|
||||
|
||||
export interface FindOneOptions extends CommonFindOptions, FilterByPK {}
|
||||
export interface FindOneOptions extends CommonFindOptions, FilterByTk {}
|
||||
|
||||
export interface AssociatedOptions extends TransactionAble {
|
||||
pk?: PK;
|
||||
tk?: TK;
|
||||
}
|
||||
|
||||
export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
@ -46,16 +46,16 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
await sourceModel[getAccessor]({
|
||||
...findOptions,
|
||||
includeIgnoreAttributes: false,
|
||||
attributes: [this.target.primaryKeyAttribute],
|
||||
group: `${this.target.name}.${this.target.primaryKeyAttribute}`,
|
||||
attributes: [this.targetKey()],
|
||||
group: `${this.targetModel.name}.${this.targetKey()}`,
|
||||
transaction,
|
||||
})
|
||||
).map((row) => row.get(this.target.primaryKeyAttribute));
|
||||
).map((row) => row.get(this.targetKey()));
|
||||
|
||||
return await sourceModel[getAccessor]({
|
||||
...omit(findOptions, ['limit', 'offset']),
|
||||
where: {
|
||||
[this.target.primaryKeyAttribute]: {
|
||||
[this.targetKey()]: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
@ -97,7 +97,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
[
|
||||
Sequelize.fn(
|
||||
'COUNT',
|
||||
Sequelize.fn('DISTINCT', Sequelize.col(`${this.target.name}.${this.target.primaryKeyAttribute}`)),
|
||||
Sequelize.fn('DISTINCT', Sequelize.col(`${this.targetModel.name}.${this.targetKey()}`)),
|
||||
),
|
||||
'count',
|
||||
],
|
||||
@ -122,7 +122,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async remove(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void> {
|
||||
async remove(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
let handleKeys = options['pk'];
|
||||
|
||||
@ -141,7 +141,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
async update(options?: UpdateOptions): Promise<any> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
|
||||
const guard = UpdateGuard.fromOptions(this.target, options);
|
||||
const guard = UpdateGuard.fromOptions(this.targetModel, options);
|
||||
|
||||
const values = guard.sanitize(options.values);
|
||||
|
||||
@ -153,7 +153,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
await updateModelByValues(instance, values, {
|
||||
...options,
|
||||
sanitized: true,
|
||||
sourceModel: this.sourceModel,
|
||||
sourceModel: this.sourceInstance,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
@ -161,7 +161,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
return instances;
|
||||
}
|
||||
|
||||
async destroy(options?: PK | DestroyOptions): Promise<Boolean> {
|
||||
async destroy(options?: TK | DestroyOptions): Promise<Boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -170,8 +170,9 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
filter: filter,
|
||||
transaction,
|
||||
});
|
||||
|
||||
return await this.destroy({
|
||||
filterByPk: instances.map((instance) => instance[this.target.primaryKeyAttribute]),
|
||||
filterByTk: instances.map((instance) => instance.get(this.targetCollection.filterTargetKey)),
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
@ -7,24 +7,36 @@ import { UpdateGuard } from '../update-guard';
|
||||
import { updateAssociations } from '../update-associations';
|
||||
import lodash from 'lodash';
|
||||
import { transactionWrapperBuilder } from '../transaction-decorator';
|
||||
import { Field, RelationField } from '@nocobase/database';
|
||||
|
||||
export const transaction = transactionWrapperBuilder(function () {
|
||||
return this.source.model.sequelize.transaction();
|
||||
return this.sourceCollection.model.sequelize.transaction();
|
||||
});
|
||||
|
||||
export abstract class RelationRepository {
|
||||
source: Collection;
|
||||
sourceCollection: Collection;
|
||||
association: Association;
|
||||
target: ModelCtor<any>;
|
||||
sourceId: string | number;
|
||||
sourceModel: Model;
|
||||
targetModel: ModelCtor<any>;
|
||||
targetCollection: Collection;
|
||||
associationName: string;
|
||||
associationField: RelationField;
|
||||
sourceKeyValue: string | number;
|
||||
sourceInstance: Model;
|
||||
|
||||
constructor(source: Collection, association: string, sourceId: string | number) {
|
||||
this.source = source;
|
||||
this.sourceId = sourceId;
|
||||
this.association = this.source.model.associations[association];
|
||||
constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number) {
|
||||
this.sourceCollection = sourceCollection;
|
||||
this.sourceKeyValue = sourceKeyValue;
|
||||
this.associationName = association;
|
||||
this.association = this.sourceCollection.model.associations[association];
|
||||
|
||||
this.target = this.association.target;
|
||||
this.associationField = this.sourceCollection.getField(association);
|
||||
|
||||
this.targetModel = this.association.target;
|
||||
this.targetCollection = this.sourceCollection.context.database.modelCollection.get(this.targetModel);
|
||||
}
|
||||
|
||||
targetKey() {
|
||||
return this.associationField.targetKey;
|
||||
}
|
||||
|
||||
protected accessors() {
|
||||
@ -34,7 +46,7 @@ export abstract class RelationRepository {
|
||||
async create(options?: CreateOptions): Promise<any> {
|
||||
const createAccessor = this.accessors().create;
|
||||
|
||||
const guard = UpdateGuard.fromOptions(this.target, options);
|
||||
const guard = UpdateGuard.fromOptions(this.targetModel, options);
|
||||
const values = options.values;
|
||||
|
||||
const sourceModel = await this.getSourceModel();
|
||||
@ -47,23 +59,30 @@ export abstract class RelationRepository {
|
||||
}
|
||||
|
||||
async getSourceModel(transaction?: any) {
|
||||
if (!this.sourceModel) {
|
||||
this.sourceModel = await this.source.model.findByPk(this.sourceId, {
|
||||
transaction,
|
||||
if (!this.sourceInstance) {
|
||||
this.sourceInstance = await this.sourceCollection.model.findOne({
|
||||
where: {
|
||||
[this.associationField.sourceKey]: this.sourceKeyValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return this.sourceModel;
|
||||
return this.sourceInstance;
|
||||
}
|
||||
|
||||
protected buildQueryOptions(options: FindOptions) {
|
||||
const parser = new OptionsParser(this.target, this.source.context.database, options);
|
||||
const parser = new OptionsParser(options, {
|
||||
collection: this.targetCollection,
|
||||
targetKey: this.targetKey(),
|
||||
});
|
||||
const params = parser.toSequelizeParams();
|
||||
return { ...options, ...params };
|
||||
}
|
||||
|
||||
protected parseFilter(filter: Filter) {
|
||||
const parser = new FilterParser(this.target, this.source.context.database, filter);
|
||||
const parser = new FilterParser(filter, {
|
||||
collection: this.targetCollection,
|
||||
});
|
||||
return parser.toSequelizeParams();
|
||||
}
|
||||
|
||||
@ -73,7 +92,7 @@ export abstract class RelationRepository {
|
||||
}
|
||||
|
||||
if (autoGen) {
|
||||
return await this.source.model.sequelize.transaction();
|
||||
return await this.sourceCollection.model.sequelize.transaction();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -2,7 +2,7 @@ import { RelationRepository, transaction } from './relation-repository';
|
||||
import { Model, SingleAssociationAccessors } from 'sequelize';
|
||||
import { updateModelByValues } from '../update-associations';
|
||||
import lodash from 'lodash';
|
||||
import { Appends, Except, Fields, Filter, PrimaryKey, TransactionAble, UpdateOptions } from '../repository';
|
||||
import { Appends, Except, Fields, Filter, TargetKey, TransactionAble, UpdateOptions } from '../repository';
|
||||
|
||||
export interface SingleRelationFindOption extends TransactionAble {
|
||||
fields?: Fields;
|
||||
@ -12,7 +12,7 @@ export interface SingleRelationFindOption extends TransactionAble {
|
||||
}
|
||||
|
||||
interface SetOption extends TransactionAble {
|
||||
pk?: PrimaryKey;
|
||||
tk?: TargetKey;
|
||||
}
|
||||
|
||||
export abstract class SingleRelationRepository extends RelationRepository {
|
||||
@ -27,13 +27,13 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
return {
|
||||
pk: args[0],
|
||||
tk: args[0],
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async set(options: PrimaryKey | SetOption): Promise<void> {
|
||||
async set(options: TargetKey | SetOption): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
let handleKey = lodash.isPlainObject(options) ? (<SetOption>options).pk : options;
|
||||
let handleKey = lodash.isPlainObject(options) ? (<SetOption>options).tk : options;
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
|
@ -43,8 +43,8 @@ export interface FilterAble {
|
||||
filter: Filter;
|
||||
}
|
||||
|
||||
export type PrimaryKey = string | number;
|
||||
export type PK = PrimaryKey | PrimaryKey[];
|
||||
export type TargetKey = string | number;
|
||||
export type TK = TargetKey | TargetKey[];
|
||||
|
||||
export type Filter = any;
|
||||
export type Appends = string[];
|
||||
@ -63,11 +63,11 @@ export interface CountOptions extends Omit<SequelizeCreateOptions, 'distinct' |
|
||||
filter?: Filter;
|
||||
}
|
||||
|
||||
export interface FilterByPK {
|
||||
filterByPk?: PrimaryKey;
|
||||
export interface FilterByTk {
|
||||
filterByTk?: TargetKey;
|
||||
}
|
||||
|
||||
export interface FindOptions extends SequelizeFindOptions, CommonFindOptions, FilterByPK {}
|
||||
export interface FindOptions extends SequelizeFindOptions, CommonFindOptions, FilterByTk {}
|
||||
|
||||
export interface CommonFindOptions {
|
||||
filter?: Filter;
|
||||
@ -81,7 +81,7 @@ interface FindOneOptions extends FindOptions, CommonFindOptions {}
|
||||
|
||||
export interface DestroyOptions extends SequelizeDestroyOptions {
|
||||
filter?: Filter;
|
||||
filterByPk?: PrimaryKey | PrimaryKey[];
|
||||
filterByTk?: TargetKey | TargetKey[];
|
||||
truncate?: boolean;
|
||||
context?: any;
|
||||
}
|
||||
@ -110,7 +110,7 @@ export interface CreateOptions extends SequelizeCreateOptions {
|
||||
export interface UpdateOptions extends Omit<SequelizeUpdateOptions, 'where'> {
|
||||
values: Values;
|
||||
filter?: Filter;
|
||||
filterByPk?: PrimaryKey;
|
||||
filterByTk?: TargetKey;
|
||||
whitelist?: WhiteList;
|
||||
blacklist?: BlackList;
|
||||
updateAssociationValues?: AssociationKeysToBeUpdate;
|
||||
@ -261,7 +261,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
* Find By Id
|
||||
*
|
||||
*/
|
||||
findById(id: PrimaryKey) {
|
||||
findById(id: string | number) {
|
||||
return this.collection.model.findByPk(id);
|
||||
}
|
||||
|
||||
@ -362,26 +362,28 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
return {
|
||||
filterByPk: args[0],
|
||||
filterByTk: args[0],
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async destroy(options?: PrimaryKey | PrimaryKey[] | DestroyOptions) {
|
||||
async destroy(options?: TargetKey | TargetKey[] | DestroyOptions) {
|
||||
const transaction = await this.getTransaction(options);
|
||||
|
||||
const modelFilterKey = this.collection.filterTargetKey;
|
||||
|
||||
options = <DestroyOptions>options;
|
||||
|
||||
const filterByPk: PrimaryKey[] | undefined =
|
||||
options.filterByPk && !lodash.isArray(options.filterByPk)
|
||||
? [options.filterByPk]
|
||||
: (options.filterByPk as PrimaryKey[] | undefined);
|
||||
const filterByTk: TargetKey[] | undefined =
|
||||
options.filterByTk && !lodash.isArray(options.filterByTk)
|
||||
? [options.filterByTk]
|
||||
: (options.filterByTk as TargetKey[] | undefined);
|
||||
|
||||
if (filterByPk && !options.filter) {
|
||||
if (filterByTk && !options.filter) {
|
||||
return await this.model.destroy({
|
||||
...options,
|
||||
where: {
|
||||
[this.model.primaryKeyAttribute]: {
|
||||
[Op.in]: filterByPk,
|
||||
[modelFilterKey]: {
|
||||
[Op.in]: filterByTk,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
@ -394,15 +396,15 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
filter: options.filter,
|
||||
transaction,
|
||||
})
|
||||
).map((instance) => instance[this.model.primaryKeyAttribute]);
|
||||
).map((instance) => instance.get(modelFilterKey) as TargetKey);
|
||||
|
||||
if (filterByPk) {
|
||||
pks = lodash.intersection(pks, filterByPk);
|
||||
if (filterByTk) {
|
||||
pks = lodash.intersection(pks, filterByTk);
|
||||
}
|
||||
|
||||
return await this.destroy({
|
||||
context: options.context,
|
||||
filterByPk: pks,
|
||||
filterByTk: pks,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
@ -424,14 +426,19 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
}
|
||||
|
||||
protected buildQueryOptions(options: any) {
|
||||
const parser = new OptionsParser(this.collection.model, this.collection.context.database, options);
|
||||
const parser = new OptionsParser(options, {
|
||||
collection: this.collection,
|
||||
});
|
||||
|
||||
const params = parser.toSequelizeParams();
|
||||
debug('sequelize query params %o', params);
|
||||
return { ...options, ...params };
|
||||
}
|
||||
|
||||
protected parseFilter(filter: Filter) {
|
||||
const parser = new FilterParser(this.collection.model, this.collection.context.database, filter);
|
||||
const parser = new FilterParser(filter, {
|
||||
collection: this.collection,
|
||||
});
|
||||
return parser.toSequelizeParams();
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ type UpdateValue = { [key: string]: any };
|
||||
|
||||
interface UpdateOptions extends TransactionAble {
|
||||
filter?: any;
|
||||
filterByPk?: number | string;
|
||||
filterByTk?: number | string;
|
||||
// 字段白名单
|
||||
whitelist?: string[];
|
||||
// 字段黑名单
|
||||
|
Loading…
Reference in New Issue
Block a user