From dfbdbc741dd02c863a1b55e1c7c225e304eeec93 Mon Sep 17 00:00:00 2001
From: ChengLei Shao <chareice@live.com>
Date: Sun, 26 Feb 2023 08:04:29 +0800
Subject: [PATCH] fix: collection schema updated but model _schema not change
 (#1500)

* fix: collection schema updated but model _schema not change

* test: schema changed
---
 .../src/__tests__/postgres/schema.test.ts     | 20 +++++++++++++++++++
 packages/core/database/src/collection.ts      |  2 +-
 packages/core/database/src/database.ts        |  6 ++++++
 packages/core/server/src/application.ts       |  4 +++-
 .../plugin-manager/PluginManagerRepository.ts |  6 +++++-
 .../src/repositories/collection-repository.ts |  6 ++++++
 .../src/models/application.ts                 | 15 +++++++-------
 packages/presets/nocobase/src/index.ts        |  5 +++--
 8 files changed, 52 insertions(+), 12 deletions(-)

diff --git a/packages/core/database/src/__tests__/postgres/schema.test.ts b/packages/core/database/src/__tests__/postgres/schema.test.ts
index 07d3ccf5e..2164ee43d 100644
--- a/packages/core/database/src/__tests__/postgres/schema.test.ts
+++ b/packages/core/database/src/__tests__/postgres/schema.test.ts
@@ -97,4 +97,24 @@ describe('postgres schema', () => {
 
     expect(newTableInfo[0].find((item) => item['table_name'] == collection.model.tableName)).toBeTruthy();
   });
+
+  it('should update schema options', async () => {
+    if (!db.inDialect('postgres')) return;
+
+    await db.clean({ drop: true });
+
+    const collection = db.collection({
+      name: 'test',
+    });
+
+    await db.sync();
+
+    collection.updateOptions({
+      ...collection.options,
+      schema: 'test',
+    });
+
+    // @ts-ignore
+    expect(collection.model._schema).toEqual('test');
+  });
 });
diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts
index 9973feabd..f37cdda23 100644
--- a/packages/core/database/src/collection.ts
+++ b/packages/core/database/src/collection.ts
@@ -351,8 +351,8 @@ export class Collection<
   updateOptions(options: CollectionOptions, mergeOptions?: any) {
     let newOptions = lodash.cloneDeep(options);
     newOptions = merge(this.options, newOptions, mergeOptions);
-    this.context.database.emit('beforeUpdateCollection', this, newOptions);
 
+    this.context.database.emit('beforeUpdateCollection', this, newOptions);
     this.options = newOptions;
 
     this.setFields(options.fields, false);
diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts
index a58db3bf9..eaa00ef0c 100644
--- a/packages/core/database/src/database.ts
+++ b/packages/core/database/src/database.ts
@@ -312,6 +312,12 @@ export class Database extends EventEmitter implements AsyncEmitter {
       }
     });
 
+    this.on('afterUpdateCollection', (collection, options) => {
+      if (collection.options.schema) {
+        collection.model._schema = collection.options.schema;
+      }
+    });
+
     this.on('beforeDefineCollection', (options) => {
       if (options.underscored) {
         if (lodash.get(options, 'sortable.scopeKey')) {
diff --git a/packages/core/server/src/application.ts b/packages/core/server/src/application.ts
index 4a4f71ede..8be425b59 100644
--- a/packages/core/server/src/application.ts
+++ b/packages/core/server/src/application.ts
@@ -483,7 +483,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
     if (this.db.inDialect('mysql')) {
       const result = await this.db.sequelize.query(`SHOW VARIABLES LIKE 'lower_case_table_names'`, { plain: true });
       if (result?.Value === '1' && !this.db.options.underscored) {
-        console.log(`Your database lower_case_table_names=1, please add ${chalk.yellow('DB_UNDERSCORED=true')} to the .env file`);
+        console.log(
+          `Your database lower_case_table_names=1, please add ${chalk.yellow('DB_UNDERSCORED=true')} to the .env file`,
+        );
         if (options?.exit) {
           process.exit();
         }
diff --git a/packages/core/server/src/plugin-manager/PluginManagerRepository.ts b/packages/core/server/src/plugin-manager/PluginManagerRepository.ts
index 6c25cabcb..9a5ee7948 100644
--- a/packages/core/server/src/plugin-manager/PluginManagerRepository.ts
+++ b/packages/core/server/src/plugin-manager/PluginManagerRepository.ts
@@ -45,7 +45,11 @@ export class PluginManagerRepository extends Repository {
   }
 
   async load() {
-    const items = await this.find();
+    // sort plugins by id
+    const items = await this.find({
+      sort: 'id',
+    });
+
     for (const item of items) {
       await this.pm.addStatic(item.get('name'), {
         ...item.get('options'),
diff --git a/packages/plugins/collection-manager/src/repositories/collection-repository.ts b/packages/plugins/collection-manager/src/repositories/collection-repository.ts
index e0acb8e6f..e0df996ca 100644
--- a/packages/plugins/collection-manager/src/repositories/collection-repository.ts
+++ b/packages/plugins/collection-manager/src/repositories/collection-repository.ts
@@ -52,6 +52,12 @@ export class CollectionRepository extends Repository {
 
   async db2cm(collectionName: string) {
     const collection = this.database.getCollection(collectionName);
+
+    // skip if collection already exists
+    if (await this.findOne({ filter: { name: collectionName } })) {
+      return;
+    }
+
     const options = collection.options;
     const fields = [];
     for (const [name, field] of collection.fields) {
diff --git a/packages/plugins/multi-app-manager/src/models/application.ts b/packages/plugins/multi-app-manager/src/models/application.ts
index 7d7b87e68..070111d33 100644
--- a/packages/plugins/multi-app-manager/src/models/application.ts
+++ b/packages/plugins/multi-app-manager/src/models/application.ts
@@ -18,14 +18,15 @@ export class ApplicationModel extends Model {
     await app.load();
 
     if (!(await app.isInstalled())) {
-      await app.db.sync({
-        force: false,
-        alter: {
-          drop: false,
-        },
-      });
-
+      await app.db.sync();
       await app.install();
+
+      // emit an event on mainApp
+      // current if you add listener on subApp through `subApp.on('afterInstall')` , it will be clear after subApp installed
+      await mainApp.emitAsync('afterSubAppInstalled', {
+        mainApp,
+        subApp: app,
+      });
     }
 
     await app.start();
diff --git a/packages/presets/nocobase/src/index.ts b/packages/presets/nocobase/src/index.ts
index 02d16e966..79e333da3 100644
--- a/packages/presets/nocobase/src/index.ts
+++ b/packages/presets/nocobase/src/index.ts
@@ -83,7 +83,7 @@ export class PresetNocoBase extends Plugin {
     this.app.on('beforeUpgrade', async (options) => {
       const result = await this.app.version.satisfies('<0.8.0-alpha.1');
       if (result) {
-        console.log(`Initialize all built-in plugins`);
+        console.log(`Initialize all built-in plugins beforeUpgrade`);
         await this.addBuiltInPlugins({ method: 'upgrade' });
       }
       const builtInPlugins = this.getBuiltInPlugins();
@@ -105,8 +105,9 @@ export class PresetNocoBase extends Plugin {
       await this.app.reload({ method: 'upgrade' });
       await this.app.db.sync();
     });
+
     this.app.on('beforeInstall', async (options) => {
-      console.log(`Initialize all built-in plugins`);
+      console.log(`Initialize all built-in plugins beforeInstall`);
       await this.addBuiltInPlugins({ method: 'install' });
     });
   }