chenos 7779cd79ac
refactor: optimize the command line (#3339)
* fix: perform load action on boot main app

* feat: add dataType option in collection duplicator

* chore: reset optional dumpable config

* chore: dump command

* chore: dump & restore command

* chore: delay restore

* fix: dump test

* chore: restore command

* chore: dump command action

* chore: dumpable collection api

* chore: client collection option

* feat: backup& restore client

* chore: content disposition header in dump response

* chore: download backup field

* feat: collection origin option

* fix: test

* chore: collection manager collection origin

* chore: upload  backup field

* chore: upload restore file

* chore: upload restore file

* fix: test

* chore: backup and restore support learn more

* refactor: upload restore file

* refactor: upload restore file

* fix: test

* fix: test

* chore: dumpable collection with title

* chore: pg only test

* chore: test

* fix: test

* chore: test sleep

* style: locale improve

* refactor: download backup file

* refactor: start restore

* fix: restore key name

* refactor: start restore

* refactor: start restore

* refactor: start restore

* refactor: start restore

* refactor: start restore

* refactor: start restore

* chore: unify duplicator option

* fix: dump empty collection

* chore: test

* chore: test

* style: style improve

* refactor: locale improve

* chore: dumpalbe collection orders

* style: style improve

* style: style improve

* style: icon adjust

* chore: nginx body size

* chore: get file status

* feat: run dump task

* feat: download api

* chore: backup files resourcer

* feat: restore destroy api

* chore: backup files resoucer

* feat: list backup files action

* chore: get collection meta from dumped file

* fix: dump file name

* fix: test

* chore: backup and restore ui

* chore: swagger api for backup & restore

* chore: api doc

* chore: api doc

* chore: api doc

* chore: backup and restore ui

* chore: backup and restore ui

* chore: backup and restore ui

* chore: backup and restore ui

* chore: backup and restore ui

* fix: restore values

* style: style improve

* fix: download field respontype

* fix: restore form local file

* refactor: local improve

* refactor: delete backup file

* fix: in progress status

* refactor: locale improve

* refactor: locale improve

* refactor: style improve

* refactor: style improve

* refactor: style improve

* test: dump collection table attribute

* chore: dump collection with table attributes

* chore: test

* chore: create new table in restore

* fix: import error

* chore: restore table from backup file

* chore: sync collection after restore collections

* fix: restore json data

* style: style improve

* chore: restore with fields

* chore: test

* fix: test

* fix: test with underscored

* style: style improve

* fix: lock file state

* chore: add test file

* refactor: backup & restore plugin

* fix: mysql test

* chore: skip import view collection

* chore: restore collection with inherits topo order

* fix: import

* style: style improve

* fix: restore sequence fields

* fix: themeConfig collection duplicator option

* fix: restore with dialectOnly meta

* fix: throw error

* fix: restore

* fix: import backup file created in postgres into mysql

* fix: repeated items in inherits

* chore: upgrade after restore

* feat: check database env before restore

* feat: handle autoincr val in postgres

* chore: sqlite & mysql queryInterface

* chore: test

* fix: test

* chore: test

* fix: build

* fix: pg test

* fix: restore with date field

* chore: theme-config collection

* chore: chage import collections method to support collection origin

* chore: fallback get autoincr value in mysql

* fix: dataType normalize

* chore: delay restore

* chore: test

* fix: build

* feat: collectin onDump

* feat: collection onDump interface

* chore: dump with view collection

* chore: sync in restore

* refactor: locale improve

* refactor: code improve

* fix: test

* fix: data sync

* chore: rename backup & restore plugin

* chore: skip test

* style: style improve

* style: style improve

* style: style improve

* style: style improve

* chore: import version check

* chore: backup file dir

* chore: build

* fix: bugs

* fix: error

* fix: pageSize

* fix: import origin

* fix: improve code

* fix: remove namespace

* chore: dump rules config

* fix: dump custom collection

* chore: version

* fix: test

* fix: test

* fix: test

* fix: test

* chore: test

* fix: load custom collection

* fix: client

* fix: translation

* chore: code

* fix: bug

* fix:  support shared option

* fix: roles collection dumpRules

* chore: test

* fix: define collections

* chore: collection group

* fix: translation

* fix: translation

* fix: restore options

* chore: restore command

* refactor: optimize the command line

* chore: dump error

* fix: test error

* fix:  test error

* fix: test error

* fix: test error

* fix: test error

* fix: skip cli test cases

* fix: test error

* fix: too many open files

* fix: update migration version

* fix: migrations

* fix: upgrade

* fix: error

* fix: migration error

* fix: upgrade

* fix: test error

* fix: timeout

* fix: width

* feat: auto load collections

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: ipc error

* fix: test error

---------

Co-authored-by: Chareice <chareice@live.com>
Co-authored-by: katherinehhh <katherine_15995@163.com>
2024-01-08 19:05:14 +08:00

249 lines
6.6 KiB
TypeScript

import Database, { Collection, MagicAttributeModel, SyncOptions, Transactionable } from '@nocobase/database';
import lodash from 'lodash';
import { FieldModel } from './field';
import { QueryInterfaceDropTableOptions } from 'sequelize';
interface LoadOptions extends Transactionable {
// TODO
skipField?: boolean | Array<string>;
skipExist?: boolean;
resetFields?: boolean;
}
export class CollectionModel extends MagicAttributeModel {
get db(): Database {
return (<any>this.constructor).database;
}
async load(loadOptions: LoadOptions = {}) {
const { skipExist, skipField, resetFields, transaction } = loadOptions;
const name = this.get('name');
let collection: Collection;
const collectionOptions = {
origin: '@nocobase/plugin-collection-manager',
...this.get(),
fields: [],
};
if (!this.db.inDialect('postgres') && collectionOptions.schema) {
delete collectionOptions.schema;
}
if (this.db.hasCollection(name)) {
collection = this.db.getCollection(name);
if (skipExist) {
return collection;
}
if (resetFields) {
collection.resetFields();
}
collection.updateOptions(collectionOptions);
} else {
if (!collectionOptions.dumpRules) {
lodash.set(collectionOptions, 'dumpRules.group', 'custom');
}
collection = this.db.collection(collectionOptions);
}
if (!skipField) {
await this.loadFields({ transaction });
}
if (lodash.isArray(skipField)) {
await this.loadFields({ transaction, skipField });
}
await this.db.emitAsync('collection:loaded', {
collection,
transaction,
});
return collection;
}
async loadFields(
options: Transactionable & {
skipField?: Array<string>;
includeFields?: Array<string>;
} = {},
) {
let fields = this.get('fields') || [];
if (!fields.length) {
fields = await this.getFields(options);
}
if (options.skipField) {
fields = fields.filter((field) => !options.skipField.includes(field.name));
}
if (options.includeFields) {
fields = fields.filter((field) => options.includeFields.includes(field.name));
}
if (this.options.view && fields.find((f) => f.name == 'id')) {
// set id field to primary key, other primary key to false
fields = fields.map((field) => {
if (field.name == 'id') {
field.set('primaryKey', true);
} else {
field.set('primaryKey', false);
}
return field;
});
}
// @ts-ignore
const instances: FieldModel[] = fields;
for (const instance of instances) {
await instance.load(options);
}
}
async remove(options?: Transactionable & QueryInterfaceDropTableOptions) {
const { transaction } = options || {};
const name = this.get('name');
const collection = this.db.getCollection(name);
if (!collection) {
return;
}
const fields = await this.db.getRepository('fields').find({
filter: {
'type.$in': ['belongsToMany', 'belongsTo', 'hasMany', 'hasOne'],
},
transaction,
});
for (const field of fields) {
if (field.get('target') && field.get('target') === name) {
await field.destroy({ transaction });
} else if (field.get('through') && field.get('through') === name) {
await field.destroy({ transaction });
}
}
await collection.removeFromDb(options);
}
async migrate(options?: SyncOptions & Transactionable) {
const pendingFieldsTargetToThis = this.db.pendingFields.get(this.get('name')) || [];
const getPendingField = () =>
pendingFieldsTargetToThis.map((field) => {
return {
name: field.get('name'),
collectionName: field.get('collectionName'),
};
});
const beforePendingFields = getPendingField();
const collection = await this.load({
transaction: options?.transaction,
});
const afterPendingFields = getPendingField();
const resolvedPendingFields = lodash.differenceWith(beforePendingFields, afterPendingFields, lodash.isEqual);
const resolvedPendingFieldsCollections = lodash.uniq(resolvedPendingFields.map((field) => field.collectionName));
// postgres support zero column table, other database should not sync it to database
// @ts-ignore
if (Object.keys(collection.model.tableAttributes).length == 0 && !this.db.inDialect('postgres')) {
return;
}
try {
const syncOptions = {
force: false,
alter: {
drop: false,
},
...options,
};
await collection.sync(syncOptions);
for (const collectionName of resolvedPendingFieldsCollections) {
await this.db.getCollection(collectionName).sync(syncOptions);
}
} catch (error) {
console.error(error);
const name = this.get('name');
this.db.removeCollection(name);
throw error;
}
}
isInheritedModel() {
return this.get('inherits');
}
async findParents(options: Transactionable) {
const { transaction } = options;
const findModelParents = async (model: CollectionModel, carry = []) => {
if (!model.get('inherits')) {
return;
}
const parents = lodash.castArray(model.get('inherits'));
for (const parent of parents) {
const parentModel = (await this.db.getCollection('collections').repository.findOne({
filterByTk: parent,
transaction,
})) as CollectionModel;
carry.push(parentModel.get('name'));
await findModelParents(parentModel, carry);
}
return carry;
};
return findModelParents(this);
}
async parentFields(options: Transactionable) {
const { transaction } = options;
return this.db.getCollection('fields').repository.find({
filter: {
collectionName: { $in: await this.findParents({ transaction }) },
},
transaction,
});
}
// sync fields from parents
async syncParentFields(options: Transactionable) {
const { transaction } = options;
const ancestorFields = await this.parentFields({ transaction });
const selfFields = await this.getFields({ transaction });
const inheritedFields = ancestorFields.filter((field: FieldModel) => {
return (
!field.isAssociationField() &&
!selfFields.find((selfField: FieldModel) => selfField.get('name') == field.get('name'))
);
});
for (const inheritedField of inheritedFields) {
await this.createField(lodash.omit(inheritedField.toJSON(), ['key', 'collectionName', 'sort']), {
transaction,
});
}
}
}