mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
Merge branch 'main' into next
This commit is contained in:
commit
289d476ad1
@ -248,41 +248,66 @@ describe('belongs to many with target key', function () {
|
||||
expect(count).toEqual(0);
|
||||
});
|
||||
|
||||
test('destroy with target key and filter', async () => {
|
||||
const t1 = await Tag.repository.create({
|
||||
values: {
|
||||
name: 't1',
|
||||
status: 'published',
|
||||
},
|
||||
});
|
||||
|
||||
const t2 = await Tag.repository.create({
|
||||
values: {
|
||||
name: 't2',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
test('firstOrCreate', async () => {
|
||||
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',
|
||||
// 测试基本创建
|
||||
const tag1 = await PostTagRepository.firstOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 't1',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
[_, count] = await PostTagRepository.findAndCount();
|
||||
expect(count).toEqual(2);
|
||||
expect(tag1.name).toEqual('t1');
|
||||
expect(tag1.status).toEqual('active');
|
||||
|
||||
// 测试查找已存在记录
|
||||
const tag2 = await PostTagRepository.firstOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 't1',
|
||||
status: 'inactive',
|
||||
},
|
||||
});
|
||||
|
||||
expect(tag2.id).toEqual(tag1.id);
|
||||
expect(tag2.status).toEqual('active');
|
||||
});
|
||||
|
||||
test('updateOrCreate', async () => {
|
||||
const p1 = await Post.repository.create({
|
||||
values: { title: 'p1' },
|
||||
});
|
||||
|
||||
const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.get('title') as string);
|
||||
|
||||
const tag1 = await PostTagRepository.updateOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 't1',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
expect(tag1.name).toEqual('t1');
|
||||
expect(tag1.status).toEqual('active');
|
||||
|
||||
const tag2 = await PostTagRepository.updateOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 't1',
|
||||
status: 'inactive',
|
||||
},
|
||||
});
|
||||
|
||||
expect(tag2.id).toEqual(tag1.id);
|
||||
expect(tag2.status).toEqual('inactive');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,138 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import Database from '../../database';
|
||||
import { BelongsToRepository } from '../../relation-repository/belongs-to-repository';
|
||||
import { mockDatabase } from '../index';
|
||||
|
||||
describe('belongs to repository', () => {
|
||||
let db: Database;
|
||||
let User: Collection;
|
||||
let Post: Collection;
|
||||
|
||||
beforeEach(async () => {
|
||||
db = mockDatabase();
|
||||
await db.clean({ drop: true });
|
||||
|
||||
User = db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{ type: 'string', name: 'name' },
|
||||
{ type: 'string', name: 'status' },
|
||||
{ type: 'hasMany', name: 'posts' },
|
||||
],
|
||||
});
|
||||
|
||||
Post = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{ type: 'string', name: 'title' },
|
||||
{ type: 'belongsTo', name: 'user' },
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('firstOrCreate', async () => {
|
||||
const p1 = await Post.repository.create({
|
||||
values: { title: 'p1' },
|
||||
});
|
||||
|
||||
const PostUserRepository = new BelongsToRepository(Post, 'user', p1.id);
|
||||
|
||||
// 测试基本创建
|
||||
const user1 = await PostUserRepository.firstOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 'u1',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
expect(user1.name).toEqual('u1');
|
||||
expect(user1.status).toEqual('active');
|
||||
|
||||
// 验证关联是否建立
|
||||
await p1.reload();
|
||||
expect(p1.userId).toEqual(user1.id);
|
||||
|
||||
// 测试查找已存在记录
|
||||
const user2 = await PostUserRepository.firstOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 'u1',
|
||||
status: 'inactive',
|
||||
},
|
||||
});
|
||||
|
||||
expect(user2.id).toEqual(user1.id);
|
||||
expect(user2.status).toEqual('active');
|
||||
|
||||
// 测试多个 filterKeys
|
||||
const user3 = await PostUserRepository.firstOrCreate({
|
||||
filterKeys: ['name', 'status'],
|
||||
values: {
|
||||
name: 'u1',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
expect(user3.id).not.toEqual(user1.id);
|
||||
expect(user3.status).toEqual('draft');
|
||||
});
|
||||
|
||||
test('updateOrCreate', async () => {
|
||||
const p1 = await Post.repository.create({
|
||||
values: { title: 'p1' },
|
||||
});
|
||||
|
||||
const PostUserRepository = new BelongsToRepository(Post, 'user', p1.id);
|
||||
|
||||
// 测试基本创建
|
||||
const user1 = await PostUserRepository.updateOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 'u1',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
expect(user1.name).toEqual('u1');
|
||||
expect(user1.status).toEqual('active');
|
||||
|
||||
// 验证关联是否建立
|
||||
await p1.reload();
|
||||
expect(p1.userId).toEqual(user1.id);
|
||||
|
||||
// 测试更新已存在记录
|
||||
const user2 = await PostUserRepository.updateOrCreate({
|
||||
filterKeys: ['name'],
|
||||
values: {
|
||||
name: 'u1',
|
||||
status: 'inactive',
|
||||
},
|
||||
});
|
||||
|
||||
expect(user2.id).toEqual(user1.id);
|
||||
expect(user2.status).toEqual('inactive');
|
||||
|
||||
// 测试多个 filterKeys 的创建
|
||||
const user3 = await PostUserRepository.updateOrCreate({
|
||||
filterKeys: ['name', 'status'],
|
||||
values: {
|
||||
name: 'u1',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
expect(user3.id).not.toEqual(user1.id);
|
||||
expect(user3.status).toEqual('draft');
|
||||
|
||||
// 验证关联是否更新
|
||||
await p1.reload();
|
||||
expect(p1.userId).toEqual(user3.id);
|
||||
});
|
||||
});
|
@ -149,6 +149,7 @@ describe('has many repository', () => {
|
||||
fields: [
|
||||
{ type: 'string', name: 'name' },
|
||||
{ type: 'hasMany', name: 'posts' },
|
||||
{ type: 'string', name: 'status' },
|
||||
],
|
||||
});
|
||||
|
||||
@ -260,6 +261,118 @@ describe('has many repository', () => {
|
||||
expect(p1.title).toEqual('u1t1');
|
||||
});
|
||||
|
||||
test('firstOrCreate', async () => {
|
||||
const u1 = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
const UserPostRepository = new HasManyRepository(User, 'posts', u1.id);
|
||||
|
||||
// 测试基本创建
|
||||
const post1 = await UserPostRepository.firstOrCreate({
|
||||
filterKeys: ['title'],
|
||||
values: {
|
||||
title: 't1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(post1.title).toEqual('t1');
|
||||
expect(post1.userId).toEqual(u1.id);
|
||||
|
||||
// 测试查找已存在记录
|
||||
const post2 = await UserPostRepository.firstOrCreate({
|
||||
filterKeys: ['title'],
|
||||
values: {
|
||||
title: 't1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(post2.id).toEqual(post1.id);
|
||||
|
||||
// 测试带关联数据的创建
|
||||
const post3 = await UserPostRepository.firstOrCreate({
|
||||
filterKeys: ['title'],
|
||||
values: {
|
||||
title: 't2',
|
||||
comments: [{ content: 'comment1' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(post3.title).toEqual('t2');
|
||||
expect(await post3.countComments()).toEqual(1);
|
||||
|
||||
// 测试多个 filterKeys
|
||||
const post4 = await UserPostRepository.firstOrCreate({
|
||||
filterKeys: ['title', 'status'],
|
||||
values: {
|
||||
title: 't2',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
expect(post4.id).not.toEqual(post3.id);
|
||||
});
|
||||
|
||||
test('updateOrCreate', async () => {
|
||||
const u1 = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
const UserPostRepository = new HasManyRepository(User, 'posts', u1.id);
|
||||
|
||||
// 测试基本创建
|
||||
const post1 = await UserPostRepository.updateOrCreate({
|
||||
filterKeys: ['title'],
|
||||
values: {
|
||||
title: 't1',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
expect(post1.title).toEqual('t1');
|
||||
expect(post1.status).toEqual('draft');
|
||||
expect(post1.userId).toEqual(u1.id);
|
||||
|
||||
// 测试更新已存在记录
|
||||
const post2 = await UserPostRepository.updateOrCreate({
|
||||
filterKeys: ['title'],
|
||||
values: {
|
||||
title: 't1',
|
||||
status: 'published',
|
||||
},
|
||||
});
|
||||
|
||||
expect(post2.id).toEqual(post1.id);
|
||||
expect(post2.status).toEqual('published');
|
||||
|
||||
// 测试带关联数据的更新
|
||||
const post3 = await UserPostRepository.updateOrCreate({
|
||||
filterKeys: ['title'],
|
||||
values: {
|
||||
title: 't1',
|
||||
status: 'archived',
|
||||
comments: [{ content: 'new comment' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(post3.id).toEqual(post1.id);
|
||||
expect(post3.status).toEqual('archived');
|
||||
expect(await post3.countComments()).toEqual(1);
|
||||
|
||||
// 测试多个 filterKeys 的创建
|
||||
const post4 = await UserPostRepository.updateOrCreate({
|
||||
filterKeys: ['title', 'status'],
|
||||
values: {
|
||||
title: 't1',
|
||||
status: 'draft',
|
||||
comments: [{ content: 'another comment' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(post4.id).not.toEqual(post1.id);
|
||||
expect(await post4.countComments()).toEqual(1);
|
||||
});
|
||||
|
||||
test('find with has many', async () => {
|
||||
const u1 = await User.repository.create({ values: { name: 'u1' } });
|
||||
|
||||
|
@ -0,0 +1,128 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import Database from '../../database';
|
||||
import { HasOneRepository } from '../../relation-repository/hasone-repository';
|
||||
import { mockDatabase } from '../index';
|
||||
|
||||
describe('has one repository', () => {
|
||||
let db: Database;
|
||||
let User: Collection;
|
||||
let Profile: Collection;
|
||||
|
||||
beforeEach(async () => {
|
||||
db = mockDatabase();
|
||||
await db.clean({ drop: true });
|
||||
|
||||
User = db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{ type: 'string', name: 'name' },
|
||||
{ type: 'hasOne', name: 'profile' },
|
||||
],
|
||||
});
|
||||
|
||||
Profile = db.collection({
|
||||
name: 'profiles',
|
||||
fields: [
|
||||
{ type: 'string', name: 'avatar' },
|
||||
{ type: 'string', name: 'status' },
|
||||
{ type: 'belongsTo', name: 'user' },
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('firstOrCreate', async () => {
|
||||
const u1 = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
const UserProfileRepository = new HasOneRepository(User, 'profile', u1.id);
|
||||
|
||||
// 测试基本创建
|
||||
const profile1 = await UserProfileRepository.firstOrCreate({
|
||||
filterKeys: ['avatar'],
|
||||
values: {
|
||||
avatar: 'avatar1.jpg',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
expect(profile1.avatar).toEqual('avatar1.jpg');
|
||||
expect(profile1.status).toEqual('active');
|
||||
expect(profile1.userId).toEqual(u1.id);
|
||||
|
||||
// 测试查找已存在记录
|
||||
const profile2 = await UserProfileRepository.firstOrCreate({
|
||||
filterKeys: ['avatar'],
|
||||
values: {
|
||||
avatar: 'avatar1.jpg',
|
||||
status: 'inactive',
|
||||
},
|
||||
});
|
||||
|
||||
expect(profile2.id).toEqual(profile1.id);
|
||||
expect(profile2.status).toEqual('active');
|
||||
|
||||
// 测试多个 filterKeys
|
||||
const profile3 = await UserProfileRepository.firstOrCreate({
|
||||
filterKeys: ['avatar', 'status'],
|
||||
values: {
|
||||
avatar: 'avatar1.jpg',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
expect(profile3.id).not.toEqual(profile1.id);
|
||||
expect(profile3.status).toEqual('draft');
|
||||
});
|
||||
|
||||
test('updateOrCreate', async () => {
|
||||
const u1 = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
const UserProfileRepository = new HasOneRepository(User, 'profile', u1.id);
|
||||
|
||||
// 测试基本创建
|
||||
const profile1 = await UserProfileRepository.updateOrCreate({
|
||||
filterKeys: ['avatar'],
|
||||
values: {
|
||||
avatar: 'avatar1.jpg',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
expect(profile1.avatar).toEqual('avatar1.jpg');
|
||||
expect(profile1.status).toEqual('active');
|
||||
expect(profile1.userId).toEqual(u1.id);
|
||||
|
||||
// 测试更新已存在记录
|
||||
const profile2 = await UserProfileRepository.updateOrCreate({
|
||||
filterKeys: ['avatar'],
|
||||
values: {
|
||||
avatar: 'avatar1.jpg',
|
||||
status: 'inactive',
|
||||
},
|
||||
});
|
||||
|
||||
expect(profile2.id).toEqual(profile1.id);
|
||||
expect(profile2.status).toEqual('inactive');
|
||||
|
||||
// 测试多个 filterKeys 的创建
|
||||
const profile3 = await UserProfileRepository.updateOrCreate({
|
||||
filterKeys: ['avatar', 'status'],
|
||||
values: {
|
||||
avatar: 'avatar1.jpg',
|
||||
status: 'draft',
|
||||
},
|
||||
});
|
||||
|
||||
expect(profile3.id).not.toEqual(profile1.id);
|
||||
expect(profile3.status).toEqual('draft');
|
||||
});
|
||||
});
|
@ -13,6 +13,7 @@ import { AggregateOptions, CreateOptions, DestroyOptions, TargetKey } from '../r
|
||||
import { updateAssociations, updateThroughTableValue } from '../update-associations';
|
||||
import { MultipleRelationRepository } from './multiple-relation-repository';
|
||||
import { transaction } from './relation-repository';
|
||||
|
||||
import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
|
||||
|
||||
type CreateBelongsToManyOptions = CreateOptions;
|
||||
|
@ -8,9 +8,7 @@
|
||||
*/
|
||||
|
||||
import { BelongsTo } from 'sequelize';
|
||||
import { SingleRelationFindOption, SingleRelationRepository } from './single-relation-repository';
|
||||
|
||||
type BelongsToFindOptions = SingleRelationFindOption;
|
||||
import { SingleRelationRepository } from './single-relation-repository';
|
||||
|
||||
export class BelongsToRepository extends SingleRelationRepository {
|
||||
/**
|
||||
|
@ -10,8 +10,9 @@
|
||||
import { omit } from 'lodash';
|
||||
import { HasMany, Op } from 'sequelize';
|
||||
import { AggregateOptions, DestroyOptions, FindOptions, TargetKey, TK } from '../repository';
|
||||
import { AssociatedOptions, MultipleRelationRepository } from './multiple-relation-repository';
|
||||
import { MultipleRelationRepository } from './multiple-relation-repository';
|
||||
import { transaction } from './relation-repository';
|
||||
import { AssociatedOptions } from './types';
|
||||
|
||||
export class HasManyRepository extends MultipleRelationRepository {
|
||||
async find(options?: FindOptions): Promise<any> {
|
||||
|
@ -8,28 +8,22 @@
|
||||
*/
|
||||
|
||||
import lodash from 'lodash';
|
||||
import { HasOne, MultiAssociationAccessors, Sequelize, Transaction, Transactionable } from 'sequelize';
|
||||
import { HasOne, MultiAssociationAccessors, Sequelize, Transaction } from 'sequelize';
|
||||
import injectTargetCollection from '../decorators/target-collection-decorator';
|
||||
import {
|
||||
CommonFindOptions,
|
||||
CountOptions,
|
||||
DestroyOptions,
|
||||
Filter,
|
||||
FindOneOptions,
|
||||
FindOptions,
|
||||
TargetKey,
|
||||
TK,
|
||||
UpdateOptions,
|
||||
} from '../repository';
|
||||
import { updateModelByValues } from '../update-associations';
|
||||
import { UpdateGuard } from '../update-guard';
|
||||
import { RelationRepository, transaction } from './relation-repository';
|
||||
|
||||
type FindAndCountOptions = CommonFindOptions;
|
||||
|
||||
export interface AssociatedOptions extends Transactionable {
|
||||
tk?: TK;
|
||||
}
|
||||
import {
|
||||
AssociatedOptions,
|
||||
CountOptions,
|
||||
DestroyOptions,
|
||||
Filter,
|
||||
FindOptions,
|
||||
TargetKey,
|
||||
UpdateOptions,
|
||||
FirstOrCreateOptions,
|
||||
} from './types';
|
||||
import { valuesToFilter } from '../utils/filter-utils';
|
||||
|
||||
export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
async targetRepositoryFilterOptionsBySourceValue(): Promise<any> {
|
||||
@ -73,7 +67,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]> {
|
||||
async findAndCount(options?: FindOptions): Promise<[any[], number]> {
|
||||
const transaction = await this.getTransaction(options, false);
|
||||
|
||||
return [
|
||||
@ -121,7 +115,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
return parseInt(count.count);
|
||||
}
|
||||
|
||||
async findOne(options?: FindOneOptions): Promise<any> {
|
||||
async findOne(options?: FindOptions): Promise<any> {
|
||||
const transaction = await this.getTransaction(options, false);
|
||||
const rows = await this.find({ ...options, limit: 1, transaction });
|
||||
return rows.length == 1 ? rows[0] : null;
|
||||
@ -181,7 +175,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
return instances;
|
||||
}
|
||||
|
||||
async destroy(options?: TK | DestroyOptions): Promise<boolean> {
|
||||
async destroy(options?: TargetKey | DestroyOptions): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -205,4 +199,10 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
protected accessors() {
|
||||
return <MultiAssociationAccessors>super.accessors();
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async updateOrCreate(options: FirstOrCreateOptions) {
|
||||
const result = await super.updateOrCreate(options);
|
||||
return Array.isArray(result) ? result[0] : result;
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,10 @@ import { RelationField } from '../fields/relation-field';
|
||||
import FilterParser from '../filter-parser';
|
||||
import { Model } from '../model';
|
||||
import { OptionsParser } from '../options-parser';
|
||||
import { CreateOptions, Filter, FindOptions, TargetKey } from '../repository';
|
||||
import { updateAssociations } from '../update-associations';
|
||||
import { UpdateGuard } from '../update-guard';
|
||||
import { CreateOptions, Filter, FindOptions, TargetKey, UpdateOptions, FirstOrCreateOptions } from './types';
|
||||
import { valuesToFilter } from '../utils/filter-utils';
|
||||
|
||||
export const transaction = transactionWrapperBuilder(function () {
|
||||
return this.sourceCollection.model.sequelize.transaction();
|
||||
@ -76,6 +77,8 @@ export abstract class RelationRepository {
|
||||
}
|
||||
|
||||
abstract find(options?: FindOptions): Promise<any>;
|
||||
abstract findOne(options?: FindOptions): Promise<any>;
|
||||
abstract update(options: UpdateOptions): Promise<any>;
|
||||
|
||||
async chunk(
|
||||
options: FindOptions & { chunkSize: number; callback: (rows: Model[], options: FindOptions) => Promise<void> },
|
||||
@ -138,6 +141,41 @@ export abstract class RelationRepository {
|
||||
return this.associationField.targetKey;
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async firstOrCreate(options: FirstOrCreateOptions) {
|
||||
const { filterKeys, values, transaction, hooks } = options;
|
||||
const filter = valuesToFilter(values, filterKeys);
|
||||
|
||||
const instance = await this.findOne({ filter, transaction });
|
||||
|
||||
if (instance) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
return this.create({ values, transaction, hooks });
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async updateOrCreate(options: FirstOrCreateOptions) {
|
||||
const { filterKeys, values, transaction, hooks } = options;
|
||||
const filter = valuesToFilter(values, filterKeys);
|
||||
|
||||
const instance = await this.findOne({ filter, transaction });
|
||||
|
||||
if (instance) {
|
||||
return await this.update({
|
||||
filterByTk: instance.get(
|
||||
this.targetCollection.filterTargetKey || this.targetCollection.model.primaryKeyAttribute,
|
||||
),
|
||||
values,
|
||||
transaction,
|
||||
hooks,
|
||||
});
|
||||
}
|
||||
|
||||
return this.create({ values, transaction, hooks });
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async create(options?: CreateOptions): Promise<any> {
|
||||
if (Array.isArray(options.values)) {
|
||||
|
@ -7,22 +7,13 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import lodash from 'lodash';
|
||||
import { SingleAssociationAccessors, Transactionable } from 'sequelize';
|
||||
import injectTargetCollection from '../decorators/target-collection-decorator';
|
||||
import { Model } from '../model';
|
||||
import { Appends, Except, Fields, Filter, TargetKey, UpdateOptions } from '../repository';
|
||||
import { FindOptions, TargetKey, UpdateOptions } from './types';
|
||||
import { updateModelByValues } from '../update-associations';
|
||||
import { RelationRepository, transaction } from './relation-repository';
|
||||
|
||||
export interface SingleRelationFindOption extends Transactionable {
|
||||
fields?: Fields;
|
||||
except?: Except;
|
||||
appends?: Appends;
|
||||
filter?: Filter;
|
||||
targetCollection?: string;
|
||||
}
|
||||
|
||||
interface SetOption extends Transactionable {
|
||||
tk?: TargetKey;
|
||||
}
|
||||
@ -55,7 +46,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async find(options?: SingleRelationFindOption): Promise<any> {
|
||||
async find(options?: FindOptions): Promise<any> {
|
||||
const targetRepository = this.targetCollection.repository;
|
||||
|
||||
const sourceModel = await this.getSourceModel(await this.getTransaction(options));
|
||||
@ -74,7 +65,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
return await targetRepository.findOne(findOptions);
|
||||
}
|
||||
|
||||
async findOne(options?: SingleRelationFindOption): Promise<Model<any>> {
|
||||
async findOne(options?: FindOptions): Promise<Model<any>> {
|
||||
return this.find({ ...options, filterByTk: null } as any);
|
||||
}
|
||||
|
||||
@ -100,6 +91,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
|
||||
const target = await this.find({
|
||||
transaction,
|
||||
// @ts-ignore
|
||||
targetCollection: options.targetCollection,
|
||||
});
|
||||
|
||||
|
@ -7,18 +7,96 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { TargetKey, Values } from '../repository';
|
||||
import { Transactionable } from 'sequelize';
|
||||
import { Transaction } from 'sequelize';
|
||||
import {
|
||||
CreateOptions as SequelizeCreateOptions,
|
||||
UpdateOptions as SequelizeUpdateOptions,
|
||||
} from 'sequelize/types/model';
|
||||
import { AssociationKeysToBeUpdate, BlackList, Values, WhiteList } from '@nocobase/database';
|
||||
|
||||
export type PrimaryKeyWithThroughValues = [TargetKey, Values];
|
||||
export type TargetKey = string | number | { [key: string]: any };
|
||||
|
||||
export interface AssociatedOptions extends Transactionable {
|
||||
tk?: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[];
|
||||
export interface Filter {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type setAssociationOptions =
|
||||
| TargetKey
|
||||
| TargetKey[]
|
||||
| PrimaryKeyWithThroughValues
|
||||
| PrimaryKeyWithThroughValues[]
|
||||
| AssociatedOptions;
|
||||
export interface Appends {
|
||||
[key: string]: true | Appends;
|
||||
}
|
||||
|
||||
export interface Except {
|
||||
[key: string]: true | Except;
|
||||
}
|
||||
|
||||
export interface CommonOptions {
|
||||
transaction?: Transaction;
|
||||
context?: any;
|
||||
}
|
||||
|
||||
export interface FindOptions extends CommonOptions {
|
||||
filter?: Filter;
|
||||
filterByTk?: TargetKey;
|
||||
fields?: string[];
|
||||
appends?: string[];
|
||||
except?: string[];
|
||||
sort?: string[];
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
raw?: boolean;
|
||||
targetCollection?: string;
|
||||
}
|
||||
|
||||
export interface CountOptions extends CommonOptions {
|
||||
filter?: Filter;
|
||||
}
|
||||
|
||||
export interface CreateOptions extends SequelizeCreateOptions {
|
||||
values?: Values | Values[];
|
||||
whitelist?: WhiteList;
|
||||
blacklist?: BlackList;
|
||||
updateAssociationValues?: AssociationKeysToBeUpdate;
|
||||
context?: any;
|
||||
}
|
||||
|
||||
export interface UpdateOptions extends Omit<SequelizeUpdateOptions, 'where'> {
|
||||
values: Values;
|
||||
filter?: Filter;
|
||||
filterByTk?: TargetKey;
|
||||
whitelist?: WhiteList;
|
||||
blacklist?: BlackList;
|
||||
updateAssociationValues?: AssociationKeysToBeUpdate;
|
||||
targetCollection?: string;
|
||||
context?: any;
|
||||
}
|
||||
|
||||
export interface DestroyOptions extends CommonOptions {
|
||||
filter?: Filter;
|
||||
filterByTk?: TargetKey;
|
||||
truncate?: boolean;
|
||||
context?: any;
|
||||
}
|
||||
|
||||
export interface FirstOrCreateOptions extends CommonOptions {
|
||||
filterKeys: string[];
|
||||
values: any;
|
||||
hooks?: boolean;
|
||||
}
|
||||
|
||||
export interface ThroughValues {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface AssociatedOptions extends CommonOptions {
|
||||
tk?: TargetKey | TargetKey[];
|
||||
transaction?: Transaction;
|
||||
}
|
||||
|
||||
export interface PrimaryKeyWithThroughValues {
|
||||
pk: any;
|
||||
throughValues?: ThroughValues;
|
||||
}
|
||||
|
||||
export interface ToggleOptions extends CommonOptions {
|
||||
tk?: TargetKey;
|
||||
transaction?: Transaction;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import { RelationRepository } from './relation-repository/relation-repository';
|
||||
import { updateAssociations, updateModelByValues } from './update-associations';
|
||||
import { UpdateGuard } from './update-guard';
|
||||
import { BelongsToArrayRepository } from './relation-repository/belongs-to-array-repository';
|
||||
import { valuesToFilter } from './utils/filter-utils';
|
||||
|
||||
const debug = require('debug')('noco-database');
|
||||
|
||||
@ -234,7 +235,7 @@ export interface AggregateOptions {
|
||||
distinct?: boolean;
|
||||
}
|
||||
|
||||
interface FirstOrCreateOptions extends Transactionable {
|
||||
export interface FirstOrCreateOptions extends Transactionable {
|
||||
filterKeys: string[];
|
||||
values?: Values;
|
||||
hooks?: boolean;
|
||||
@ -251,53 +252,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
this.model = collection.model;
|
||||
}
|
||||
|
||||
public static valuesToFilter(values: Values = {}, filterKeys: Array<string>) {
|
||||
const removeArrayIndexInKey = (key) => {
|
||||
const chunks = key.split('.');
|
||||
return chunks
|
||||
.filter((chunk) => {
|
||||
return !chunk.match(/\d+/);
|
||||
})
|
||||
.join('.');
|
||||
};
|
||||
|
||||
const filterAnd = [];
|
||||
const flattedValues = flatten(values);
|
||||
const flattedValuesObject = {};
|
||||
|
||||
for (const key in flattedValues) {
|
||||
const keyWithoutArrayIndex = removeArrayIndexInKey(key);
|
||||
if (flattedValuesObject[keyWithoutArrayIndex]) {
|
||||
if (!Array.isArray(flattedValuesObject[keyWithoutArrayIndex])) {
|
||||
flattedValuesObject[keyWithoutArrayIndex] = [flattedValuesObject[keyWithoutArrayIndex]];
|
||||
}
|
||||
|
||||
flattedValuesObject[keyWithoutArrayIndex].push(flattedValues[key]);
|
||||
} else {
|
||||
flattedValuesObject[keyWithoutArrayIndex] = [flattedValues[key]];
|
||||
}
|
||||
}
|
||||
|
||||
for (const filterKey of filterKeys) {
|
||||
const filterValue = flattedValuesObject[filterKey]
|
||||
? flattedValuesObject[filterKey]
|
||||
: lodash.get(values, filterKey);
|
||||
|
||||
if (filterValue) {
|
||||
filterAnd.push({
|
||||
[filterKey]: filterValue,
|
||||
});
|
||||
} else {
|
||||
filterAnd.push({
|
||||
[filterKey]: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
$and: filterAnd,
|
||||
};
|
||||
}
|
||||
public static valuesToFilter = valuesToFilter;
|
||||
|
||||
/**
|
||||
* return count by filter
|
||||
|
59
packages/core/database/src/utils/filter-utils.ts
Normal file
59
packages/core/database/src/utils/filter-utils.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import lodash from 'lodash';
|
||||
import { flatten } from 'flat';
|
||||
|
||||
type Values = Record<string, any>;
|
||||
|
||||
export function valuesToFilter(values: Values = {}, filterKeys: Array<string>) {
|
||||
const removeArrayIndexInKey = (key) => {
|
||||
const chunks = key.split('.');
|
||||
return chunks
|
||||
.filter((chunk) => {
|
||||
return !chunk.match(/\d+/);
|
||||
})
|
||||
.join('.');
|
||||
};
|
||||
|
||||
const filterAnd = [];
|
||||
const flattedValues = flatten(values);
|
||||
const flattedValuesObject = {};
|
||||
|
||||
for (const key in flattedValues) {
|
||||
const keyWithoutArrayIndex = removeArrayIndexInKey(key);
|
||||
if (flattedValuesObject[keyWithoutArrayIndex]) {
|
||||
if (!Array.isArray(flattedValuesObject[keyWithoutArrayIndex])) {
|
||||
flattedValuesObject[keyWithoutArrayIndex] = [flattedValuesObject[keyWithoutArrayIndex]];
|
||||
}
|
||||
|
||||
flattedValuesObject[keyWithoutArrayIndex].push(flattedValues[key]);
|
||||
} else {
|
||||
flattedValuesObject[keyWithoutArrayIndex] = [flattedValues[key]];
|
||||
}
|
||||
}
|
||||
|
||||
for (const filterKey of filterKeys) {
|
||||
const filterValue = flattedValuesObject[filterKey] ? flattedValuesObject[filterKey] : lodash.get(values, filterKey);
|
||||
|
||||
if (filterValue) {
|
||||
filterAnd.push({
|
||||
[filterKey]: filterValue,
|
||||
});
|
||||
} else {
|
||||
filterAnd.push({
|
||||
[filterKey]: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
$and: filterAnd,
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user