From 8c03d2692c4607cbf7707268ef06cf6239710f89 Mon Sep 17 00:00:00 2001 From: TowyTowy Date: Sun, 12 Jul 2026 22:36:16 +0200 Subject: [PATCH] fix: create internal application when plugin adds Messenger after init When a plugin gains the Messenger capability after it was first initialized for a user, its plugin conf already exists without an associated internal application (ApplicationID == 0). Messages sent by the plugin were then stored with application_id = 0, orphaning them: they disappeared on reload and could not be deleted (#653). Back-fill the internal application during initialization when a Messenger plugin has none yet, mirroring the creation already done for plugins that support Messenger from the start. Fixes #653 Co-Authored-By: Claude --- plugin/manager.go | 42 +++++++++++++++++++++++++++++++++--------- plugin/manager_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/plugin/manager.go b/plugin/manager.go index 5048f232..438cd1eb 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -323,6 +323,20 @@ func (m *Manager) initializeSingleUserPlugin(userCtx compat.UserContext, p compa m.instances[pluginConf.ID] = instance if compat.HasSupport(instance, compat.Messenger) { + if pluginConf.ApplicationID == 0 { + // The Messenger capability was added after this plugin was first + // initialized for the user, so no internal application exists yet. + // Create one now, otherwise messages would be stored with + // application_id = 0 and become orphaned (not shown, not deletable). + app, err := m.createInternalApplication(info, userID) + if err != nil { + return err + } + pluginConf.ApplicationID = app.ID + if err := m.db.UpdatePluginConf(pluginConf); err != nil { + return err + } + } instance.SetMessageHandler(redirectToChannel{ ApplicationID: pluginConf.ApplicationID, UserID: pluginConf.UserID, @@ -399,15 +413,8 @@ func (m *Manager) createPluginConf(instance compat.PluginInstance, info compat.I pluginConf.Config, _ = yaml.Marshal(instance.DefaultConfig()) } if compat.HasSupport(instance, compat.Messenger) { - tokenPublic, _ := auth.GenerateApplicationToken() - app := &model.Application{ - Token: tokenPublic, - Name: info.String(), - UserID: userID, - Internal: true, - Description: fmt.Sprintf("auto generated application for %s", info.ModulePath), - } - if err := m.db.CreateApplication(app); err != nil { + app, err := m.createInternalApplication(info, userID) + if err != nil { return nil, err } pluginConf.ApplicationID = app.ID @@ -417,3 +424,20 @@ func (m *Manager) createPluginConf(instance compat.PluginInstance, info compat.I } return pluginConf, nil } + +// createInternalApplication creates the auto generated internal application a +// Messenger plugin uses to publish its messages. +func (m *Manager) createInternalApplication(info compat.Info, userID uint) (*model.Application, error) { + tokenPublic, _ := auth.GenerateApplicationToken() + app := &model.Application{ + Token: tokenPublic, + Name: info.String(), + UserID: userID, + Internal: true, + Description: fmt.Sprintf("auto generated application for %s", info.ModulePath), + } + if err := m.db.CreateApplication(app); err != nil { + return nil, err + } + return app, nil +} diff --git a/plugin/manager_test.go b/plugin/manager_test.go index 63811cff..6be5c74e 100644 --- a/plugin/manager_test.go +++ b/plugin/manager_test.go @@ -443,6 +443,40 @@ func TestNewManager_InternalApplicationManagement(t *testing.T) { } } +func TestNewManager_MessengerAddedAfterInit_createsApplication(t *testing.T) { + db := testdb.NewDBWithDefaultUser(t) + + // Simulate a plugin conf that was created on a previous startup when the + // plugin did not yet implement the Messenger interface: it has no + // associated internal application (ApplicationID == 0). + assert.NoError(t, db.CreatePluginConf(&model.PluginConf{ + UserID: 1, + ModulePath: mock.ModulePath, + Enabled: true, + Token: auth.GeneratePluginToken(), + })) + + manager, err := NewManager(db, "", nil, nil) + assert.Nil(t, err) + assert.Nil(t, manager.LoadPlugin(new(mock.Plugin))) + // The mock plugin supports Messenger, so re-initializing must back-fill the + // missing internal application instead of leaving ApplicationID at 0. + assert.Nil(t, manager.InitializeForUserID(1)) + + conf, err := db.GetPluginConfByUserAndPath(1, mock.ModulePath) + assert.NoError(t, err) + if assert.NotNil(t, conf) { + assert.NotZero(t, conf.ApplicationID, "an internal application should have been created for the messenger plugin") + + app, err := db.GetApplicationByID(conf.ApplicationID) + assert.NoError(t, err) + if assert.NotNil(t, app) { + assert.True(t, app.Internal) + assert.Equal(t, uint(1), app.UserID) + } + } +} + func TestPluginFileLoadError(t *testing.T) { err := pluginFileLoadError{Filename: "test.so", UnderlyingError: errors.New("test error")} assert.Error(t, err)