diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 68b1d00f1..cd291ff30 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -55,7 +55,7 @@ jobs: run: yarn test -i env: DB_DIALECT: sqlite - DB_STORAGE: ":memory:" + DB_STORAGE: /tmp/db.sqlite - name: Test with MySQL run: yarn test -i env: diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts index 7c9033080..76f6b570e 100644 --- a/packages/core/database/src/collection.ts +++ b/packages/core/database/src/collection.ts @@ -11,7 +11,6 @@ export type RepositoryType = typeof Repository; export type CollectionSortable = string | boolean | { name?: string; scopeKey?: string }; - export interface CollectionOptions extends Omit { name: string; tableName?: string; diff --git a/packages/core/resourcer/src/utils.ts b/packages/core/resourcer/src/utils.ts index 216aaad94..45d6aee36 100644 --- a/packages/core/resourcer/src/utils.ts +++ b/packages/core/resourcer/src/utils.ts @@ -1,4 +1,5 @@ import _ from 'lodash'; +// @ts-ignore import { pathToRegexp } from 'path-to-regexp'; import qs from 'qs'; import { ResourceType } from './resource'; diff --git a/packages/plugins/collection-manager/src/__tests__/index.ts b/packages/plugins/collection-manager/src/__tests__/index.ts index 1f5fdc956..8a7a811b9 100644 --- a/packages/plugins/collection-manager/src/__tests__/index.ts +++ b/packages/plugins/collection-manager/src/__tests__/index.ts @@ -2,12 +2,18 @@ import { mockServer } from '@nocobase/test'; import PluginUiSchema from '@nocobase/plugin-ui-schema-storage'; import CollectionManagerPlugin from '..'; +import lodash from 'lodash'; -export async function createApp() { +export async function createApp(options = {}) { const app = mockServer(); - await app.cleanDb(); + + if (lodash.get(options, 'cleanDB', true)) { + await app.cleanDb(); + } + app.plugin(CollectionManagerPlugin); app.plugin(PluginUiSchema); + await app.load(); return app; } diff --git a/packages/plugins/collection-manager/src/__tests__/through.test.ts b/packages/plugins/collection-manager/src/__tests__/through.test.ts new file mode 100644 index 000000000..d702ddf36 --- /dev/null +++ b/packages/plugins/collection-manager/src/__tests__/through.test.ts @@ -0,0 +1,148 @@ +import { mockServer, MockServer } from '@nocobase/test'; +import CollectionManagerPlugin from '../plugin'; + +describe('collections repository', () => { + let app: MockServer; + + beforeEach(async () => { + app = mockServer({ + database: { + tablePrefix: 'through_', + }, + }); + await app.cleanDb(); + app.plugin(CollectionManagerPlugin); + await app.load(); + await app.install({ clean: true }); + await app.start(); + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('case 1', async () => { + await app + .agent() + .resource('collections') + .create({ + values: { + name: 'resumes', + createdBy: true, + updatedBy: true, + sortable: true, + fields: [ + { + name: 'id', + type: 'integer', + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true }, + interface: 'id', + }, + ], + title: '简历', + }, + }); + + await app + .agent() + .resource('collections') + .create({ + values: { + name: 'jobs', + createdBy: true, + updatedBy: true, + sortable: true, + fields: [ + { + name: 'id', + type: 'integer', + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true }, + interface: 'id', + }, + ], + title: '职位', + }, + }); + + await app + .agent() + .resource('collections') + .create({ + values: { + name: 'matches', + createdBy: true, + updatedBy: true, + sortable: true, + fields: [ + { + name: 'id', + type: 'integer', + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true }, + interface: 'id', + }, + ], + title: '匹配', + }, + }); + + await app + .agent() + .resource('collections.fields', 'resumes') + .create({ + values: { + name: 'jobs', + type: 'belongsToMany', + uiSchema: { + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '职位', + }, + reverseField: { + interface: 'linkTo', + type: 'belongsToMany', + uiSchema: { + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '简历', + }, + }, + interface: 'linkTo', + target: 'jobs', + through: 'matches', + }, + }); + + const matchesCollection = app.db.getCollection('matches'); + + const matchesFields = [...matchesCollection.fields.entries()]; + const matchJobField = matchesFields.find((item) => item[1].options.target == 'jobs'); + + expect(matchesCollection.model.rawAttributes[matchJobField[1].options.foreignKey].primaryKey).not.toBeTruthy(); + + const app2 = mockServer({ + database: { + tablePrefix: 'through_', + }, + }); + app2.plugin(CollectionManagerPlugin); + await app2.load(); + await app2.start(); + + await app2.db.sync(); + + expect( + app.db.getCollection('matches').model.rawAttributes[matchJobField[1].options.foreignKey].primaryKey, + ).not.toBeTruthy(); + + await app2.destroy(); + }); +}); diff --git a/packages/plugins/collection-manager/src/models/collection.ts b/packages/plugins/collection-manager/src/models/collection.ts index f73fd0dbb..b1cc696ec 100644 --- a/packages/plugins/collection-manager/src/models/collection.ts +++ b/packages/plugins/collection-manager/src/models/collection.ts @@ -44,6 +44,7 @@ export class CollectionModel extends MagicAttributeModel { async loadFields(options: Transactionable = {}) { // @ts-ignore const instances: FieldModel[] = await this.getFields(options); + for (const instance of instances) { await instance.load(options); } diff --git a/packages/plugins/collection-manager/src/plugin.ts b/packages/plugins/collection-manager/src/plugin.ts index 366f7b079..8190ac444 100644 --- a/packages/plugins/collection-manager/src/plugin.ts +++ b/packages/plugins/collection-manager/src/plugin.ts @@ -8,7 +8,7 @@ import { afterCreateForReverseField, beforeCreateForChildrenCollection, beforeCreateForReverseField, - beforeInitOptions + beforeInitOptions, } from './hooks'; import { CollectionModel, FieldModel } from './models'; diff --git a/packages/plugins/collection-manager/src/repositories/collection-repository.ts b/packages/plugins/collection-manager/src/repositories/collection-repository.ts index c413c25ca..6eadf87cb 100644 --- a/packages/plugins/collection-manager/src/repositories/collection-repository.ts +++ b/packages/plugins/collection-manager/src/repositories/collection-repository.ts @@ -10,6 +10,30 @@ export class CollectionRepository extends Repository { async load(options: LoadOptions = {}) { const { filter, skipExist } = options; const instances = (await this.find({ filter })) as CollectionModel[]; + + const throughModels = []; + + for (const instance of instances) { + // @ts-ignore + const fields = await instance.getFields(); + for (const field of fields) { + if (field['type'] === 'belongsToMany') { + const throughName = field.options.through; + if (throughName) { + throughModels.push(throughName); + } + } + } + } + + instances.sort((a, b) => { + if (throughModels.includes(a.get('name'))) { + return -1; + } + + return 1; + }); + for (const instance of instances) { await instance.load({ skipExist }); }