tachybase_todo/packages/database/src/relation-repository/belongs-to-many-repository.ts
ChengLei Shao 246737906d
Feat/GitHub actions (#148)
* test github actions

* mod: github actions

* mod: github actions

* fix: yarn build

* fix: database pk key error

* fix: test

* skip failed tests

* github test with sqlite && mysql

* fix: mysql query error
2022-01-08 17:16:11 +08:00

239 lines
6.8 KiB
TypeScript

import { BelongsToMany, Model, Op, Transaction } from 'sequelize';
import { updateThroughTableValue } from '../update-associations';
import { FindAndCountOptions, FindOneOptions, MultipleRelationRepository } from './multiple-relation-repository';
import { CreateOptions, DestroyOptions, FindOptions, TargetKey, UpdateOptions } from '../repository';
import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
import lodash from 'lodash';
import { transaction } from './relation-repository';
type CreateBelongsToManyOptions = CreateOptions;
interface IBelongsToManyRepository<M extends Model> {
find(options?: FindOptions): Promise<M[]>;
findAndCount(options?: FindAndCountOptions): Promise<[M[], number]>;
findOne(options?: FindOneOptions): Promise<M>;
// 新增并关联,存在中间表数据
create(options?: CreateBelongsToManyOptions): Promise<M>;
// 更新,存在中间表数据
update(options?: UpdateOptions): Promise<M>;
// 删除
destroy(options?: number | string | number[] | string[] | DestroyOptions): Promise<Boolean>;
// 建立关联
set(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
// 附加关联,存在中间表数据
add(options: TargetKey | TargetKey[] | AssociatedOptions): 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> {
@transaction()
async create(options?: CreateBelongsToManyOptions): Promise<any> {
const transaction = await this.getTransaction(options);
const createAccessor = this.accessors().create;
const values = options.values;
const sourceModel = await this.getSourceModel(transaction);
const createOptions = {
...options,
through: values[this.throughName()],
transaction,
};
return sourceModel[createAccessor](values, createOptions);
}
@transaction((args, transaction) => {
return {
filterByTk: args[0],
transaction,
};
})
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.targetKey()));
};
// Through Table
const throughTableWhere: Array<any> = [
{
[association.foreignKey]: this.sourceKeyValue,
},
];
let ids;
if (options && options['filter']) {
const instances = await this.find({
filter: options['filter'],
transaction,
});
ids = instancesToIds(instances);
}
if (options && options['filterByTk']) {
const instances = (<any>this.association).toInstanceArray(options['filterByTk']);
ids = ids ? lodash.intersection(ids, instancesToIds(instances)) : instancesToIds(instances);
}
if (options && !options['filterByTk'] && !options['filter']) {
const sourceModel = await this.getSourceModel(transaction);
const instances = await sourceModel[this.accessors().get]({
transaction,
});
ids = instancesToIds(instances);
}
throughTableWhere.push({
[association.otherKey]: {
[Op.in]: ids,
},
});
// delete through table data
await this.throughModel().destroy({
where: throughTableWhere,
transaction,
});
await this.targetModel.destroy({
where: {
[this.targetKey()]: {
[Op.in]: ids,
},
},
transaction,
});
return true;
}
protected async setTargets(
call: 'add' | 'set',
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
) {
let handleKeys: TargetKey[] | PrimaryKeyWithThroughValues[];
const transaction = await this.getTransaction(options, false);
if (lodash.isPlainObject(options)) {
options = (<AssociatedOptions>options).tk || [];
}
if (lodash.isString(options) || lodash.isNumber(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 = <TargetKey[] | PrimaryKeyWithThroughValues[]>options;
}
const sourceModel = await this.getSourceModel(transaction);
const setObj = (<any>handleKeys).reduce((carry, item) => {
if (Array.isArray(item)) {
carry[item[0]] = item[1];
} else {
carry[item] = true;
}
return carry;
}, {});
await sourceModel[this.accessors()[call]](Object.keys(setObj), {
transaction,
});
for (const [id, throughValues] of Object.entries(setObj)) {
if (typeof throughValues === 'object') {
const instance = await this.targetModel.findByPk(id, {
transaction,
});
await updateThroughTableValue(instance, this.throughName(), throughValues, sourceModel, transaction);
}
}
}
@transaction((args, transaction) => {
return {
tk: args[0],
transaction,
};
})
async add(
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
): Promise<void> {
await this.setTargets('add', options);
}
@transaction((args, transaction) => {
return {
tk: args[0],
transaction,
};
})
async set(
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
): Promise<void> {
await this.setTargets('set', options);
}
@transaction((args, transaction) => {
return {
tk: args[0],
transaction,
};
})
async toggle(options: TargetKey | { tk?: 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['tk'], {
transaction,
});
if (has) {
await this.remove({
...(<any>options),
transaction,
});
} else {
await this.add({
...(<any>options),
transaction,
});
}
return;
}
extendFindOptions(findOptions) {
let joinTableAttributes;
if (lodash.get(findOptions, 'fields')) {
joinTableAttributes = [];
}
return {
...findOptions,
joinTableAttributes,
};
}
throughName() {
return this.throughModel().name;
}
throughModel() {
return (<any>this.association).through.model;
}
}