diff --git a/packages/core/database/src/repository.ts b/packages/core/database/src/repository.ts index 216993332..59c7a2057 100644 --- a/packages/core/database/src/repository.ts +++ b/packages/core/database/src/repository.ts @@ -555,7 +555,7 @@ export class Repository { await app.destroy(); }); + it('should create through table when pending fields', async () => { + await Collection.repository.create({ + values: { + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'belongsToMany', + target: 'tags', + name: 'tags', + through: 'posts_tags', + foreignKey: 'post_id', + otherKey: 'tag_id', + sourceKey: 'id', + targetKey: 'id', + }, + ], + }, + context: {}, + }); + + const postsCollection = db.getCollection('posts'); + expect(postsCollection).toBeTruthy(); + + await Collection.repository.create({ + values: { + name: 'tags', + fields: [ + { + type: 'string', + name: 'name', + }, + ], + }, + context: {}, + }); + + const throughCollection = db.getCollection('posts_tags'); + expect(throughCollection).toBeTruthy(); + + expect(await throughCollection.existsInDb()).toBeTruthy(); + const rawAttribute = throughCollection.model.rawAttributes['tag_id'].field; + const columns = await db.sequelize.getQueryInterface().describeTable(throughCollection.getTableNameWithSchema()); + expect(columns[rawAttribute]).toBeDefined(); + }); + + it('should create collections with array', async () => { + await Collection.repository.create({ + values: [ + { + name: 'users', + fields: [ + { + type: 'string', + name: 'username', + }, + { + type: 'hasMany', + target: 'posts', + name: 'posts', + }, + { + type: 'hasOne', + target: 'profiles', + name: 'profile', + }, + ], + }, + { + name: 'profiles', + fields: [ + { + type: 'string', + name: 'nickname', + }, + { + type: 'belongsTo', + target: 'users', + name: 'user', + }, + ], + }, + { + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'belongsToMany', + target: 'tags', + name: 'tags', + through: 'posts_tags', + foreignKey: 'post_id', + otherKey: 'tag_id', + sourceKey: 'id', + targetKey: 'id', + }, + ], + }, + { + name: 'tags', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'belongsToMany', + target: 'posts', + name: 'posts', + through: 'posts_tags', + foreignKey: 'tag_id', + otherKey: 'post_id', + sourceKey: 'id', + targetKey: 'id', + }, + ], + }, + ], + context: {}, + }); + + const postsCollection = db.getCollection('posts'); + expect(postsCollection).toBeTruthy(); + + const tagsCollection = db.getCollection('tags'); + expect(tagsCollection).toBeTruthy(); + + const usersCollection = db.getCollection('users'); + expect(usersCollection).toBeTruthy(); + + const profilesCollection = db.getCollection('profiles'); + expect(profilesCollection).toBeTruthy(); + + await usersCollection.repository.create({ + values: { + username: 'admin', + profile: { + nickname: '管理员', + }, + posts: [ + { + title: 'test', + tags: [ + { + name: 'test', + }, + ], + }, + ], + }, + }); + }); + it('should create collection with description', async () => { const description = 'this collection is for tests'; await Collection.repository.create({ diff --git a/packages/plugins/@nocobase/plugin-collection-manager/src/server/models/collection.ts b/packages/plugins/@nocobase/plugin-collection-manager/src/server/models/collection.ts index 522999b17..856634d32 100644 --- a/packages/plugins/@nocobase/plugin-collection-manager/src/server/models/collection.ts +++ b/packages/plugins/@nocobase/plugin-collection-manager/src/server/models/collection.ts @@ -1,6 +1,7 @@ import Database, { Collection, MagicAttributeModel, SyncOptions, Transactionable } from '@nocobase/database'; import lodash from 'lodash'; import { FieldModel } from './field'; +import { async } from 'fast-glob'; interface LoadOptions extends Transactionable { // TODO @@ -114,10 +115,26 @@ export class CollectionModel extends MagicAttributeModel { } 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')) { @@ -125,13 +142,19 @@ export class CollectionModel extends MagicAttributeModel { } try { - await collection.sync({ + 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');