mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-04 21:19:27 +08:00
* feat: environments plugin * feat: improve code * fix: improve code * feat: improve code * refactor: package description * feat: bulk import * fix: remove * refactor: file manager support environment variables * refactor: file manager support environment variables * refactor: map manager support environment variables * refactor: support environment variables * refactor: support environment variables * refactor: support delete environment variables * fix: bug * refactor: workflow support environment variables * refactor: email environment variables * refactor: support bulk import * refactor: support bulk import * refactor: support bulk import * refactor: support bulk import * refactor: code improve * feat: env * chore: update * feat: environment * fix: bug * fix: acl snippet * fix: acl snippets * chore: map manager * refactor: support line break * refactor: support password * chore: environment variables * fix: bug * fix: bug * chore: enviroment variables * chore: system settings * fix: improve code * feat: verification * feat: map * feat: file-manager * feat: notification * fix: bug * feat: workflow * fix: improve code * fix: bug * feat: data-source * feat: auth * fix: error * fix: bug * refactor: description * refactor: locale * refactor: locale * refactor: locale * refactor: code improve * refactor: locale * refactor: locale * style: style improve * fix: error * fix: bug * fix: bug * refactor: environment * fix: ellipsis * refactor: password * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * chore: test * fix: cache * fix: mysql dialect options * refactor: email config form * fix: bug * fix: bug * fix: authenticator.dataValues parse * fix: include undefined * fix: json * fix: json parse * chore: enviromentProvider * fix: acl * fix: rowKey * fix: update ProviderOptions.tsx * feat: get app instance * fix: bug * fix: text * fix: build error * fix: error * chore: migration rules options * chore: migration rules * refactor: code improve * feat: env v2 * chore: environment varibales * chore: environment serve * fix: getVariables * feat: improve code * fix: bug * chore: collection options for migration * chore: tree collection options * chore: migration rules * chore: migration rules * chore: env api * chore: env api * fix: optionsKeysNotAllowedInEnv * fix: required true * fix: improve code * fix: app refresh * fix: remove db.import * fix: type error * fix: map * refactor: locale improve * refactor: tx-cos * fix: undefined * refactor: code improve * chore: use bookworm * fix: npm add user * fix: npm login * fix: npm adduser * fix: npm adduser * fix: expect * fix: expect * fix: environmentVariables * refactor: support bulk delete & filter * refactor: locale improve * feat: filter * refactor: useGlobalVariable * fix: scope * fix: bug * fix: optionsKeysNotAllowedInEnv * fix: test error * fix: test * fix: test * feat: improve code --------- Co-authored-by: chenos <chenlinxh@gmail.com> Co-authored-by: Chareice <chareice@live.com>
247 lines
7.4 KiB
TypeScript
247 lines
7.4 KiB
TypeScript
/**
|
|
* 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 { omit } from 'lodash';
|
|
import { AssociationScope, BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize';
|
|
import { Collection } from '../collection';
|
|
import { Reference } from '../features/references-map';
|
|
import { checkIdentifier } from '../utils';
|
|
import { BelongsToField } from './belongs-to-field';
|
|
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
|
|
|
|
export class BelongsToManyField extends RelationField {
|
|
get dataType() {
|
|
return 'BelongsToMany';
|
|
}
|
|
|
|
get through() {
|
|
return (
|
|
this.options.through ||
|
|
Utils.camelize(
|
|
[this.context.collection.model.name, this.target]
|
|
.map((name) => name.toLowerCase())
|
|
.sort()
|
|
.join('_'),
|
|
)
|
|
);
|
|
}
|
|
|
|
get otherKey() {
|
|
return this.options.otherKey;
|
|
}
|
|
|
|
references(association): Reference[] {
|
|
const db = this.context.database;
|
|
|
|
const onDelete = this.options.onDelete || 'CASCADE';
|
|
|
|
const priority = this.options.onDelete ? 'user' : 'default';
|
|
|
|
const targetAssociation = association.toTarget;
|
|
|
|
if (association.targetKey) {
|
|
targetAssociation.targetKey = association.targetKey;
|
|
}
|
|
|
|
const sourceAssociation = association.toSource;
|
|
|
|
if (association.sourceKey) {
|
|
sourceAssociation.targetKey = association.sourceKey;
|
|
}
|
|
|
|
return [
|
|
BelongsToField.toReference(db, targetAssociation, onDelete, priority),
|
|
BelongsToField.toReference(db, sourceAssociation, onDelete, priority),
|
|
];
|
|
}
|
|
|
|
checkAssociationKeys(database) {
|
|
let { foreignKey, sourceKey, otherKey, targetKey } = this.options;
|
|
|
|
const through = this.through;
|
|
const throughCollection = database.getCollection(through);
|
|
|
|
if (!throughCollection) {
|
|
// skip check if through collection not found
|
|
return;
|
|
}
|
|
|
|
if (!sourceKey) {
|
|
sourceKey = this.collection.model.primaryKeyAttribute;
|
|
}
|
|
|
|
if (!foreignKey) {
|
|
foreignKey = Utils.camelize([Utils.singularize(this.collection.model.name), sourceKey].join('_'));
|
|
}
|
|
|
|
if (!targetKey) {
|
|
targetKey = this.TargetModel.primaryKeyAttribute;
|
|
}
|
|
|
|
if (!otherKey) {
|
|
otherKey = Utils.camelize([Utils.singularize(this.TargetModel.name), targetKey].join('_'));
|
|
}
|
|
|
|
const foreignKeyAttribute = throughCollection.model.rawAttributes[foreignKey];
|
|
const otherKeyAttribute = throughCollection.model.rawAttributes[otherKey];
|
|
const sourceKeyAttribute = this.collection.model.rawAttributes[sourceKey];
|
|
const targetKeyAttribute = this.TargetModel.rawAttributes[targetKey];
|
|
|
|
if (!foreignKeyAttribute || !otherKeyAttribute || !sourceKeyAttribute || !targetKeyAttribute) {
|
|
return;
|
|
}
|
|
|
|
const foreignKeyType = foreignKeyAttribute.type.constructor.toString();
|
|
const otherKeyType = otherKeyAttribute.type.constructor.toString();
|
|
const sourceKeyType = sourceKeyAttribute.type.constructor.toString();
|
|
const targetKeyType = targetKeyAttribute.type.constructor.toString();
|
|
|
|
if (!this.keyPairsTypeMatched(foreignKeyType, sourceKeyType)) {
|
|
throw new Error(
|
|
`Foreign key "${foreignKey}" type "${foreignKeyType}" does not match source key "${sourceKey}" type "${sourceKeyType}" in belongs to many relation "${this.name}" of collection "${this.collection.name}"`,
|
|
);
|
|
}
|
|
|
|
if (!this.keyPairsTypeMatched(otherKeyType, targetKeyType)) {
|
|
throw new Error(
|
|
`Other key "${otherKey}" type "${otherKeyType}" does not match target key "${targetKey}" type "${targetKeyType}" in belongs to many relation "${this.name}" of collection "${this.collection.name}"`,
|
|
);
|
|
}
|
|
}
|
|
|
|
bind() {
|
|
const { database, collection } = this.context;
|
|
|
|
const Target = this.TargetModel;
|
|
|
|
if (!Target) {
|
|
database.addPendingField(this);
|
|
return false;
|
|
}
|
|
|
|
if (!this.collection.model.primaryKeyAttribute) {
|
|
throw new Error(`Collection model ${this.collection.model.name} has no primary key attribute`);
|
|
}
|
|
|
|
if (!Target.primaryKeyAttribute) {
|
|
throw new Error(`Target model ${Target.name} has no primary key attribute`);
|
|
}
|
|
|
|
this.checkAssociationKeys(database);
|
|
|
|
const through = this.through;
|
|
|
|
let Through: Collection;
|
|
|
|
if (database.hasCollection(through)) {
|
|
Through = database.getCollection(through);
|
|
} else {
|
|
const throughCollectionOptions = {
|
|
name: through,
|
|
isThrough: true,
|
|
sourceCollectionName: this.collection.name,
|
|
targetCollectionName: this.target,
|
|
};
|
|
|
|
if (this.collection.options.dumpRules) {
|
|
throughCollectionOptions['dumpRules'] = this.collection.options.dumpRules;
|
|
}
|
|
|
|
// set through collection schema
|
|
if (this.collection.collectionSchema()) {
|
|
throughCollectionOptions['schema'] = this.collection.collectionSchema();
|
|
}
|
|
|
|
Through = database.collection(throughCollectionOptions);
|
|
|
|
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
|
}
|
|
|
|
const belongsToManyOptions = {
|
|
constraints: false,
|
|
...omit(this.options, ['name', 'type', 'target']),
|
|
as: this.name,
|
|
through: {
|
|
model: Through.model,
|
|
scope: this.options.throughScope,
|
|
paranoid: this.options.throughParanoid,
|
|
unique: this.options.throughUnique,
|
|
},
|
|
};
|
|
|
|
const association = collection.model.belongsToMany(Target, belongsToManyOptions);
|
|
|
|
// 建立关系之后从 pending 列表中删除
|
|
database.removePendingField(this);
|
|
|
|
if (!this.options.foreignKey) {
|
|
this.options.foreignKey = association.foreignKey;
|
|
}
|
|
|
|
if (!this.options.sourceKey) {
|
|
this.options.sourceKey = association.sourceKey;
|
|
}
|
|
|
|
if (!this.options.otherKey) {
|
|
this.options.otherKey = association.otherKey;
|
|
}
|
|
|
|
if (!this.options.targetKey) {
|
|
this.options.targetKey = association.targetKey;
|
|
}
|
|
|
|
try {
|
|
checkIdentifier(this.options.foreignKey);
|
|
checkIdentifier(this.options.otherKey);
|
|
} catch (error) {
|
|
this.unbind();
|
|
throw error;
|
|
}
|
|
|
|
if (!this.options.through) {
|
|
this.options.through = this.through;
|
|
}
|
|
|
|
Through.addIndex([this.options.foreignKey]);
|
|
Through.addIndex([this.options.otherKey]);
|
|
|
|
this.references(association).forEach((reference) => this.database.referenceMap.addReference(reference));
|
|
return true;
|
|
}
|
|
|
|
unbind() {
|
|
const { database, collection } = this.context;
|
|
const Through = database.getCollection(this.through);
|
|
|
|
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
|
database.removePendingField(this);
|
|
// 删掉 model 的关联字段
|
|
|
|
const association = collection.model.associations[this.name];
|
|
|
|
if (association && !this.options.inherit) {
|
|
this.references(association).forEach((reference) => this.database.referenceMap.removeReference(reference));
|
|
}
|
|
|
|
this.clearAccessors();
|
|
delete collection.model.associations[this.name];
|
|
}
|
|
}
|
|
|
|
export interface BelongsToManyFieldOptions
|
|
extends MultipleRelationFieldOptions,
|
|
Omit<SequelizeBelongsToManyOptions, 'through'> {
|
|
type: 'belongsToMany';
|
|
target?: string;
|
|
through?: string;
|
|
throughScope?: AssociationScope;
|
|
throughUnique?: boolean;
|
|
throughParanoid?: boolean;
|
|
}
|