diff --git a/server/channels/api4/apitestlib.go b/server/channels/api4/apitestlib.go index 8092541755..4751ec9095 100644 --- a/server/channels/api4/apitestlib.go +++ b/server/channels/api4/apitestlib.go @@ -299,8 +299,7 @@ func SetupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelpe } func SetupConfigWithStoreMock(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper { - setupOptions := []app.Option{app.SkipProductsInitialization()} - th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, false, false, updateConfig, setupOptions) + th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, false, false, updateConfig, nil) statusMock := mocks.StatusStore{} statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil) statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil) @@ -314,8 +313,7 @@ func SetupConfigWithStoreMock(tb testing.TB, updateConfig func(cfg *model.Config } func SetupWithStoreMock(tb testing.TB) *TestHelper { - setupOptions := []app.Option{app.SkipProductsInitialization()} - th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, false, false, nil, setupOptions) + th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, false, false, nil, nil) statusMock := mocks.StatusStore{} statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil) statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil) @@ -329,8 +327,7 @@ func SetupWithStoreMock(tb testing.TB) *TestHelper { } func SetupEnterpriseWithStoreMock(tb testing.TB, options ...app.Option) *TestHelper { - setupOptions := append(options, app.SkipProductsInitialization()) - th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, true, false, nil, setupOptions) + th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, true, false, nil, options) statusMock := mocks.StatusStore{} statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil) statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil) diff --git a/server/channels/app/app.go b/server/channels/app/app.go index 736e9b5046..f4dab46ed7 100644 --- a/server/channels/app/app.go +++ b/server/channels/app/app.go @@ -12,7 +12,6 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/timezones" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/einterfaces" "github.com/mattermost/mattermost/server/v8/platform/services/httpservice" "github.com/mattermost/mattermost/server/v8/platform/services/imageproxy" @@ -155,15 +154,3 @@ func (a *App) SetServer(srv *Server) { func (a *App) UpdateExpiredDNDStatuses() ([]*model.Status, error) { return a.Srv().Store().Status().UpdateExpiredDNDStatuses() } - -// Ensure system service adapter implements `product.SystemService` -var _ product.SystemService = (*systemServiceAdapter)(nil) - -// systemServiceAdapter provides a collection of system APIs for use by products. -type systemServiceAdapter struct { - server *Server -} - -func (ssa *systemServiceAdapter) GetDiagnosticId() string { - return ssa.server.TelemetryId() -} diff --git a/server/channels/app/app_iface.go b/server/channels/app/app_iface.go index 0a7c1013c9..b042cd8056 100644 --- a/server/channels/app/app_iface.go +++ b/server/channels/app/app_iface.go @@ -140,9 +140,7 @@ type AppIface interface { EnablePlugin(id string) *model.AppError // EnsureBot provides similar functionality with the plugin-api BotService. It doesn't accept // any ensureBotOptions hence it is not required for now. - // TODO: Once the focalboard migration completed, we should add this logic to the app and - // let plugin-api use the same code - EnsureBot(rctx request.CTX, productID string, bot *model.Bot) (string, error) + EnsureBot(rctx request.CTX, pluginID string, bot *model.Bot) (string, error) // Expand announcements in incoming webhooks from Slack. Those announcements // can be found in the text attribute, or in the pretext, text, title and value // attributes of the attachment structure. The Slack attachment structure is diff --git a/server/channels/app/bot.go b/server/channels/app/bot.go index c0a3d23141..f8d4fae514 100644 --- a/server/channels/app/bot.go +++ b/server/channels/app/bot.go @@ -14,7 +14,6 @@ import ( "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" ) @@ -23,23 +22,9 @@ const ( botUserKey = internalKeyPrefix + "botid" ) -// Ensure bot service wrapper implements `product.BotService` -var _ product.BotService = (*botServiceWrapper)(nil) - -// botServiceWrapper provides an implementation of `product.BotService` for use by products. -type botServiceWrapper struct { - app AppIface -} - -func (w *botServiceWrapper) EnsureBot(c request.CTX, productID string, bot *model.Bot) (string, error) { - return w.app.EnsureBot(c, productID, bot) -} - // EnsureBot provides similar functionality with the plugin-api BotService. It doesn't accept // any ensureBotOptions hence it is not required for now. -// TODO: Once the focalboard migration completed, we should add this logic to the app and -// let plugin-api use the same code -func (a *App) EnsureBot(rctx request.CTX, productID string, bot *model.Bot) (string, error) { +func (a *App) EnsureBot(rctx request.CTX, pluginID string, bot *model.Bot) (string, error) { if bot == nil { return "", errors.New("passed a nil bot") } @@ -48,7 +33,7 @@ func (a *App) EnsureBot(rctx request.CTX, productID string, bot *model.Bot) (str return "", errors.New("passed a bot with no username") } - botIDBytes, err := a.GetPluginKey(productID, botUserKey) + botIDBytes, err := a.GetPluginKey(pluginID, botUserKey) if err != nil { return "", err } @@ -74,11 +59,11 @@ func (a *App) EnsureBot(rctx request.CTX, productID string, bot *model.Bot) (str // Check for an existing bot user with that username. If one exists, then use that. if user, appErr := a.GetUserByUsername(bot.Username); appErr == nil && user != nil { if user.IsBot { - if appErr := a.SetPluginKey(productID, botUserKey, []byte(user.Id)); appErr != nil { + if appErr := a.SetPluginKey(pluginID, botUserKey, []byte(user.Id)); appErr != nil { return "", fmt.Errorf("failed to set plugin key: %w", err) } } else { - rctx.Logger().Error("Product attempted to use an account that already exists. Convert user to a bot "+ + rctx.Logger().Error("Plugin attempted to use an account that already exists. Convert user to a bot "+ "account in the CLI by running 'mattermost user convert --bot'. If the user is an "+ "existing user account you want to preserve, change its username and restart the Mattermost server, "+ "after which the plugin will create a bot account with that name. For more information about bot "+ @@ -96,7 +81,7 @@ func (a *App) EnsureBot(rctx request.CTX, productID string, bot *model.Bot) (str return "", fmt.Errorf("failed to create bot: %w", err) } - if appErr := a.SetPluginKey(productID, botUserKey, []byte(createdBot.UserId)); appErr != nil { + if appErr := a.SetPluginKey(pluginID, botUserKey, []byte(createdBot.UserId)); appErr != nil { return "", fmt.Errorf("failed to set plugin key: %w", err) } diff --git a/server/channels/app/channel.go b/server/channels/app/channel.go index 9eab610d70..40387e6b13 100644 --- a/server/channels/app/channel.go +++ b/server/channels/app/channel.go @@ -19,7 +19,6 @@ import ( "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" pUtils "github.com/mattermost/mattermost/server/public/utils" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore" ) @@ -28,89 +27,6 @@ const ( UpdateMultipleMaximum = 200 ) -// channelsWrapper provides an implementation of `product.ChannelService` to be used by products. -type channelsWrapper struct { - app *App -} - -func (s *channelsWrapper) GetDirectChannel(userID1, userID2 string) (*model.Channel, *model.AppError) { - return s.app.getDirectChannel(request.EmptyContext(s.app.Log()), userID1, userID2) -} - -// GetChannelByID gets a Channel by its ID. -func (s *channelsWrapper) GetChannelByID(channelID string) (*model.Channel, *model.AppError) { - return s.app.GetChannel(request.EmptyContext(s.app.Log()), channelID) -} - -// GetChannelMember gets a channel member by userID. -func (s *channelsWrapper) GetChannelMember(channelID string, userID string) (*model.ChannelMember, *model.AppError) { - return s.app.GetChannelMember(request.EmptyContext(s.app.Log()), channelID, userID) -} - -func (s *channelsWrapper) GetChannelsForTeamForUser(teamID string, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, *model.AppError) { - return s.app.GetChannelsForTeamForUser(request.EmptyContext(s.app.Log()), teamID, userID, opts) -} - -func (s *channelsWrapper) GetChannelSidebarCategories(userID, teamID string) (*model.OrderedSidebarCategories, *model.AppError) { - return s.app.GetSidebarCategoriesForTeamForUser(request.EmptyContext(s.app.Log()), userID, teamID) -} - -func (s *channelsWrapper) GetChannelMembers(channelID string, page, perPage int) (model.ChannelMembers, *model.AppError) { - return s.app.GetChannelMembersPage(request.EmptyContext(s.app.Log()), channelID, page, perPage) -} - -func (s *channelsWrapper) CreateChannelSidebarCategory(userID, teamID string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) { - return s.app.CreateSidebarCategory(request.EmptyContext(s.app.Log()), userID, teamID, newCategory) -} - -func (s *channelsWrapper) UpdateChannelSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) { - return s.app.UpdateSidebarCategories(request.EmptyContext(s.app.Log()), userID, teamID, categories) -} - -func (s *channelsWrapper) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) { - return s.app.CreateChannel(request.EmptyContext(s.app.Log()), channel, false) -} - -func (s *channelsWrapper) AddUserToChannel(channelID, userID, asUserID string) (*model.ChannelMember, *model.AppError) { - ctx := request.EmptyContext(s.app.Log()) - channel, err := s.app.GetChannel(ctx, channelID) - if err != nil { - return nil, err - } - - return s.app.AddChannelMember(ctx, userID, channel, ChannelMemberOpts{ - UserRequestorID: asUserID, - }) -} - -func (s *channelsWrapper) UpdateChannelMemberRoles(channelID, userID, newRoles string) (*model.ChannelMember, *model.AppError) { - return s.app.UpdateChannelMemberRoles(request.EmptyContext(s.app.Log()), channelID, userID, newRoles) -} - -func (s *channelsWrapper) DeleteChannelMember(channelID, userID string) *model.AppError { - return s.app.LeaveChannel(request.EmptyContext(s.app.Log()), channelID, userID) -} - -func (s *channelsWrapper) AddChannelMember(channelID, userID string) (*model.ChannelMember, *model.AppError) { - channel, err := s.GetChannelByID(channelID) - if err != nil { - return nil, err - } - - return s.app.AddChannelMember(request.EmptyContext(s.app.Log()), userID, channel, ChannelMemberOpts{ - // For now, don't allow overriding these via the plugin API. - UserRequestorID: "", - PostRootID: "", - }) -} - -func (s *channelsWrapper) GetDirectChannelOrCreate(userID1, userID2 string) (*model.Channel, *model.AppError) { - return s.app.GetOrCreateDirectChannel(request.EmptyContext(s.app.Log()), userID1, userID2) -} - -// Ensure the wrapper implements the product service. -var _ product.ChannelService = (*channelsWrapper)(nil) - // DefaultChannelNames returns the list of system-wide default channel names. // // By default the list will be (not necessarily in this order): diff --git a/server/channels/app/channels.go b/server/channels/app/channels.go index a4721e1c4e..af2b224e67 100644 --- a/server/channels/app/channels.go +++ b/server/channels/app/channels.go @@ -4,7 +4,6 @@ package app import ( - "fmt" "runtime" "strings" "sync" @@ -16,31 +15,26 @@ import ( "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/app/imaging" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/config" "github.com/mattermost/mattermost/server/v8/einterfaces" "github.com/mattermost/mattermost/server/v8/platform/services/imageproxy" "github.com/mattermost/mattermost/server/v8/platform/shared/filestore" ) -const ServerKey product.ServiceKey = "server" - -// licenseSvc is added to act as a starting point for future integrated products. -// It has the same signature and functionality with the license related APIs of the plugin-api. -type licenseSvc interface { - GetLicense() *model.License - RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError - RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError +type configService interface { + Config() *model.Config + AddConfigListener(listener func(*model.Config, *model.Config)) string + RemoveConfigListener(id string) + UpdateConfig(f func(*model.Config)) + SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) } // Channels contains all channels related state. type Channels struct { srv *Server - cfgSvc product.ConfigService + cfgSvc configService filestore filestore.FileBackend exportFilestore filestore.FileBackend - licenseSvc licenseSvc - routerSvc *routerService postActionCookieSecret []byte @@ -84,76 +78,16 @@ type Channels struct { postReminderTask *model.ScheduledTask } -func init() { - product.RegisterProduct("channels", product.Manifest{ - Initializer: func(services map[product.ServiceKey]any) (product.Product, error) { - return NewChannels(services) - }, - Dependencies: map[product.ServiceKey]struct{}{ - ServerKey: {}, - product.ConfigKey: {}, - product.LicenseKey: {}, - product.FilestoreKey: {}, - product.ExportFilestoreKey: {}, - }, - }) -} - -func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { - s, ok := services[ServerKey].(*Server) - if !ok { - return nil, errors.New("server not passed") - } +func NewChannels(s *Server) (*Channels, error) { ch := &Channels{ - srv: s, - imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), - uploadLockMap: map[string]bool{}, + srv: s, + imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), + uploadLockMap: map[string]bool{}, + filestore: s.FileBackend(), + exportFilestore: s.ExportFileBackend(), + cfgSvc: s.Platform(), } - // To get another service: - // 1. Prepare the service interface - // 2. Add the field to *Channels - // 3. Add the service key to the slice. - // 4. Add a new case in the switch statement. - requiredServices := []product.ServiceKey{ - product.ConfigKey, - product.LicenseKey, - product.FilestoreKey, - product.ExportFilestoreKey, - } - for _, svcKey := range requiredServices { - svc, ok := services[svcKey] - if !ok { - return nil, fmt.Errorf("Service %s not passed", svcKey) - } - switch svcKey { - // Keep adding more services here - case product.ConfigKey: - cfgSvc, ok := svc.(product.ConfigService) - if !ok { - return nil, errors.New("Config service did not satisfy ConfigSvc interface") - } - ch.cfgSvc = cfgSvc - case product.FilestoreKey: - filestore, ok := svc.(filestore.FileBackend) - if !ok { - return nil, errors.New("Filestore service did not satisfy FileBackend interface") - } - ch.filestore = filestore - case product.ExportFilestoreKey: - exportFilestore, ok := svc.(filestore.FileBackend) - if !ok { - return nil, errors.New("Export filestore service did not satisfy FileBackend interface") - } - ch.exportFilestore = exportFilestore - case product.LicenseKey: - svc, ok := svc.(licenseSvc) - if !ok { - return nil, errors.New("License service did not satisfy licenseSvc interface") - } - ch.licenseSvc = svc - } - } // We are passing a partially filled Channels struct so that the enterprise // methods can have access to app methods. // Otherwise, passing server would mean it has to call s.Channels(), @@ -207,45 +141,12 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { return nil, errors.Wrap(imgErr, "failed to create image encoder") } - ch.routerSvc = newRouterService() - services[product.RouterKey] = ch.routerSvc - // Setup routes. pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() pluginsRoute.HandleFunc("", ch.ServePluginRequest) pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest) pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest) - services[product.ChannelKey] = &channelsWrapper{ - app: &App{ch: ch}, - } - - services[product.PostKey] = &postServiceWrapper{ - app: &App{ch: ch}, - } - - services[product.PermissionsKey] = &permissionsServiceWrapper{ - app: &App{ch: ch}, - } - - services[product.TeamKey] = &teamServiceWrapper{ - app: &App{ch: ch}, - } - - services[product.BotKey] = &botServiceWrapper{ - app: &App{ch: ch}, - } - - services[product.UserKey] = &App{ch: ch} - - services[product.PreferencesKey] = &preferencesServiceWrapper{ - app: &App{ch: ch}, - } - - services[product.CommandKey] = &App{ch: ch} - - services[product.ThreadsKey] = &App{ch: ch} - return ch, nil } @@ -314,35 +215,22 @@ func (ch *Channels) RemoveConfigListener(id string) { ch.cfgSvc.RemoveConfigListener(id) } -func (ch *Channels) License() *model.License { - return ch.licenseSvc.GetLicense() -} - -func (ch *Channels) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError { - return ch.licenseSvc.RequestTrialLicenseWithExtraFields(requesterID, trialRequest) -} - -func (ch *Channels) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError { - return ch.licenseSvc.RequestTrialLicense(requesterID, users, termsAccepted, - receiveEmailsAccepted) -} - func (ch *Channels) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { if env := ch.GetPluginsEnvironment(); env != nil { env.RunMultiPluginHook(hookRunnerFunc, hookId) } } -func (ch *Channels) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { - var hooks plugin.Hooks - if env := ch.GetPluginsEnvironment(); env != nil { - // we intentionally ignore the error here, because the id can be a product id - // we are going to check if we have the hooks or not - hooks, _ = env.HooksForPlugin(id) - if hooks != nil { - return hooks, nil - } +func (ch *Channels) HooksForPlugin(id string) (plugin.Hooks, error) { + env := ch.GetPluginsEnvironment() + if env == nil { + return nil, errors.New("plugins are not initialized") } - return nil, fmt.Errorf("could not find hooks for id %s", id) + hooks, err := env.HooksForPlugin(id) + if err != nil { + return nil, err + } + + return hooks, nil } diff --git a/server/channels/app/cloud.go b/server/channels/app/cloud.go index 788c5eae7a..7335068549 100644 --- a/server/channels/app/cloud.go +++ b/server/channels/app/cloud.go @@ -13,27 +13,9 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" - "github.com/mattermost/mattermost/server/v8/einterfaces" ) -// Ensure cloud service wrapper implements `product.CloudService` -var _ product.CloudService = (*cloudWrapper)(nil) - -// cloudWrapper provides an implementation of `product.CloudService` for use by products. -type cloudWrapper struct { - cloud einterfaces.CloudInterface -} - -func (c *cloudWrapper) GetCloudLimits() (*model.ProductLimits, error) { - if c.cloud != nil { - return c.cloud.GetCloudLimits("") - } - - return &model.ProductLimits{}, nil -} - func (a *App) getSysAdminsEmailRecipients() ([]*model.User, *model.AppError) { userOptions := &model.UserGetOptions{ Page: 0, diff --git a/server/channels/app/cluster_handlers.go b/server/channels/app/cluster_handlers.go index 20ee62e0d6..720ce9ea07 100644 --- a/server/channels/app/cluster_handlers.go +++ b/server/channels/app/cluster_handlers.go @@ -33,10 +33,6 @@ func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { return } pluginID := msg.Props["PluginID"] - // if the plugin key is empty, the message might be coming from a product. - if pluginID == "" { - pluginID = msg.Props["ProductID"] - } eventID := msg.Props["EventID"] if pluginID == "" || eventID == "" { s.Log().Warn("Invalid ClusterMessage.Props values for plugin event", @@ -46,12 +42,7 @@ func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { return } - channels, ok := s.products["channels"].(*Channels) - if !ok { - return - } - - hooks, err := channels.HooksForPluginOrProduct(pluginID) + hooks, err := s.Channels().HooksForPlugin(pluginID) if err != nil { s.Log().Warn("Getting hooks for plugin failed", mlog.String("plugin_id", pluginID), mlog.Err(err)) return diff --git a/server/channels/app/file.go b/server/channels/app/file.go index dff15a17a7..fddbe4483f 100644 --- a/server/channels/app/file.go +++ b/server/channels/app/file.go @@ -29,7 +29,6 @@ import ( "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/app/imaging" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/channels/utils" "github.com/mattermost/mattermost/server/v8/platform/services/docextractor" @@ -49,18 +48,6 @@ const ( maxContentExtractionSize = 1024 * 1024 // 1MB ) -// Ensure fileInfo service wrapper implements `product.FileInfoStoreService` -var _ product.FileInfoStoreService = (*fileInfoWrapper)(nil) - -// fileInfoWrapper implements `product.FileInfoStoreService` for use by products. -type fileInfoWrapper struct { - srv *Server -} - -func (f *fileInfoWrapper) GetFileInfo(fileID string) (*model.FileInfo, *model.AppError) { - return f.srv.getFileInfo(fileID) -} - func (a *App) FileBackend() filestore.FileBackend { return a.ch.filestore } diff --git a/server/channels/app/helper_test.go b/server/channels/app/helper_test.go index c10e20e396..6a46e11f6d 100644 --- a/server/channels/app/helper_test.go +++ b/server/channels/app/helper_test.go @@ -191,8 +191,7 @@ func SetupWithoutPreloadMigrations(tb testing.TB) *TestHelper { func SetupWithStoreMock(tb testing.TB) *TestHelper { mockStore := testlib.GetMockStoreForSetupFunctions() - setupOptions := []Option{SkipProductsInitialization()} - th := setupTestHelper(mockStore, false, false, nil, setupOptions, tb) + th := setupTestHelper(mockStore, false, false, nil, nil, tb) statusMock := mocks.StatusStore{} statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil) statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil) @@ -213,8 +212,7 @@ func SetupWithStoreMock(tb testing.TB) *TestHelper { func SetupEnterpriseWithStoreMock(tb testing.TB) *TestHelper { mockStore := testlib.GetMockStoreForSetupFunctions() - setupOptions := []Option{SkipProductsInitialization()} - th := setupTestHelper(mockStore, true, false, nil, setupOptions, tb) + th := setupTestHelper(mockStore, true, false, nil, nil, tb) statusMock := mocks.StatusStore{} statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil) statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil) diff --git a/server/channels/app/license.go b/server/channels/app/license.go index c2f879e475..65ede1188a 100644 --- a/server/channels/app/license.go +++ b/server/channels/app/license.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" ) @@ -19,29 +18,16 @@ const ( JWTDefaultTokenExpiration = 7 * 24 * time.Hour // 7 days of expiration ) -// ensure the license service wrapper implements `product.LicenseService` -var _ product.LicenseService = (*licenseWrapper)(nil) - -// licenseWrapper is an adapter struct that only exposes the -// config related functionality to be passed down to other products. -type licenseWrapper struct { - srv *Server +func (ch *Channels) License() *model.License { + return ch.srv.License() } -func (w *licenseWrapper) Name() product.ServiceKey { - return product.LicenseKey -} - -func (w *licenseWrapper) GetLicense() *model.License { - return w.srv.License() -} - -func (w *licenseWrapper) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError { - if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin { +func (ch *Channels) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError { + if *ch.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin { return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden) } - requester, err := w.srv.userService.GetUser(requesterID) + requester, err := ch.srv.userService.GetUser(requesterID) if err != nil { var nfErr *store.ErrNotFound switch { @@ -52,17 +38,17 @@ func (w *licenseWrapper) RequestTrialLicenseWithExtraFields(requesterID string, } } - if w.srv.Cloud.ValidateBusinessEmail(requesterID, trialRequest.ContactEmail) != nil { + if ch.srv.Cloud.ValidateBusinessEmail(requesterID, trialRequest.ContactEmail) != nil { return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request.business-email", nil, "", http.StatusBadRequest) } // Create a new struct only using the fields from the request that are allowed to be set by the client sanitizedRequest := &model.TrialLicenseRequest{ - ServerID: w.srv.TelemetryId(), + ServerID: ch.srv.TelemetryId(), Name: requester.GetDisplayName(model.ShowFullName), Email: requester.Email, - SiteName: *w.srv.platform.Config().TeamSettings.SiteName, - SiteURL: *w.srv.platform.Config().ServiceSettings.SiteURL, + SiteName: *ch.srv.platform.Config().TeamSettings.SiteName, + SiteURL: *ch.srv.platform.Config().ServiceSettings.SiteURL, Users: trialRequest.Users, TermsAccepted: trialRequest.TermsAccepted, ReceiveEmailsAccepted: trialRequest.ReceiveEmailsAccepted, @@ -77,12 +63,12 @@ func (w *licenseWrapper) RequestTrialLicenseWithExtraFields(requesterID string, return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request", nil, "", http.StatusBadRequest) } - return w.srv.platform.RequestTrialLicense(sanitizedRequest) + return ch.srv.platform.RequestTrialLicense(sanitizedRequest) } -// DEPRECATED - use RequestTrialLicenseWithExtraFields instead. This function remains to support the Plugin API. -func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError { - if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin { +// Deprecated: Use RequestTrialLicenseWithExtraFields instead. This function remains to support the Plugin API. +func (ch *Channels) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError { + if *ch.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin { return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden) } @@ -94,7 +80,7 @@ func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, term return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request", nil, "", http.StatusBadRequest) } - requester, err := w.srv.userService.GetUser(requesterID) + requester, err := ch.srv.userService.GetUser(requesterID) if err != nil { var nfErr *store.ErrNotFound switch { @@ -106,17 +92,17 @@ func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, term } trialLicenseRequest := &model.TrialLicenseRequest{ - ServerID: w.srv.TelemetryId(), + ServerID: ch.srv.TelemetryId(), Name: requester.GetDisplayName(model.ShowFullName), Email: requester.Email, - SiteName: *w.srv.platform.Config().TeamSettings.SiteName, - SiteURL: *w.srv.platform.Config().ServiceSettings.SiteURL, + SiteName: *ch.srv.platform.Config().TeamSettings.SiteName, + SiteURL: *ch.srv.platform.Config().ServiceSettings.SiteURL, Users: users, TermsAccepted: termsAccepted, ReceiveEmailsAccepted: receiveEmailsAccepted, } - return w.srv.platform.RequestTrialLicense(trialLicenseRequest) + return ch.srv.platform.RequestTrialLicense(trialLicenseRequest) } // JWTClaims custom JWT claims with the needed information for the diff --git a/server/channels/app/notification_push_test.go b/server/channels/app/notification_push_test.go index 8775c2f6ac..7874c3acfa 100644 --- a/server/channels/app/notification_push_test.go +++ b/server/channels/app/notification_push_test.go @@ -20,7 +20,6 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/i18n" "github.com/mattermost/mattermost/server/v8/channels/app/platform" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks" "github.com/mattermost/mattermost/server/v8/channels/testlib" "github.com/mattermost/mattermost/server/v8/config" @@ -1519,8 +1518,7 @@ func TestPushNotificationRace(t *testing.T) { Return(&model.Preference{Value: "test"}, nil) mockStore.On("Preference").Return(&mockPreferenceStore) s := &Server{ - products: make(map[string]product.Product), - Router: mux.NewRouter(), + Router: mux.NewRouter(), } var err error s.platform, err = platform.New( @@ -1531,16 +1529,9 @@ func TestPushNotificationRace(t *testing.T) { platform.SetExportFileStore(&fmocks.FileBackend{}), platform.StoreOverride(mockStore)) require.NoError(t, err) - serviceMap := map[product.ServiceKey]any{ - ServerKey: s, - product.ConfigKey: s.platform, - product.LicenseKey: &licenseWrapper{s}, - product.FilestoreKey: s.FileBackend(), - product.ExportFilestoreKey: s.ExportFileBackend(), - } - ch, err := NewChannels(serviceMap) + ch, err := NewChannels(s) require.NoError(t, err) - s.products["channels"] = ch + s.ch = ch app := New(ServerConnector(s.Channels())) require.NotPanics(t, func() { diff --git a/server/channels/app/onboarding.go b/server/channels/app/onboarding.go index 08b9efbcff..26f7a9cef4 100644 --- a/server/channels/app/onboarding.go +++ b/server/channels/app/onboarding.go @@ -69,7 +69,7 @@ func (a *App) CompleteOnboarding(c request.CTX, request *model.CompleteOnboardin return } - hooks, err := a.ch.HooksForPluginOrProduct(id) + hooks, err := a.ch.HooksForPlugin(id) if err != nil { c.Logger().Warn("Getting hooks for plugin failed", mlog.String("plugin_id", id), mlog.Err(err)) return diff --git a/server/channels/app/opentracing/opentracing_layer.go b/server/channels/app/opentracing/opentracing_layer.go index 1059e9bf54..be7b144fe3 100644 --- a/server/channels/app/opentracing/opentracing_layer.go +++ b/server/channels/app/opentracing/opentracing_layer.go @@ -4122,7 +4122,7 @@ func (a *OpenTracingAppLayer) EnableUserAccessToken(c request.CTX, token *model. return resultVar0 } -func (a *OpenTracingAppLayer) EnsureBot(rctx request.CTX, productID string, bot *model.Bot) (string, error) { +func (a *OpenTracingAppLayer) EnsureBot(rctx request.CTX, pluginID string, bot *model.Bot) (string, error) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.EnsureBot") @@ -4134,7 +4134,7 @@ func (a *OpenTracingAppLayer) EnsureBot(rctx request.CTX, productID string, bot }() defer span.Finish() - resultVar0, resultVar1 := a.app.EnsureBot(rctx, productID, bot) + resultVar0, resultVar1 := a.app.EnsureBot(rctx, pluginID, bot) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) diff --git a/server/channels/app/options.go b/server/channels/app/options.go index 5877d2aaee..7617e8b4e0 100644 --- a/server/channels/app/options.go +++ b/server/channels/app/options.go @@ -103,17 +103,6 @@ func SkipPostInitialization() Option { } } -// SkipProductsInitialization is intended for testing only, in cases -// where we're mocking components like the store and products cannot -// be initialized correctly -func SkipProductsInitialization() Option { - return func(s *Server) error { - s.skipProductsInit = true - - return nil - } -} - type AppOption func(a *App) type AppOptionCreator func() []AppOption diff --git a/server/channels/app/permissions.go b/server/channels/app/permissions.go index 47225c132e..4557464b99 100644 --- a/server/channels/app/permissions.go +++ b/server/channels/app/permissions.go @@ -14,37 +14,11 @@ import ( "github.com/pkg/errors" "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/public/shared/request" - "github.com/mattermost/mattermost/server/v8/channels/product" ) const permissionsExportBatchSize = 100 const systemSchemeName = "00000000-0000-0000-0000-000000000000" // Prevents collisions with user-created schemes. -// Ensure permissions service wrapper implements `product.PermissionService` -var _ product.PermissionService = (*permissionsServiceWrapper)(nil) - -// permissionsServiceWrapper provides an implementation of `product.PermissionService` for use by products. -type permissionsServiceWrapper struct { - app AppIface -} - -func (s *permissionsServiceWrapper) HasPermissionTo(userID string, permission *model.Permission) bool { - return s.app.HasPermissionTo(userID, permission) -} - -func (s *permissionsServiceWrapper) HasPermissionToTeam(c request.CTX, userID string, teamID string, permission *model.Permission) bool { - return s.app.HasPermissionToTeam(c, userID, teamID, permission) -} - -func (s *permissionsServiceWrapper) HasPermissionToChannel(c request.CTX, askingUserID string, channelID string, permission *model.Permission) bool { - return s.app.HasPermissionToChannel(c, askingUserID, channelID, permission) -} - -func (s *permissionsServiceWrapper) RolesGrantPermission(roleNames []string, permissionId string) bool { - return s.app.RolesGrantPermission(roleNames, permissionId) -} - func (a *App) ResetPermissionsSystem() *model.AppError { // Reset all Teams to not have a scheme. if err := a.Srv().Store().Team().ResetAllTeamSchemes(); err != nil { diff --git a/server/channels/app/platform/cluster.go b/server/channels/app/platform/cluster.go index 8c43e97dc8..b5c3423412 100644 --- a/server/channels/app/platform/cluster.go +++ b/server/channels/app/platform/cluster.go @@ -10,17 +10,10 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/einterfaces" ) -// ensure cluster service wrapper implements `product.ClusterService` -var _ product.ClusterService = (*PlatformService)(nil) - -// Ensure KV store wrapper implements `product.KVStoreService` -var _ product.KVStoreService = (*PlatformService)(nil) - func (ps *PlatformService) Cluster() einterfaces.ClusterInterface { return ps.clusterIFace } @@ -57,8 +50,7 @@ func (ps *PlatformService) PublishPluginClusterEvent(productID string, ev model. SendType: opts.SendType, WaitForAllToSend: false, Props: map[string]string{ - "ProductID": productID, - "EventID": ev.Id, + "EventID": ev.Id, }, Data: ev.Data, } diff --git a/server/channels/app/platform/config.go b/server/channels/app/platform/config.go index 2a9ed4aa7f..8789a7adcc 100644 --- a/server/channels/app/platform/config.go +++ b/server/channels/app/platform/config.go @@ -21,7 +21,6 @@ import ( "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/utils" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/config" "github.com/mattermost/mattermost/server/v8/einterfaces" @@ -37,9 +36,6 @@ type ServiceConfig struct { Cluster einterfaces.ClusterInterface } -// ensure the config wrapper implements `product.ConfigService` -var _ product.ConfigService = (*PlatformService)(nil) - func (ps *PlatformService) Config() *model.Config { return ps.configStore.Get() } diff --git a/server/channels/app/plugin.go b/server/channels/app/plugin.go index 23b86a5f87..cd0b764a70 100644 --- a/server/channels/app/plugin.go +++ b/server/channels/app/plugin.go @@ -17,7 +17,6 @@ import ( "sync" "github.com/blang/semver/v4" - "github.com/gorilla/mux" svg "github.com/h2non/go-is-svg" "github.com/pkg/errors" @@ -25,7 +24,6 @@ import ( "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/utils/fileutils" "github.com/mattermost/mattermost/server/v8/platform/services/marketplace" ) @@ -41,31 +39,6 @@ type pluginSignaturePath struct { signaturePath string } -// Ensure routerService implements `product.RouterService` -var _ product.RouterService = (*routerService)(nil) - -type routerService struct { - mu sync.Mutex - routerMap map[string]*mux.Router -} - -func newRouterService() *routerService { - return &routerService{ - routerMap: make(map[string]*mux.Router), - } -} - -func (rs *routerService) RegisterRouter(productID string, sub *mux.Router) { - rs.mu.Lock() - defer rs.mu.Unlock() - rs.routerMap[productID] = sub -} - -func (rs *routerService) getHandler(productID string) (http.Handler, bool) { - handler, ok := rs.routerMap[productID] - return handler, ok -} - // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and // initialized. // diff --git a/server/channels/app/plugin_hooks_test.go b/server/channels/app/plugin_hooks_test.go index e6e7ea1334..ad51d3b136 100644 --- a/server/channels/app/plugin_hooks_test.go +++ b/server/channels/app/plugin_hooks_test.go @@ -1302,7 +1302,7 @@ func TestHookReactionHasBeenRemoved(t *testing.T) { } func TestHookRunDataRetention(t *testing.T) { - th := Setup(t, SkipProductsInitialization()).InitBasic() + th := Setup(t).InitBasic() defer th.TearDown() tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t, diff --git a/server/channels/app/plugin_requests.go b/server/channels/app/plugin_requests.go index 2a61ca8738..1b2f13bbda 100644 --- a/server/channels/app/plugin_requests.go +++ b/server/channels/app/plugin_requests.go @@ -21,12 +21,7 @@ import ( func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - if handler, ok := ch.routerSvc.getHandler(params["plugin_id"]); ok { - ch.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { - handler.ServeHTTP(w, r) - }) - return - } + pluginID := params["plugin_id"] pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { @@ -38,10 +33,10 @@ func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { return } - hooks, err := pluginsEnvironment.HooksForPlugin(params["plugin_id"]) + hooks, err := pluginsEnvironment.HooksForPlugin(pluginID) if err != nil { mlog.Debug("Access to route for non-existent plugin", - mlog.String("missing_plugin_id", params["plugin_id"]), + mlog.String("missing_plugin_id", pluginID), mlog.String("url", r.URL.String()), mlog.Err(err)) http.NotFound(w, r) diff --git a/server/channels/app/plugin_signature.go b/server/channels/app/plugin_signature.go index 9fefed3b6b..e976f907d5 100644 --- a/server/channels/app/plugin_signature.go +++ b/server/channels/app/plugin_signature.go @@ -82,7 +82,7 @@ func (ch *Channels) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppErro if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil { return nil } - publicKeys := ch.cfgSvc.Config().PluginSettings.SignaturePublicKeyFiles + publicKeys := ch.srv.Config().PluginSettings.SignaturePublicKeyFiles for _, pk := range publicKeys { pkBytes, appErr := ch.srv.getPublicKey(pk) if appErr != nil { diff --git a/server/channels/app/post.go b/server/channels/app/post.go index 6353c59072..f9feec935c 100644 --- a/server/channels/app/post.go +++ b/server/channels/app/post.go @@ -20,7 +20,6 @@ import ( "github.com/mattermost/mattermost/server/public/shared/i18n" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore" "github.com/mattermost/mattermost/server/v8/platform/services/cache" @@ -34,38 +33,6 @@ const ( var atMentionPattern = regexp.MustCompile(`\B@`) -// Ensure post service wrapper implements `product.PostService` -var _ product.PostService = (*postServiceWrapper)(nil) - -// postServiceWrapper provides an implementation of `product.PostService` for use by products. -type postServiceWrapper struct { - app AppIface -} - -func (s *postServiceWrapper) CreatePost(ctx request.CTX, post *model.Post) (*model.Post, *model.AppError) { - return s.app.CreatePostMissingChannel(ctx, post, true, true) -} - -func (s *postServiceWrapper) GetPostsByIds(postIDs []string) ([]*model.Post, int64, *model.AppError) { - return s.app.GetPostsByIds(postIDs) -} - -func (s *postServiceWrapper) SendEphemeralPost(ctx request.CTX, userID string, post *model.Post) *model.Post { - return s.app.SendEphemeralPost(ctx, userID, post) -} - -func (s *postServiceWrapper) GetPost(postID string) (*model.Post, *model.AppError) { - return s.app.GetSinglePost(postID, false) -} - -func (s *postServiceWrapper) DeletePost(ctx request.CTX, postID, productID string) (*model.Post, *model.AppError) { - return s.app.DeletePost(ctx, postID, productID) -} - -func (s *postServiceWrapper) UpdatePost(ctx request.CTX, post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { - return s.app.UpdatePost(ctx, post, false) -} - func (a *App) CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) { // Check that channel has not been deleted channel, errCh := a.Srv().Store().Channel().Get(post.ChannelId, true) diff --git a/server/channels/app/preference.go b/server/channels/app/preference.go index 0b571660d8..677f503159 100644 --- a/server/channels/app/preference.go +++ b/server/channels/app/preference.go @@ -11,29 +11,8 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/shared/request" - "github.com/mattermost/mattermost/server/v8/channels/product" ) -// Ensure preferences service wrapper implements `product.PreferencesService` -var _ product.PreferencesService = (*preferencesServiceWrapper)(nil) - -// preferencesServiceWrapper provides an implementation of `product.PreferencesService` for use by products. -type preferencesServiceWrapper struct { - app AppIface -} - -func (w *preferencesServiceWrapper) GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError) { - return w.app.GetPreferencesForUser(c, userID) -} - -func (w *preferencesServiceWrapper) UpdatePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError { - return w.app.UpdatePreferences(c, userID, preferences) -} - -func (w *preferencesServiceWrapper) DeletePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError { - return w.app.DeletePreferences(c, userID, preferences) -} - func (a *App) GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError) { preferences, err := a.Srv().Store().Preference().GetAll(userID) if err != nil { diff --git a/server/channels/app/product.go b/server/channels/app/product.go deleted file mode 100644 index 073c198317..0000000000 --- a/server/channels/app/product.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package app - -import ( - "fmt" - "strings" - - "github.com/mattermost/mattermost/server/public/shared/mlog" - "github.com/mattermost/mattermost/server/v8/channels/product" -) - -func (s *Server) initializeProducts( - productMap map[string]product.Manifest, - serviceMap map[product.ServiceKey]any, -) error { - // create a product map to consume - pmap := make(map[string]struct{}) - for name := range productMap { - if !s.shouldStart(name) { - continue - } - pmap[name] = struct{}{} - } - - // We figure out the initialization order by trial and error fashion hence maxTry - // is the maximum possible trials of initialization attempts. The order is not - // determined elsewhere therefore we do a on the fly sorting here. Which means the - // initialization order will be resolved during the loop. - maxTry := len(pmap) * len(pmap) - - for len(pmap) > 0 && maxTry != 0 { - initLoop: - for product := range pmap { - manifest := productMap[product] - // we have dependencies defined. Here we check if the serviceMap - // has all the dependencies registered. If not, we continue to the - // loop to let other products initialize and register their services - // if they have any. - for key := range manifest.Dependencies { - if _, ok := serviceMap[key]; !ok { - maxTry-- - continue initLoop - } - } - - // some products can register themselves/their services - initializer := manifest.Initializer - prod, err := initializer(serviceMap) - if err != nil { - return fmt.Errorf("error initializing product %q: %w", product, err) - } - s.products[product] = prod - - // we remove this product from the map to not try to initialize it again - delete(pmap, product) - } - } - - if maxTry == 0 && len(pmap) != 0 { - var products string - for p := range pmap { - products = strings.Join([]string{products, fmt.Sprintf("%q", p)}, " ") - } - return fmt.Errorf("could not initialize product(s) due to circular dependency: %s", products) - } - - return nil -} - -func (s *Server) shouldStart(product string) bool { - if s.skipProductsInit && product != "channels" { - s.Log().Warn("Skipping product start: disabled via server options", mlog.String("product", product)) - return false - } - - return true -} diff --git a/server/channels/app/product_test.go b/server/channels/app/product_test.go deleted file mode 100644 index b3ef99fa8a..0000000000 --- a/server/channels/app/product_test.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package app - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost/server/v8/channels/app/platform" - "github.com/mattermost/mattermost/server/v8/channels/product" - "github.com/mattermost/mattermost/server/v8/config" -) - -const ( - testSrvKey1 = "test_1" - testSrvKey2 = "test_2" -) - -type productA struct{} - -func newProductA(m map[product.ServiceKey]any) (product.Product, error) { - m[testSrvKey1] = nil - return &productA{}, nil -} - -func (p *productA) Start() error { return nil } -func (p *productA) Stop() error { return nil } - -type productB struct{} - -func newProductB(m map[product.ServiceKey]any) (product.Product, error) { - m[testSrvKey2] = nil - return &productB{}, nil -} - -func (p *productB) Start() error { return nil } -func (p *productB) Stop() error { return nil } - -func TestInitializeProducts(t *testing.T) { - configStore := config.NewTestMemoryStore() - memoryConfig := configStore.Get() - memoryConfig.SqlSettings = *mainHelper.GetSQLSettings() - configStore.Set(memoryConfig) - - ps, err := platform.New(platform.ServiceConfig{ConfigStore: configStore}) - require.NoError(t, err) - defer ps.Shutdown() - - t.Run("2 products and no circular dependency", func(t *testing.T) { - serviceMap := map[product.ServiceKey]any{ - product.ConfigKey: nil, - product.LicenseKey: nil, - product.FilestoreKey: nil, - product.ExportFilestoreKey: nil, - product.ClusterKey: nil, - } - - products := map[string]product.Manifest{ - "productA": { - Initializer: newProductA, - Dependencies: map[product.ServiceKey]struct{}{ - product.ConfigKey: {}, - product.LicenseKey: {}, - product.FilestoreKey: {}, - product.ExportFilestoreKey: {}, - product.ClusterKey: {}, - }, - }, - "productB": { - Initializer: newProductB, - Dependencies: map[product.ServiceKey]struct{}{ - product.ConfigKey: {}, - testSrvKey1: {}, - product.FilestoreKey: {}, - product.ExportFilestoreKey: {}, - product.ClusterKey: {}, - }, - }, - } - - server := &Server{ - products: make(map[string]product.Product), - platform: ps, - } - - err = server.initializeProducts(products, serviceMap) - require.NoError(t, err) - require.Len(t, server.products, 2) - }) - - t.Run("2 products and circular dependency", func(t *testing.T) { - serviceMap := map[product.ServiceKey]any{ - product.ConfigKey: nil, - product.LicenseKey: nil, - product.FilestoreKey: nil, - product.ExportFilestoreKey: nil, - product.ClusterKey: nil, - } - - products := map[string]product.Manifest{ - "productA": { - Initializer: newProductA, - Dependencies: map[product.ServiceKey]struct{}{ - product.ConfigKey: {}, - product.LicenseKey: {}, - product.FilestoreKey: {}, - product.ExportFilestoreKey: {}, - product.ClusterKey: {}, - testSrvKey2: {}, - }, - }, - "productB": { - Initializer: newProductB, - Dependencies: map[product.ServiceKey]struct{}{ - product.ConfigKey: {}, - testSrvKey1: {}, - product.FilestoreKey: {}, - product.ExportFilestoreKey: {}, - product.ClusterKey: {}, - }, - }, - } - server := &Server{ - products: make(map[string]product.Product), - platform: ps, - } - - err := server.initializeProducts(products, serviceMap) - require.Error(t, err) - }) - - t.Run("2 products and one w/o any dependency", func(t *testing.T) { - serviceMap := map[product.ServiceKey]any{ - product.ConfigKey: nil, - product.LicenseKey: nil, - product.FilestoreKey: nil, - product.ExportFilestoreKey: nil, - product.ClusterKey: nil, - } - - products := map[string]product.Manifest{ - "productA": { - Initializer: newProductA, - Dependencies: map[product.ServiceKey]struct{}{ - product.ConfigKey: {}, - product.LicenseKey: {}, - }, - }, - "productB": { - Initializer: newProductB, - }, - } - server := &Server{ - products: make(map[string]product.Product), - platform: ps, - } - - err := server.initializeProducts(products, serviceMap) - require.NoError(t, err) - require.Len(t, server.products, 2) - }) -} diff --git a/server/channels/app/server.go b/server/channels/app/server.go index 911392acb4..af98a1e7c7 100644 --- a/server/channels/app/server.go +++ b/server/channels/app/server.go @@ -60,7 +60,6 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/jobs/refresh_post_stats" "github.com/mattermost/mattermost/server/v8/channels/jobs/resend_invitation_email" "github.com/mattermost/mattermost/server/v8/channels/jobs/s3_path_migration" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/channels/utils" "github.com/mattermost/mattermost/server/v8/config" @@ -117,8 +116,6 @@ type Server struct { runEssentialJobs bool Jobs *jobs.JobServer - licenseWrapper *licenseWrapper - timezones *timezones.Timezones htmlTemplateWatcher *templates.Container @@ -141,8 +138,7 @@ type Server struct { Audit *audit.Audit - joinCluster bool - // startSearchEngine bool + joinCluster bool skipPostInit bool Cloud einterfaces.CloudInterface @@ -151,10 +147,7 @@ type Server struct { tracer *tracing.Tracer - skipProductsInit bool - - products map[string]product.Product - services map[product.ServiceKey]any + ch *Channels } func (s *Server) Store() store.Store { @@ -179,8 +172,6 @@ func NewServer(options ...Option) (*Server, error) { RootRouter: rootRouter, LocalRouter: localRouter, timezones: timezones.New(), - products: make(map[string]product.Product), - services: make(map[product.ServiceKey]any), } for _, option := range options { @@ -228,10 +219,6 @@ func NewServer(options ...Option) (*Server, error) { return nil, errors.Wrapf(err, "unable to create users service") } - s.licenseWrapper = &licenseWrapper{ - srv: s, - } - s.teamService, err = teams.New(teams.ServiceConfig{ TeamStore: s.Store().Team(), ChannelStore: s.Store().Channel(), @@ -245,29 +232,6 @@ func NewServer(options ...Option) (*Server, error) { return nil, errors.Wrapf(err, "unable to create teams service") } - // ensure app implements `product.UserService` - var _ product.UserService = (*App)(nil) - - app := New(ServerConnector(s.Channels())) - serviceMap := map[product.ServiceKey]any{ - ServerKey: s, - product.ConfigKey: s.platform, - product.LicenseKey: s.licenseWrapper, - product.FilestoreKey: s.platform.FileBackend(), - product.ExportFilestoreKey: s.platform.ExportFileBackend(), - product.FileInfoStoreKey: &fileInfoWrapper{srv: s}, - product.ClusterKey: s.platform, - product.UserKey: app, - product.LogKey: s.platform.Log(), - product.CloudKey: &cloudWrapper{cloud: s.Cloud}, - product.KVStoreKey: s.platform, - product.StoreKey: store.NewStoreServiceAdapter(s.Store()), - product.SystemKey: &systemServiceAdapter{server: s}, - product.SessionKey: app, - product.FrontendKey: app, - product.CommandKey: app, - } - // It is important to initialize the hub only after the global logger is set // to avoid race conditions while logging from inside the hub. // Step 4: Start platform @@ -276,21 +240,17 @@ func NewServer(options ...Option) (*Server, error) { // NOTE: There should be no call to App.Srv().Channels() before step 5 is done // otherwise it will throw a panic. - // Step 5: Initialize products. + // Step 5: Initialize channels. // Depends on s.httpService, and depends on the hub to be initialized. // Otherwise we run into race conditions. - err = s.initializeProducts(product.GetProducts(), serviceMap) + channels, err := NewChannels(s) if err != nil { - return nil, errors.Wrap(err, "failed to initialize products") + return nil, errors.Wrap(err, "failed to initialize channels") } - s.services = serviceMap + s.ch = channels // After channel is initialized set it to the App object - channelsWrapper, ok := serviceMap[product.ChannelKey].(*channelsWrapper) - if !ok { - return nil, errors.Wrap(err, "channels product is not initialized") - } - app.ch = channelsWrapper.app.ch + app := New(ServerConnector(channels)) // ------------------------------------------------------------------------- // Everything below this is not order sensitive and safe to be moved around. @@ -596,8 +556,7 @@ func (s *Server) AppOptions() []AppOption { } func (s *Server) Channels() *Channels { - ch, _ := s.products["channels"].(*Channels) - return ch + return s.ch } // Return Database type (postgres or mysql) and current version of the schema @@ -770,13 +729,11 @@ func (s *Server) Shutdown() { } } - // Stop products. - // This needs to happen last because products are dependent + // Stop channels. + // This needs to happen last because channels are dependent // on parent services. - for name, product := range s.products { - if err2 := product.Stop(); err2 != nil { - s.Log().Warn("Unable to cleanly stop product", mlog.String("name", name), mlog.Err(err2)) - } + if err = s.Channels().Stop(); err != nil { + s.Log().Warn("Unable to cleanly stop channels", mlog.Err(err)) } if err = s.platform.Shutdown(); err != nil { @@ -874,21 +831,11 @@ func stripPort(hostport string) string { } func (s *Server) Start() error { - // Start products. - // This needs to happen before because products are dependent on the HTTP server. - - // make sure channels starts first - if err := s.products["channels"].Start(); err != nil { + // Start channels. + // This needs to happen before because channels is dependent on the HTTP server. + if err := s.Channels().Start(); err != nil { return errors.Wrap(err, "Unable to start channels") } - for name, product := range s.products { - if name == "channels" { - continue - } - if err := product.Start(); err != nil { - return errors.Wrapf(err, "Unable to start %s", name) - } - } if s.joinCluster && s.platform.Cluster() != nil { s.registerClusterHandlers() diff --git a/server/channels/app/server_test.go b/server/channels/app/server_test.go index ec08ddd1ad..4892a77ecf 100644 --- a/server/channels/app/server_test.go +++ b/server/channels/app/server_test.go @@ -172,7 +172,7 @@ func TestDatabaseTypeAndMattermostVersion(t *testing.T) { os.Setenv("MM_SQLSETTINGS_DRIVERNAME", "postgres") - th := Setup(t, SkipProductsInitialization()) + th := Setup(t) defer th.TearDown() databaseType, mattermostVersion := th.Server.DatabaseTypeAndSchemaVersion() @@ -181,7 +181,7 @@ func TestDatabaseTypeAndMattermostVersion(t *testing.T) { os.Setenv("MM_SQLSETTINGS_DRIVERNAME", "mysql") - th2 := Setup(t, SkipProductsInitialization()) + th2 := Setup(t) defer th2.TearDown() databaseType, mattermostVersion = th2.Server.DatabaseTypeAndSchemaVersion() diff --git a/server/channels/app/team.go b/server/channels/app/team.go index f96fe7d6bd..329d3e7632 100644 --- a/server/channels/app/team.go +++ b/server/channels/app/team.go @@ -26,40 +26,10 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/app/imaging" "github.com/mattermost/mattermost/server/v8/channels/app/teams" "github.com/mattermost/mattermost/server/v8/channels/app/users" - "github.com/mattermost/mattermost/server/v8/channels/product" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore" ) -// teamServiceWrapper provides an implementation of `product.TeamService` to be used by products. -type teamServiceWrapper struct { - app AppIface -} - -func (w *teamServiceWrapper) GetMember(c request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) { - return w.app.GetTeamMember(c, teamID, userID) -} - -func (w *teamServiceWrapper) CreateMember(ctx request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) { - return w.app.AddTeamMember(ctx, teamID, userID) -} - -func (w *teamServiceWrapper) GetGroup(groupID string) (*model.Group, *model.AppError) { - return w.app.GetGroup(groupID, nil, nil) -} - -func (w *teamServiceWrapper) GetTeam(teamID string) (*model.Team, *model.AppError) { - return w.app.GetTeam(teamID) -} - -func (w *teamServiceWrapper) GetGroupMemberUsers(groupID string, page, perPage int) ([]*model.User, *model.AppError) { - users, _, err := w.app.GetGroupMemberUsersPage(groupID, page, perPage, nil) - return users, err -} - -// Ensure the wrapper implements the product service. -var _ product.TeamService = (*teamServiceWrapper)(nil) - func (a *App) AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError { maxActiveTeams := *teamLimits.Active teams, appErr := a.GetAllTeams() diff --git a/server/channels/product/README.md b/server/channels/product/README.md deleted file mode 100644 index 1134e7da7e..0000000000 --- a/server/channels/product/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# Product - -Package product defines the interfaces provided in the multi-product architecture framework. The service interfaces are designed to be a drop in replacement for services defined in the https://github.com/mattermost/mattermost-plugin-api project. Due to limitations such as the use of https://github.com/mattermost/mattermost-server/blob/master/plugin/api.go emerged this new API. Our hope is to use a single API definition or maybe even more interesting solutions like using the app.AppIFace instead (temporarily). - -## Multi-product architecture framework - -The main goal of multi-product architecture effort is to divide the prominent “app” package into sub packages so that we can maintain the complexity and lay the groundwork for future scaling opportunities. And the framework is the implementation of this idea. Currently the framework is very early to be stable and it's going to be evolve in time once we start using it. - -### How does the framework work? - -A product should conform to the following interface: - -```Go -type Product interface { - Start() error - Stop() error -} -``` - -The `app.Server` will take care of starting and stopping products. The product shall register itself via a function called `RegisterProduct` provided by `github.com/mattermost/mattermost-server/server/v8/channels/app` package. To register a product, -a product initializer is required. The signature of a product initializer is defined as following: - -```Go -type app.ProductManifest struct { - Initializer func(*app.Server, map[app.ServiceKey]interface{}) (app.Product, error) - Dependencies map[app.ServiceKey]struct{} -} -``` - -Note that adding dependencies is crucial to let product framework sort product initialization. For example Channels product provides the `product.PostService` implementation therefore it should be initialized before the Boards product since it requires the PostService. An example registration could be depicted as following: - -```Go -func init() { - app.RegisterProduct("focalboard", app.ProductManifest{ - Initializer: NewBoards, - Dependencies: map[app.ServiceKey]struct{}{ - app.PostKey: {}, - app.PermissionsKey: {}, - app.UserKey: {}, - ... - }, - }) -} -``` - -### Adding services to the framework - -A product can provide services to the framework. In fact, `Channels` product provides many services by itself, so it will only need to register the service to `services` map provided by the product initializer. An example of registering a service to the "registry" is shown below: - -```Go -func NewChannels(*app.Server, map[app.ServiceKey]interface{}) (app.Product, error){ - ... - services[app.PostKey] = &postService{ - ... - } - ... -} -``` - -To improve the developer experience, you should also add the service interface to the [api definition](api.go) so that a consumer of the service can explore the methods available to them. Another good practice would be to add the servie key to the [server.go](../app/server.go) file. - -### How does a product get initialized? - -The overall server initialization starts with essential components such as the store, config etc. Right after that we start to initialize the services which are either a standalone service such as the `FileStore` and `UserService` or some services which are eventually wrappers to the server struct itself such as `ClusterService` and `LicenseService`. And the initial service map is created after these stages. - -```Go -func NewServer(options ...Option) (*Server, error) { - ... - s := &Server{} - ... - serviceMap := map[ServiceKey]interface{}{ - ... - } - - if err := s.initializeProducts(products, serviceMap); err != nil { - return nil, errors.Wrap(err, "failed to initialize products") - } - ... -} -``` - -And the product initialization is figured out by a trial and error fashion hence it is done by a maximum possible trials of initialization attempts. The order is not determined elsewhere therefore we do a on the fly sorting here. Which means the initialization order will be resolved during the loop. We have dependencies defined in the product manifest defined above. During the initialization we check if the serviceMap has all the dependencies registered. If not, we continue to the try initialize other products and register their services if they have any. - -### How to add a product to the mattermost-server? - -We don't need to define a product dependency in the `go.mod` file, we can leverage the [module workspaces](https://go.dev/ref/mod#workspaces) here. You can get more info about how we use it [here](https://docs.google.com/document/d/1Uwg_dTSNR9mx9ZDx-7osjlD4n3w13cnG6Kpz3ZGXzsM). We create another file such as `go.work`, and add the dependency there as following: - -``` -go 1.18 -use ./ -use ../sample-product -``` - -This tells the compiler to include `sample-product` to be compiled with the mattermost-server. And in order to trigger `init()` function of a product we add an empty import to a file as following: - -```Go -package imports - -import ( - ... - // Product Imports - _ "github.com/mattermost/focalboard/product" -) -``` - -### Frequently asked questions - -#### Can a product use app.App instead of services? - -Theoretically yes, but you shouldn't. The reason is we want to figure out the common entry points and use cases for the services so that we can divide the App into meaningful and functional sub services. The current service interfaces are great example of how we want to use the services among the products. - -#### How to handle circular dependency of two products? - -We are not expecting this is a requirement for the initial phase, once we complete the first product migration we can think start thinking about this. The first attempt would be to increase the granularity of the initialization phase by adopting service initialization resolution. So that a service can be initialized even a product initialization starts. diff --git a/server/channels/product/api.go b/server/channels/product/api.go deleted file mode 100644 index 00ebc47272..0000000000 --- a/server/channels/product/api.go +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package product - -import ( - "database/sql" - - "github.com/gorilla/mux" - - "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/public/shared/mlog" - "github.com/mattermost/mattermost/server/public/shared/request" - "github.com/mattermost/mattermost/server/v8/platform/shared/filestore" -) - -// RouterService enables registering the product router to the server. After registering the -// router, the ServeHTTP hook which was being used in plugin mode is not required anymore. -// For now, the service implementation is provided by Channels therefore the consumer products -// should add this service key to their dependencies map in the app.ProductManifest. -// -// The service shall be registered via app.RouterKey service key. -type RouterService interface { - RegisterRouter(productID string, sub *mux.Router) -} - -// PostService provides posts related utilities. For now, the service implementation -// is provided by Channels therefore the consumer products should add this service key to -// their dependencies map in the app.ProductManifest. -// -// The service shall be registered via app.PostKey service key. -type PostService interface { - CreatePost(context request.CTX, post *model.Post) (*model.Post, *model.AppError) - GetPostsByIds(postIDs []string) ([]*model.Post, int64, *model.AppError) - SendEphemeralPost(ctx request.CTX, userID string, post *model.Post) *model.Post - GetPost(postID string) (*model.Post, *model.AppError) - DeletePost(ctx request.CTX, postID, productID string) (*model.Post, *model.AppError) - UpdatePost(c request.CTX, post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) -} - -// PermissionService provides permissions related utilities. For now, the service implementation -// is provided by Channels therefore the consumer products should add this service key to their -// dependencies map in the app.ProductManifest. -// -// The service shall be registered via app.PermissionKey service key. -type PermissionService interface { - HasPermissionTo(userID string, permission *model.Permission) bool - HasPermissionToTeam(c request.CTX, userID, teamID string, permission *model.Permission) bool - HasPermissionToChannel(c request.CTX, askingUserID string, channelID string, permission *model.Permission) bool - RolesGrantPermission(roleNames []string, permissionID string) bool -} - -// ClusterService enables to publish cluster events. In addition to that, It's being used for -// mattermost-plugin-api Mutex API with the SetPluginKeyWithOptions method. -// -// The service shall be registered via app.ClusterKey key. -type ClusterService interface { - PublishPluginClusterEvent(productID string, ev model.PluginClusterEvent, opts model.PluginClusterEventSendOptions) error - PublishWebSocketEvent(productID string, event string, payload map[string]any, broadcast *model.WebsocketBroadcast) -} - -// ChannelService provides channel related API The service implementation is provided by -// Channels product therefore the consumer products should add this service key to their -// dependencies map in the app.ProductManifest. -// -// The service shall be registered via app.ChannelKey service key. -type ChannelService interface { - GetDirectChannel(userID1, userID2 string) (*model.Channel, *model.AppError) - GetDirectChannelOrCreate(userID1, userID2 string) (*model.Channel, *model.AppError) - GetChannelByID(channelID string) (*model.Channel, *model.AppError) - GetChannelMember(channelID string, userID string) (*model.ChannelMember, *model.AppError) - GetChannelsForTeamForUser(teamID string, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, *model.AppError) - GetChannelSidebarCategories(userID, teamID string) (*model.OrderedSidebarCategories, *model.AppError) - GetChannelMembers(channelID string, page, perPage int) (model.ChannelMembers, *model.AppError) - CreateChannelSidebarCategory(userID, teamID string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) - UpdateChannelSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) - CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) - AddUserToChannel(channelID, userID, asUserID string) (*model.ChannelMember, *model.AppError) - UpdateChannelMemberRoles(channelID, userID, newRoles string) (*model.ChannelMember, *model.AppError) - DeleteChannelMember(channelID, userID string) *model.AppError - AddChannelMember(channelID, userID string) (*model.ChannelMember, *model.AppError) -} - -// LicenseService provides license related utilities. -// -// The service shall be registered via app.LicenseKey service key. -type LicenseService interface { - GetLicense() *model.License - RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError -} - -// UserService provides user related utilities. Initially this was thought to be app/users.UserService -// but it's replaced by app.App temporarily. The reason is; UserService is a standalone tool whereas the -// existing plugin API was using channels related app functionalities as well. We shall improve the UserService -// to meet emerging requirements. -// -// The service shall be registered via app.UserKey service key. -type UserService interface { - GetUser(userID string) (*model.User, *model.AppError) - UpdateUser(c request.CTX, user *model.User, sendNotifications bool) (*model.User, *model.AppError) - GetUserByEmail(email string) (*model.User, *model.AppError) - GetUserByUsername(username string) (*model.User, *model.AppError) - GetUsersFromProfiles(options *model.UserGetOptions) ([]*model.User, *model.AppError) -} - -// TeamService provides team related utilities. -// -// The service shall be registered via app.TeamKey service key. -type TeamService interface { - GetMember(c request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) - CreateMember(ctx request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) - GetGroup(groupId string) (*model.Group, *model.AppError) - GetTeam(teamID string) (*model.Team, *model.AppError) - GetGroupMemberUsers(groupID string, page, perPage int) ([]*model.User, *model.AppError) -} - -// BotService is just a copy implementation of mattermost-plugin-api EnsureBot method. -// -// The service shall be registered via app.BotKey service key. -type BotService interface { - EnsureBot(ctx request.CTX, productID string, bot *model.Bot) (string, error) -} - -// ConfigService shall be registered via app.ConfigKey service key. -type ConfigService interface { - Config() *model.Config - AddConfigListener(listener func(*model.Config, *model.Config)) string - RemoveConfigListener(id string) - UpdateConfig(f func(*model.Config)) - SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) -} - -// HooksService is the API for adding exiting plugin hooks to the server so that they can be called as -// they were. This Service is required to be accessed after the channels product initialized. -// -// The service shall be registered via app.HooksKey service key. -type HooksService interface { - // RegisterHook checks whether if the 'hooks' implements any method of plugin.Hooks methods. Rather than - // using the whole plugin.Hooks interface with its 20+ methods, a product can implement any exiting method - // of plugin.Hooks w/o requiring to declare which method they implemented or not. This is going to be - // checked on runtime. We have individual interfaces for each method declared in plugin.Hooks interface. - // Hence, while registering a product, the service will check if the product implements any of these individual - // interfaces. If so, a map of hook IDs that are implemented will be used to call the hooks. The method will - // return an error in case if there is an incorrect implementation of the any of the individual interface in runtime. - // Consider checking plugin.Hooks for the reference. - // Following methods are not allowed to be implemented in the product: - // - plugin.Hooks.OnActivate - // - plugin.Hooks.OnDeactivate - // - plugin.Hooks.Implemented - // - plugin.Hooks.ServeHTTP - RegisterHooks(productID string, hooks any) error -} - -// FilestoreService is the API for accessing the file store. -// -// The service shall be registered via app.FilestoreKey service key. -type FilestoreService interface { - filestore.FileBackend -} - -// FileInfoStoreService is the API for accessing the file info store. -// -// The service shall be registered via app.FileInfoStoreKey service key. -type FileInfoStoreService interface { - GetFileInfo(fileID string) (*model.FileInfo, *model.AppError) -} - -// CloudService is the API for accessing the cloud service APIs. -// -// The service shall be registered via app.CloudKey service key. -type CloudService interface { - GetCloudLimits() (*model.ProductLimits, error) -} - -// KVStoreService is the API for accessing the KVStore service APIs. -// -// The service shall be registered via app.KVStoreKey service key. -type KVStoreService interface { - SetPluginKeyWithOptions(pluginID string, key string, value []byte, options model.PluginKVSetOptions) (bool, *model.AppError) - KVGet(productID, key string) ([]byte, *model.AppError) - KVDelete(productID, key string) *model.AppError - KVList(productID string, page, perPage int) ([]string, *model.AppError) -} - -// LogService is the API for accessing the log service APIs. -// -// The service shall be registered via app.LogKey service key. -type LogService interface { - mlog.LoggerIFace -} - -// StoreService is the API for accessing the Store service APIs. -// -// The service shall be registered via app.StoreKey service key. -type StoreService interface { - GetMasterDB() *sql.DB -} - -// SystemService is the API for accessing the System service APIs. -// -// The service shall be registered via app.SystemKey service key. -type SystemService interface { - GetDiagnosticId() string -} - -// PreferencesService is the API for accessing the Preferences service APIs. -// -// The service shall be registered via app.PreferencesKey service key. -type PreferencesService interface { - GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError) - UpdatePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError - DeletePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError -} - -// SessionService is the API for accessing the session. -// -// The service shall be registered via app.SessionKey service key. -type SessionService interface { - GetSessionById(sessionID string) (*model.Session, *model.AppError) -} - -// FrontendService is the API for interacting with front end. -// -// The service shall be registered via app.FrontendKey service key. -type FrontendService interface { - OpenInteractiveDialog(dialog model.OpenDialogRequest) *model.AppError -} - -// ThreadsService is the API for interacting with threads anywhere. -// -// The service shall be registered via app.ThreadsKey service key. -type ThreadsService interface { - RegisterCollectionAndTopic(productID string, collectionType, topicType string) error -} diff --git a/server/channels/product/doc.go b/server/channels/product/doc.go deleted file mode 100644 index 39df413813..0000000000 --- a/server/channels/product/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// Package product defines the interfaces provided in the multi-product architecture -// framework. The service interfaces are designed to be a drop in replacement for services -// defined in the https://github.com/mattermost/mattermost-plugin-api project. Due to limitations -// such as the use of https://github.com/mattermost/mattermost-server/blob/master/plugin/api.go -// emerged this new API. Our hope is to use a single API definition or maybe even more interesting -// solutions like using the app.AppIFace instead. -package product diff --git a/server/channels/product/product.go b/server/channels/product/product.go deleted file mode 100644 index df084ff015..0000000000 --- a/server/channels/product/product.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package product - -type Product interface { - Start() error - Stop() error -} - -type Manifest struct { - Initializer func(map[ServiceKey]any) (Product, error) - Dependencies map[ServiceKey]struct{} -} - -var products = make(map[string]Manifest) - -func RegisterProduct(name string, m Manifest) { - products[name] = m -} - -func GetProducts() map[string]Manifest { - return products -} diff --git a/server/channels/product/service.go b/server/channels/product/service.go deleted file mode 100644 index 57088b8854..0000000000 --- a/server/channels/product/service.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package product - -type ServiceKey string - -const ( - ChannelKey ServiceKey = "channel" - ConfigKey ServiceKey = "config" - LicenseKey ServiceKey = "license" - FilestoreKey ServiceKey = "filestore" - ExportFilestoreKey ServiceKey = "exportfilestore" - FileInfoStoreKey ServiceKey = "fileinfostore" - ClusterKey ServiceKey = "cluster" - CloudKey ServiceKey = "cloud" - PostKey ServiceKey = "post" - TeamKey ServiceKey = "team" - UserKey ServiceKey = "user" - PermissionsKey ServiceKey = "permissions" - RouterKey ServiceKey = "router" - BotKey ServiceKey = "bot" - LogKey ServiceKey = "log" - KVStoreKey ServiceKey = "kvstore" - StoreKey ServiceKey = "storekey" - SystemKey ServiceKey = "systemkey" - PreferencesKey ServiceKey = "preferenceskey" - SessionKey ServiceKey = "sessionkey" - FrontendKey ServiceKey = "frontendkey" - CommandKey ServiceKey = "commandkey" - ThreadsKey ServiceKey = "threadskey" -) diff --git a/server/channels/store/store.go b/server/channels/store/store.go index 2ea88f50ca..11cdddc0d9 100644 --- a/server/channels/store/store.go +++ b/server/channels/store/store.go @@ -13,7 +13,6 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" - "github.com/mattermost/mattermost/server/v8/channels/product" ) type StoreResult[T any] struct { @@ -74,7 +73,7 @@ type Store interface { GetAppliedMigrations() ([]model.AppliedMigration, error) GetDbVersion(numerical bool) (string, error) // GetInternalMasterDB allows access to the raw master DB - // handle for the multi-product architecture. + // handle for plugins. GetInternalMasterDB() *sql.DB GetInternalReplicaDB() *sql.DB TotalMasterDbConnections() int @@ -1117,21 +1116,3 @@ type SidebarCategorySearchOpts struct { ExcludeTeam bool Type model.SidebarCategoryType } - -// Ensure store service adapter implements `product.StoreService` -var _ product.StoreService = (*StoreServiceAdapter)(nil) - -// StoreServiceAdapter provides a simple Store wrapper for use with products. -type StoreServiceAdapter struct { - store Store -} - -func NewStoreServiceAdapter(store Store) *StoreServiceAdapter { - return &StoreServiceAdapter{ - store: store, - } -} - -func (a *StoreServiceAdapter) GetMasterDB() *sql.DB { - return a.store.GetInternalMasterDB() -} diff --git a/server/channels/web/web_test.go b/server/channels/web/web_test.go index f9631a9f72..77946c3dd4 100644 --- a/server/channels/web/web_test.go +++ b/server/channels/web/web_test.go @@ -55,7 +55,7 @@ func SetupWithStoreMock(tb testing.TB) *TestHelper { tb.SkipNow() } - th := setupTestHelper(tb, false, []app.Option{app.SkipProductsInitialization()}) + th := setupTestHelper(tb, false, nil) emptyMockStore := mocks.Store{} emptyMockStore.On("Close").Return(nil) th.App.Srv().SetStore(&emptyMockStore) diff --git a/server/platform/services/telemetry/telemetry.go b/server/platform/services/telemetry/telemetry.go index 4ce8be5ced..d0dd66a668 100644 --- a/server/platform/services/telemetry/telemetry.go +++ b/server/platform/services/telemetry/telemetry.go @@ -78,7 +78,6 @@ const ( TrackConfigExport = "config_export" TrackConfigWrangler = "config_wrangler" TrackFeatureFlags = "config_feature_flags" - TrackConfigProducts = "products" TrackPermissionsGeneral = "permissions_general" TrackPermissionsSystemScheme = "permissions_system_scheme" TrackPermissionsTeamSchemes = "permissions_team_schemes" diff --git a/server/public/model/config.go b/server/public/model/config.go index 864663f581..9fca265c98 100644 --- a/server/public/model/config.go +++ b/server/public/model/config.go @@ -3494,7 +3494,7 @@ type Config struct { DataRetentionSettings DataRetentionSettings MessageExportSettings MessageExportSettings JobSettings JobSettings - ProductSettings ProductSettings + ProductSettings ProductSettings // Deprecated: Remove in next major version:: https://mattermost.atlassian.net/browse/MM-56655 PluginSettings PluginSettings DisplaySettings DisplaySettings GuestAccountsSettings GuestAccountsSettings diff --git a/server/public/plugin/interface_generator/main.go b/server/public/plugin/interface_generator/main.go index 777677c660..2605094842 100644 --- a/server/public/plugin/interface_generator/main.go +++ b/server/public/plugin/interface_generator/main.go @@ -40,13 +40,6 @@ var excludedPluginHooks = []string{ "ServeMetrics", } -var excludedProductHooks = []string{ - "Implemented", - "OnActivate", - "OnDeactivate", - "ServeHTTP", -} - type IHookEntry struct { FuncName string Args *ast.FieldList @@ -385,62 +378,6 @@ func (s *apiRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name {{end}} ` -var productHooksTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// Code generated by "make pluginapi" -// DO NOT EDIT - -package plugin - -{{range .HooksMethods}} -type {{.Name}}IFace interface { - {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} -} - -{{end}} - -type HooksAdapter struct { - implemented map[int]struct{} - productHooks any -} - -func NewAdapter(productHooks any) (*HooksAdapter, error) { - a := &HooksAdapter{ - implemented: make(map[int]struct{}), - productHooks: productHooks, - } - var tt reflect.Type - ft := reflect.TypeOf(productHooks) - {{range .HooksMethods}} - // Assessing the type of the productHooks if it individually implements {{.Name}} interface. - tt = reflect.TypeOf((*{{.Name}}IFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[{{.Name}}ID] = struct{}{} - } else if _, ok := ft.MethodByName("{{.Name}}"); ok{ - return nil, errors.New("hook has {{.Name}} method but does not implement plugin.{{.Name}} interface") - } - - {{end}} - - return a, nil -} - -{{range .HooksMethods}} -func (a *HooksAdapter) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} { - if _, ok := a.implemented[{{.Name}}ID]; !ok { - panic("product hooks must implement {{.Name}}") - } - - {{if .Return}}return a.productHooks.({{.Name}}IFace).{{.Name}}({{valuesOnly .Params}}){{else}}a.productHooks.({{.Name}}IFace).{{.Name}}({{valuesOnly .Params}}){{end}} - -} - -{{end}} - -` - var apiTimerLayerTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. @@ -586,39 +523,6 @@ func generateHooksGlue(info *PluginInterfaceInfo) { } } -func generateProductHooksInterfaces(info *PluginInterfaceInfo) { - templateFunctions := map[string]any{ - "funcStyle": func(fields *ast.FieldList) string { return FieldListToFuncList(fields, info.FileSet) }, - "valuesOnly": func(fields *ast.FieldList) string { return FieldListToNames(fields, false) }, - } - - templateParams := HooksTemplateParams{} - for _, hook := range info.Hooks { - templateParams.HooksMethods = append(templateParams.HooksMethods, MethodParams{ - Name: hook.FuncName, - Params: hook.Args, - Return: hook.Results, - }) - } - - productHooksTemplate, err := template.New("hooks").Funcs(templateFunctions).Parse(productHooksTemplate) - if err != nil { - panic(err) - } - - templateResult := &bytes.Buffer{} - productHooksTemplate.Execute(templateResult, &templateParams) - - formatted, err := imports.Process("", templateResult.Bytes(), nil) - if err != nil { - panic(err) - } - - if err := os.WriteFile(filepath.Join(getPluginPackageDir(), "product_hooks_generated.go"), formatted, 0664); err != nil { - panic(err) - } -} - func generatePluginTimerLayer(info *PluginInterfaceInfo) { templateFunctions := map[string]any{ "funcStyle": func(fields *ast.FieldList) string { return FieldListToFuncList(fields, info.FileSet) }, @@ -723,8 +627,6 @@ func main() { if err != nil { fmt.Println("Unable to get plugin info: " + err.Error()) } - log.Println("Generating product hooks interfaces") - generateProductHooksInterfaces(removeExcluded(forRPC, excludedProductHooks)) log.Println("Generating plugin hooks glue") generateHooksGlue(removeExcluded(forRPC, excludedPluginHooks)) diff --git a/server/public/plugin/product.go b/server/public/plugin/product.go deleted file mode 100644 index 557b7546af..0000000000 --- a/server/public/plugin/product.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package plugin - -import ( - "net/http" -) - -type RegisteredProduct struct { - ProductID string - Adapter Hooks -} - -func (rp *RegisteredProduct) Implements(hookId int) bool { - adapter, ok := rp.Adapter.(*HooksAdapter) - if !ok { - return false - } - - _, ok = adapter.implemented[hookId] - return ok -} - -// Implemented method is overridden intentionally to prevent calling it from outside. -func (a *HooksAdapter) Implemented() ([]string, error) { - return nil, nil -} - -// OnActivate is overridden intentionally as product should not call it. -func (a *HooksAdapter) OnActivate() error { - return nil -} - -// OnDeactivate is overridden intentionally as product should not call it. -func (a *HooksAdapter) OnDeactivate() error { - return nil -} - -// ServeHTTP is overridden intentionally as product should not call it. -func (a *HooksAdapter) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) {} diff --git a/server/public/plugin/product_hooks_generated.go b/server/public/plugin/product_hooks_generated.go deleted file mode 100644 index 2e4f112358..0000000000 --- a/server/public/plugin/product_hooks_generated.go +++ /dev/null @@ -1,824 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// Code generated by "make pluginapi" -// DO NOT EDIT - -package plugin - -import ( - "errors" - "io" - "net/http" - "reflect" - - "github.com/mattermost/mattermost/server/public/model" -) - -type OnConfigurationChangeIFace interface { - OnConfigurationChange() error -} - -type ExecuteCommandIFace interface { - ExecuteCommand(c *Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) -} - -type UserHasBeenCreatedIFace interface { - UserHasBeenCreated(c *Context, user *model.User) -} - -type UserWillLogInIFace interface { - UserWillLogIn(c *Context, user *model.User) string -} - -type UserHasLoggedInIFace interface { - UserHasLoggedIn(c *Context, user *model.User) -} - -type MessageWillBePostedIFace interface { - MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string) -} - -type MessageWillBeUpdatedIFace interface { - MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string) -} - -type MessageHasBeenPostedIFace interface { - MessageHasBeenPosted(c *Context, post *model.Post) -} - -type MessageHasBeenUpdatedIFace interface { - MessageHasBeenUpdated(c *Context, newPost, oldPost *model.Post) -} - -type MessagesWillBeConsumedIFace interface { - MessagesWillBeConsumed(posts []*model.Post) []*model.Post -} - -type MessageHasBeenDeletedIFace interface { - MessageHasBeenDeleted(c *Context, post *model.Post) -} - -type ChannelHasBeenCreatedIFace interface { - ChannelHasBeenCreated(c *Context, channel *model.Channel) -} - -type UserHasJoinedChannelIFace interface { - UserHasJoinedChannel(c *Context, channelMember *model.ChannelMember, actor *model.User) -} - -type UserHasLeftChannelIFace interface { - UserHasLeftChannel(c *Context, channelMember *model.ChannelMember, actor *model.User) -} - -type UserHasJoinedTeamIFace interface { - UserHasJoinedTeam(c *Context, teamMember *model.TeamMember, actor *model.User) -} - -type UserHasLeftTeamIFace interface { - UserHasLeftTeam(c *Context, teamMember *model.TeamMember, actor *model.User) -} - -type FileWillBeUploadedIFace interface { - FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) -} - -type ReactionHasBeenAddedIFace interface { - ReactionHasBeenAdded(c *Context, reaction *model.Reaction) -} - -type ReactionHasBeenRemovedIFace interface { - ReactionHasBeenRemoved(c *Context, reaction *model.Reaction) -} - -type OnPluginClusterEventIFace interface { - OnPluginClusterEvent(c *Context, ev model.PluginClusterEvent) -} - -type OnWebSocketConnectIFace interface { - OnWebSocketConnect(webConnID, userID string) -} - -type OnWebSocketDisconnectIFace interface { - OnWebSocketDisconnect(webConnID, userID string) -} - -type WebSocketMessageHasBeenPostedIFace interface { - WebSocketMessageHasBeenPosted(webConnID, userID string, req *model.WebSocketRequest) -} - -type RunDataRetentionIFace interface { - RunDataRetention(nowTime, batchSize int64) (int64, error) -} - -type OnInstallIFace interface { - OnInstall(c *Context, event model.OnInstallEvent) error -} - -type OnSendDailyTelemetryIFace interface { - OnSendDailyTelemetry() -} - -type OnCloudLimitsUpdatedIFace interface { - OnCloudLimitsUpdated(limits *model.ProductLimits) -} - -type ConfigurationWillBeSavedIFace interface { - ConfigurationWillBeSaved(newCfg *model.Config) (*model.Config, error) -} - -type NotificationWillBePushedIFace interface { - NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string) -} - -type UserHasBeenDeactivatedIFace interface { - UserHasBeenDeactivated(c *Context, user *model.User) -} - -type ServeMetricsIFace interface { - ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request) -} - -type OnSharedChannelsSyncMsgIFace interface { - OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error) -} - -type OnSharedChannelsPingIFace interface { - OnSharedChannelsPing(rc *model.RemoteCluster) bool -} - -type PreferencesHaveChangedIFace interface { - PreferencesHaveChanged(c *Context, preferences []model.Preference) -} - -type OnSharedChannelsAttachmentSyncMsgIFace interface { - OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error -} - -type OnSharedChannelsProfileImageSyncMsgIFace interface { - OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error -} - -type HooksAdapter struct { - implemented map[int]struct{} - productHooks any -} - -func NewAdapter(productHooks any) (*HooksAdapter, error) { - a := &HooksAdapter{ - implemented: make(map[int]struct{}), - productHooks: productHooks, - } - var tt reflect.Type - ft := reflect.TypeOf(productHooks) - - // Assessing the type of the productHooks if it individually implements OnConfigurationChange interface. - tt = reflect.TypeOf((*OnConfigurationChangeIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnConfigurationChangeID] = struct{}{} - } else if _, ok := ft.MethodByName("OnConfigurationChange"); ok { - return nil, errors.New("hook has OnConfigurationChange method but does not implement plugin.OnConfigurationChange interface") - } - - // Assessing the type of the productHooks if it individually implements ExecuteCommand interface. - tt = reflect.TypeOf((*ExecuteCommandIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[ExecuteCommandID] = struct{}{} - } else if _, ok := ft.MethodByName("ExecuteCommand"); ok { - return nil, errors.New("hook has ExecuteCommand method but does not implement plugin.ExecuteCommand interface") - } - - // Assessing the type of the productHooks if it individually implements UserHasBeenCreated interface. - tt = reflect.TypeOf((*UserHasBeenCreatedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserHasBeenCreatedID] = struct{}{} - } else if _, ok := ft.MethodByName("UserHasBeenCreated"); ok { - return nil, errors.New("hook has UserHasBeenCreated method but does not implement plugin.UserHasBeenCreated interface") - } - - // Assessing the type of the productHooks if it individually implements UserWillLogIn interface. - tt = reflect.TypeOf((*UserWillLogInIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserWillLogInID] = struct{}{} - } else if _, ok := ft.MethodByName("UserWillLogIn"); ok { - return nil, errors.New("hook has UserWillLogIn method but does not implement plugin.UserWillLogIn interface") - } - - // Assessing the type of the productHooks if it individually implements UserHasLoggedIn interface. - tt = reflect.TypeOf((*UserHasLoggedInIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserHasLoggedInID] = struct{}{} - } else if _, ok := ft.MethodByName("UserHasLoggedIn"); ok { - return nil, errors.New("hook has UserHasLoggedIn method but does not implement plugin.UserHasLoggedIn interface") - } - - // Assessing the type of the productHooks if it individually implements MessageWillBePosted interface. - tt = reflect.TypeOf((*MessageWillBePostedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[MessageWillBePostedID] = struct{}{} - } else if _, ok := ft.MethodByName("MessageWillBePosted"); ok { - return nil, errors.New("hook has MessageWillBePosted method but does not implement plugin.MessageWillBePosted interface") - } - - // Assessing the type of the productHooks if it individually implements MessageWillBeUpdated interface. - tt = reflect.TypeOf((*MessageWillBeUpdatedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[MessageWillBeUpdatedID] = struct{}{} - } else if _, ok := ft.MethodByName("MessageWillBeUpdated"); ok { - return nil, errors.New("hook has MessageWillBeUpdated method but does not implement plugin.MessageWillBeUpdated interface") - } - - // Assessing the type of the productHooks if it individually implements MessageHasBeenPosted interface. - tt = reflect.TypeOf((*MessageHasBeenPostedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[MessageHasBeenPostedID] = struct{}{} - } else if _, ok := ft.MethodByName("MessageHasBeenPosted"); ok { - return nil, errors.New("hook has MessageHasBeenPosted method but does not implement plugin.MessageHasBeenPosted interface") - } - - // Assessing the type of the productHooks if it individually implements MessageHasBeenUpdated interface. - tt = reflect.TypeOf((*MessageHasBeenUpdatedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[MessageHasBeenUpdatedID] = struct{}{} - } else if _, ok := ft.MethodByName("MessageHasBeenUpdated"); ok { - return nil, errors.New("hook has MessageHasBeenUpdated method but does not implement plugin.MessageHasBeenUpdated interface") - } - - // Assessing the type of the productHooks if it individually implements MessagesWillBeConsumed interface. - tt = reflect.TypeOf((*MessagesWillBeConsumedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[MessagesWillBeConsumedID] = struct{}{} - } else if _, ok := ft.MethodByName("MessagesWillBeConsumed"); ok { - return nil, errors.New("hook has MessagesWillBeConsumed method but does not implement plugin.MessagesWillBeConsumed interface") - } - - // Assessing the type of the productHooks if it individually implements MessageHasBeenDeleted interface. - tt = reflect.TypeOf((*MessageHasBeenDeletedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[MessageHasBeenDeletedID] = struct{}{} - } else if _, ok := ft.MethodByName("MessageHasBeenDeleted"); ok { - return nil, errors.New("hook has MessageHasBeenDeleted method but does not implement plugin.MessageHasBeenDeleted interface") - } - - // Assessing the type of the productHooks if it individually implements ChannelHasBeenCreated interface. - tt = reflect.TypeOf((*ChannelHasBeenCreatedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[ChannelHasBeenCreatedID] = struct{}{} - } else if _, ok := ft.MethodByName("ChannelHasBeenCreated"); ok { - return nil, errors.New("hook has ChannelHasBeenCreated method but does not implement plugin.ChannelHasBeenCreated interface") - } - - // Assessing the type of the productHooks if it individually implements UserHasJoinedChannel interface. - tt = reflect.TypeOf((*UserHasJoinedChannelIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserHasJoinedChannelID] = struct{}{} - } else if _, ok := ft.MethodByName("UserHasJoinedChannel"); ok { - return nil, errors.New("hook has UserHasJoinedChannel method but does not implement plugin.UserHasJoinedChannel interface") - } - - // Assessing the type of the productHooks if it individually implements UserHasLeftChannel interface. - tt = reflect.TypeOf((*UserHasLeftChannelIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserHasLeftChannelID] = struct{}{} - } else if _, ok := ft.MethodByName("UserHasLeftChannel"); ok { - return nil, errors.New("hook has UserHasLeftChannel method but does not implement plugin.UserHasLeftChannel interface") - } - - // Assessing the type of the productHooks if it individually implements UserHasJoinedTeam interface. - tt = reflect.TypeOf((*UserHasJoinedTeamIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserHasJoinedTeamID] = struct{}{} - } else if _, ok := ft.MethodByName("UserHasJoinedTeam"); ok { - return nil, errors.New("hook has UserHasJoinedTeam method but does not implement plugin.UserHasJoinedTeam interface") - } - - // Assessing the type of the productHooks if it individually implements UserHasLeftTeam interface. - tt = reflect.TypeOf((*UserHasLeftTeamIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserHasLeftTeamID] = struct{}{} - } else if _, ok := ft.MethodByName("UserHasLeftTeam"); ok { - return nil, errors.New("hook has UserHasLeftTeam method but does not implement plugin.UserHasLeftTeam interface") - } - - // Assessing the type of the productHooks if it individually implements FileWillBeUploaded interface. - tt = reflect.TypeOf((*FileWillBeUploadedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[FileWillBeUploadedID] = struct{}{} - } else if _, ok := ft.MethodByName("FileWillBeUploaded"); ok { - return nil, errors.New("hook has FileWillBeUploaded method but does not implement plugin.FileWillBeUploaded interface") - } - - // Assessing the type of the productHooks if it individually implements ReactionHasBeenAdded interface. - tt = reflect.TypeOf((*ReactionHasBeenAddedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[ReactionHasBeenAddedID] = struct{}{} - } else if _, ok := ft.MethodByName("ReactionHasBeenAdded"); ok { - return nil, errors.New("hook has ReactionHasBeenAdded method but does not implement plugin.ReactionHasBeenAdded interface") - } - - // Assessing the type of the productHooks if it individually implements ReactionHasBeenRemoved interface. - tt = reflect.TypeOf((*ReactionHasBeenRemovedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[ReactionHasBeenRemovedID] = struct{}{} - } else if _, ok := ft.MethodByName("ReactionHasBeenRemoved"); ok { - return nil, errors.New("hook has ReactionHasBeenRemoved method but does not implement plugin.ReactionHasBeenRemoved interface") - } - - // Assessing the type of the productHooks if it individually implements OnPluginClusterEvent interface. - tt = reflect.TypeOf((*OnPluginClusterEventIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnPluginClusterEventID] = struct{}{} - } else if _, ok := ft.MethodByName("OnPluginClusterEvent"); ok { - return nil, errors.New("hook has OnPluginClusterEvent method but does not implement plugin.OnPluginClusterEvent interface") - } - - // Assessing the type of the productHooks if it individually implements OnWebSocketConnect interface. - tt = reflect.TypeOf((*OnWebSocketConnectIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnWebSocketConnectID] = struct{}{} - } else if _, ok := ft.MethodByName("OnWebSocketConnect"); ok { - return nil, errors.New("hook has OnWebSocketConnect method but does not implement plugin.OnWebSocketConnect interface") - } - - // Assessing the type of the productHooks if it individually implements OnWebSocketDisconnect interface. - tt = reflect.TypeOf((*OnWebSocketDisconnectIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnWebSocketDisconnectID] = struct{}{} - } else if _, ok := ft.MethodByName("OnWebSocketDisconnect"); ok { - return nil, errors.New("hook has OnWebSocketDisconnect method but does not implement plugin.OnWebSocketDisconnect interface") - } - - // Assessing the type of the productHooks if it individually implements WebSocketMessageHasBeenPosted interface. - tt = reflect.TypeOf((*WebSocketMessageHasBeenPostedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[WebSocketMessageHasBeenPostedID] = struct{}{} - } else if _, ok := ft.MethodByName("WebSocketMessageHasBeenPosted"); ok { - return nil, errors.New("hook has WebSocketMessageHasBeenPosted method but does not implement plugin.WebSocketMessageHasBeenPosted interface") - } - - // Assessing the type of the productHooks if it individually implements RunDataRetention interface. - tt = reflect.TypeOf((*RunDataRetentionIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[RunDataRetentionID] = struct{}{} - } else if _, ok := ft.MethodByName("RunDataRetention"); ok { - return nil, errors.New("hook has RunDataRetention method but does not implement plugin.RunDataRetention interface") - } - - // Assessing the type of the productHooks if it individually implements OnInstall interface. - tt = reflect.TypeOf((*OnInstallIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnInstallID] = struct{}{} - } else if _, ok := ft.MethodByName("OnInstall"); ok { - return nil, errors.New("hook has OnInstall method but does not implement plugin.OnInstall interface") - } - - // Assessing the type of the productHooks if it individually implements OnSendDailyTelemetry interface. - tt = reflect.TypeOf((*OnSendDailyTelemetryIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnSendDailyTelemetryID] = struct{}{} - } else if _, ok := ft.MethodByName("OnSendDailyTelemetry"); ok { - return nil, errors.New("hook has OnSendDailyTelemetry method but does not implement plugin.OnSendDailyTelemetry interface") - } - - // Assessing the type of the productHooks if it individually implements OnCloudLimitsUpdated interface. - tt = reflect.TypeOf((*OnCloudLimitsUpdatedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnCloudLimitsUpdatedID] = struct{}{} - } else if _, ok := ft.MethodByName("OnCloudLimitsUpdated"); ok { - return nil, errors.New("hook has OnCloudLimitsUpdated method but does not implement plugin.OnCloudLimitsUpdated interface") - } - - // Assessing the type of the productHooks if it individually implements ConfigurationWillBeSaved interface. - tt = reflect.TypeOf((*ConfigurationWillBeSavedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[ConfigurationWillBeSavedID] = struct{}{} - } else if _, ok := ft.MethodByName("ConfigurationWillBeSaved"); ok { - return nil, errors.New("hook has ConfigurationWillBeSaved method but does not implement plugin.ConfigurationWillBeSaved interface") - } - - // Assessing the type of the productHooks if it individually implements NotificationWillBePushed interface. - tt = reflect.TypeOf((*NotificationWillBePushedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[NotificationWillBePushedID] = struct{}{} - } else if _, ok := ft.MethodByName("NotificationWillBePushed"); ok { - return nil, errors.New("hook has NotificationWillBePushed method but does not implement plugin.NotificationWillBePushed interface") - } - - // Assessing the type of the productHooks if it individually implements UserHasBeenDeactivated interface. - tt = reflect.TypeOf((*UserHasBeenDeactivatedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[UserHasBeenDeactivatedID] = struct{}{} - } else if _, ok := ft.MethodByName("UserHasBeenDeactivated"); ok { - return nil, errors.New("hook has UserHasBeenDeactivated method but does not implement plugin.UserHasBeenDeactivated interface") - } - - // Assessing the type of the productHooks if it individually implements ServeMetrics interface. - tt = reflect.TypeOf((*ServeMetricsIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[ServeMetricsID] = struct{}{} - } else if _, ok := ft.MethodByName("ServeMetrics"); ok { - return nil, errors.New("hook has ServeMetrics method but does not implement plugin.ServeMetrics interface") - } - - // Assessing the type of the productHooks if it individually implements OnSharedChannelsSyncMsg interface. - tt = reflect.TypeOf((*OnSharedChannelsSyncMsgIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnSharedChannelsSyncMsgID] = struct{}{} - } else if _, ok := ft.MethodByName("OnSharedChannelsSyncMsg"); ok { - return nil, errors.New("hook has OnSharedChannelsSyncMsg method but does not implement plugin.OnSharedChannelsSyncMsg interface") - } - - // Assessing the type of the productHooks if it individually implements OnSharedChannelsPing interface. - tt = reflect.TypeOf((*OnSharedChannelsPingIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnSharedChannelsPingID] = struct{}{} - } else if _, ok := ft.MethodByName("OnSharedChannelsPing"); ok { - return nil, errors.New("hook has OnSharedChannelsPing method but does not implement plugin.OnSharedChannelsPing interface") - } - - // Assessing the type of the productHooks if it individually implements PreferencesHaveChanged interface. - tt = reflect.TypeOf((*PreferencesHaveChangedIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[PreferencesHaveChangedID] = struct{}{} - } else if _, ok := ft.MethodByName("PreferencesHaveChanged"); ok { - return nil, errors.New("hook has PreferencesHaveChanged method but does not implement plugin.PreferencesHaveChanged interface") - } - - // Assessing the type of the productHooks if it individually implements OnSharedChannelsAttachmentSyncMsg interface. - tt = reflect.TypeOf((*OnSharedChannelsAttachmentSyncMsgIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnSharedChannelsAttachmentSyncMsgID] = struct{}{} - } else if _, ok := ft.MethodByName("OnSharedChannelsAttachmentSyncMsg"); ok { - return nil, errors.New("hook has OnSharedChannelsAttachmentSyncMsg method but does not implement plugin.OnSharedChannelsAttachmentSyncMsg interface") - } - - // Assessing the type of the productHooks if it individually implements OnSharedChannelsProfileImageSyncMsg interface. - tt = reflect.TypeOf((*OnSharedChannelsProfileImageSyncMsgIFace)(nil)).Elem() - - if ft.Implements(tt) { - a.implemented[OnSharedChannelsProfileImageSyncMsgID] = struct{}{} - } else if _, ok := ft.MethodByName("OnSharedChannelsProfileImageSyncMsg"); ok { - return nil, errors.New("hook has OnSharedChannelsProfileImageSyncMsg method but does not implement plugin.OnSharedChannelsProfileImageSyncMsg interface") - } - - return a, nil -} - -func (a *HooksAdapter) OnConfigurationChange() error { - if _, ok := a.implemented[OnConfigurationChangeID]; !ok { - panic("product hooks must implement OnConfigurationChange") - } - - return a.productHooks.(OnConfigurationChangeIFace).OnConfigurationChange() - -} - -func (a *HooksAdapter) ExecuteCommand(c *Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { - if _, ok := a.implemented[ExecuteCommandID]; !ok { - panic("product hooks must implement ExecuteCommand") - } - - return a.productHooks.(ExecuteCommandIFace).ExecuteCommand(c, args) - -} - -func (a *HooksAdapter) UserHasBeenCreated(c *Context, user *model.User) { - if _, ok := a.implemented[UserHasBeenCreatedID]; !ok { - panic("product hooks must implement UserHasBeenCreated") - } - - a.productHooks.(UserHasBeenCreatedIFace).UserHasBeenCreated(c, user) - -} - -func (a *HooksAdapter) UserWillLogIn(c *Context, user *model.User) string { - if _, ok := a.implemented[UserWillLogInID]; !ok { - panic("product hooks must implement UserWillLogIn") - } - - return a.productHooks.(UserWillLogInIFace).UserWillLogIn(c, user) - -} - -func (a *HooksAdapter) UserHasLoggedIn(c *Context, user *model.User) { - if _, ok := a.implemented[UserHasLoggedInID]; !ok { - panic("product hooks must implement UserHasLoggedIn") - } - - a.productHooks.(UserHasLoggedInIFace).UserHasLoggedIn(c, user) - -} - -func (a *HooksAdapter) MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string) { - if _, ok := a.implemented[MessageWillBePostedID]; !ok { - panic("product hooks must implement MessageWillBePosted") - } - - return a.productHooks.(MessageWillBePostedIFace).MessageWillBePosted(c, post) - -} - -func (a *HooksAdapter) MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string) { - if _, ok := a.implemented[MessageWillBeUpdatedID]; !ok { - panic("product hooks must implement MessageWillBeUpdated") - } - - return a.productHooks.(MessageWillBeUpdatedIFace).MessageWillBeUpdated(c, newPost, oldPost) - -} - -func (a *HooksAdapter) MessageHasBeenPosted(c *Context, post *model.Post) { - if _, ok := a.implemented[MessageHasBeenPostedID]; !ok { - panic("product hooks must implement MessageHasBeenPosted") - } - - a.productHooks.(MessageHasBeenPostedIFace).MessageHasBeenPosted(c, post) - -} - -func (a *HooksAdapter) MessageHasBeenUpdated(c *Context, newPost, oldPost *model.Post) { - if _, ok := a.implemented[MessageHasBeenUpdatedID]; !ok { - panic("product hooks must implement MessageHasBeenUpdated") - } - - a.productHooks.(MessageHasBeenUpdatedIFace).MessageHasBeenUpdated(c, newPost, oldPost) - -} - -func (a *HooksAdapter) MessagesWillBeConsumed(posts []*model.Post) []*model.Post { - if _, ok := a.implemented[MessagesWillBeConsumedID]; !ok { - panic("product hooks must implement MessagesWillBeConsumed") - } - - return a.productHooks.(MessagesWillBeConsumedIFace).MessagesWillBeConsumed(posts) - -} - -func (a *HooksAdapter) MessageHasBeenDeleted(c *Context, post *model.Post) { - if _, ok := a.implemented[MessageHasBeenDeletedID]; !ok { - panic("product hooks must implement MessageHasBeenDeleted") - } - - a.productHooks.(MessageHasBeenDeletedIFace).MessageHasBeenDeleted(c, post) - -} - -func (a *HooksAdapter) ChannelHasBeenCreated(c *Context, channel *model.Channel) { - if _, ok := a.implemented[ChannelHasBeenCreatedID]; !ok { - panic("product hooks must implement ChannelHasBeenCreated") - } - - a.productHooks.(ChannelHasBeenCreatedIFace).ChannelHasBeenCreated(c, channel) - -} - -func (a *HooksAdapter) UserHasJoinedChannel(c *Context, channelMember *model.ChannelMember, actor *model.User) { - if _, ok := a.implemented[UserHasJoinedChannelID]; !ok { - panic("product hooks must implement UserHasJoinedChannel") - } - - a.productHooks.(UserHasJoinedChannelIFace).UserHasJoinedChannel(c, channelMember, actor) - -} - -func (a *HooksAdapter) UserHasLeftChannel(c *Context, channelMember *model.ChannelMember, actor *model.User) { - if _, ok := a.implemented[UserHasLeftChannelID]; !ok { - panic("product hooks must implement UserHasLeftChannel") - } - - a.productHooks.(UserHasLeftChannelIFace).UserHasLeftChannel(c, channelMember, actor) - -} - -func (a *HooksAdapter) UserHasJoinedTeam(c *Context, teamMember *model.TeamMember, actor *model.User) { - if _, ok := a.implemented[UserHasJoinedTeamID]; !ok { - panic("product hooks must implement UserHasJoinedTeam") - } - - a.productHooks.(UserHasJoinedTeamIFace).UserHasJoinedTeam(c, teamMember, actor) - -} - -func (a *HooksAdapter) UserHasLeftTeam(c *Context, teamMember *model.TeamMember, actor *model.User) { - if _, ok := a.implemented[UserHasLeftTeamID]; !ok { - panic("product hooks must implement UserHasLeftTeam") - } - - a.productHooks.(UserHasLeftTeamIFace).UserHasLeftTeam(c, teamMember, actor) - -} - -func (a *HooksAdapter) FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { - if _, ok := a.implemented[FileWillBeUploadedID]; !ok { - panic("product hooks must implement FileWillBeUploaded") - } - - return a.productHooks.(FileWillBeUploadedIFace).FileWillBeUploaded(c, info, file, output) - -} - -func (a *HooksAdapter) ReactionHasBeenAdded(c *Context, reaction *model.Reaction) { - if _, ok := a.implemented[ReactionHasBeenAddedID]; !ok { - panic("product hooks must implement ReactionHasBeenAdded") - } - - a.productHooks.(ReactionHasBeenAddedIFace).ReactionHasBeenAdded(c, reaction) - -} - -func (a *HooksAdapter) ReactionHasBeenRemoved(c *Context, reaction *model.Reaction) { - if _, ok := a.implemented[ReactionHasBeenRemovedID]; !ok { - panic("product hooks must implement ReactionHasBeenRemoved") - } - - a.productHooks.(ReactionHasBeenRemovedIFace).ReactionHasBeenRemoved(c, reaction) - -} - -func (a *HooksAdapter) OnPluginClusterEvent(c *Context, ev model.PluginClusterEvent) { - if _, ok := a.implemented[OnPluginClusterEventID]; !ok { - panic("product hooks must implement OnPluginClusterEvent") - } - - a.productHooks.(OnPluginClusterEventIFace).OnPluginClusterEvent(c, ev) - -} - -func (a *HooksAdapter) OnWebSocketConnect(webConnID, userID string) { - if _, ok := a.implemented[OnWebSocketConnectID]; !ok { - panic("product hooks must implement OnWebSocketConnect") - } - - a.productHooks.(OnWebSocketConnectIFace).OnWebSocketConnect(webConnID, userID) - -} - -func (a *HooksAdapter) OnWebSocketDisconnect(webConnID, userID string) { - if _, ok := a.implemented[OnWebSocketDisconnectID]; !ok { - panic("product hooks must implement OnWebSocketDisconnect") - } - - a.productHooks.(OnWebSocketDisconnectIFace).OnWebSocketDisconnect(webConnID, userID) - -} - -func (a *HooksAdapter) WebSocketMessageHasBeenPosted(webConnID, userID string, req *model.WebSocketRequest) { - if _, ok := a.implemented[WebSocketMessageHasBeenPostedID]; !ok { - panic("product hooks must implement WebSocketMessageHasBeenPosted") - } - - a.productHooks.(WebSocketMessageHasBeenPostedIFace).WebSocketMessageHasBeenPosted(webConnID, userID, req) - -} - -func (a *HooksAdapter) RunDataRetention(nowTime, batchSize int64) (int64, error) { - if _, ok := a.implemented[RunDataRetentionID]; !ok { - panic("product hooks must implement RunDataRetention") - } - - return a.productHooks.(RunDataRetentionIFace).RunDataRetention(nowTime, batchSize) - -} - -func (a *HooksAdapter) OnInstall(c *Context, event model.OnInstallEvent) error { - if _, ok := a.implemented[OnInstallID]; !ok { - panic("product hooks must implement OnInstall") - } - - return a.productHooks.(OnInstallIFace).OnInstall(c, event) - -} - -func (a *HooksAdapter) OnSendDailyTelemetry() { - if _, ok := a.implemented[OnSendDailyTelemetryID]; !ok { - panic("product hooks must implement OnSendDailyTelemetry") - } - - a.productHooks.(OnSendDailyTelemetryIFace).OnSendDailyTelemetry() - -} - -func (a *HooksAdapter) OnCloudLimitsUpdated(limits *model.ProductLimits) { - if _, ok := a.implemented[OnCloudLimitsUpdatedID]; !ok { - panic("product hooks must implement OnCloudLimitsUpdated") - } - - a.productHooks.(OnCloudLimitsUpdatedIFace).OnCloudLimitsUpdated(limits) - -} - -func (a *HooksAdapter) ConfigurationWillBeSaved(newCfg *model.Config) (*model.Config, error) { - if _, ok := a.implemented[ConfigurationWillBeSavedID]; !ok { - panic("product hooks must implement ConfigurationWillBeSaved") - } - - return a.productHooks.(ConfigurationWillBeSavedIFace).ConfigurationWillBeSaved(newCfg) - -} - -func (a *HooksAdapter) NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string) { - if _, ok := a.implemented[NotificationWillBePushedID]; !ok { - panic("product hooks must implement NotificationWillBePushed") - } - - return a.productHooks.(NotificationWillBePushedIFace).NotificationWillBePushed(pushNotification, userID) - -} - -func (a *HooksAdapter) UserHasBeenDeactivated(c *Context, user *model.User) { - if _, ok := a.implemented[UserHasBeenDeactivatedID]; !ok { - panic("product hooks must implement UserHasBeenDeactivated") - } - - a.productHooks.(UserHasBeenDeactivatedIFace).UserHasBeenDeactivated(c, user) - -} - -func (a *HooksAdapter) ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request) { - if _, ok := a.implemented[ServeMetricsID]; !ok { - panic("product hooks must implement ServeMetrics") - } - - a.productHooks.(ServeMetricsIFace).ServeMetrics(c, w, r) - -} - -func (a *HooksAdapter) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error) { - if _, ok := a.implemented[OnSharedChannelsSyncMsgID]; !ok { - panic("product hooks must implement OnSharedChannelsSyncMsg") - } - - return a.productHooks.(OnSharedChannelsSyncMsgIFace).OnSharedChannelsSyncMsg(msg, rc) - -} - -func (a *HooksAdapter) OnSharedChannelsPing(rc *model.RemoteCluster) bool { - if _, ok := a.implemented[OnSharedChannelsPingID]; !ok { - panic("product hooks must implement OnSharedChannelsPing") - } - - return a.productHooks.(OnSharedChannelsPingIFace).OnSharedChannelsPing(rc) - -} - -func (a *HooksAdapter) PreferencesHaveChanged(c *Context, preferences []model.Preference) { - if _, ok := a.implemented[PreferencesHaveChangedID]; !ok { - panic("product hooks must implement PreferencesHaveChanged") - } - - a.productHooks.(PreferencesHaveChangedIFace).PreferencesHaveChanged(c, preferences) - -} - -func (a *HooksAdapter) OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error { - if _, ok := a.implemented[OnSharedChannelsAttachmentSyncMsgID]; !ok { - panic("product hooks must implement OnSharedChannelsAttachmentSyncMsg") - } - - return a.productHooks.(OnSharedChannelsAttachmentSyncMsgIFace).OnSharedChannelsAttachmentSyncMsg(fi, post, rc) - -} - -func (a *HooksAdapter) OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error { - if _, ok := a.implemented[OnSharedChannelsProfileImageSyncMsgID]; !ok { - panic("product hooks must implement OnSharedChannelsProfileImageSyncMsg") - } - - return a.productHooks.(OnSharedChannelsProfileImageSyncMsgIFace).OnSharedChannelsProfileImageSyncMsg(user, rc) - -} diff --git a/server/public/pluginapi/bot.go b/server/public/pluginapi/bot.go index 74a7710b74..100c83de8a 100644 --- a/server/public/pluginapi/bot.go +++ b/server/public/pluginapi/bot.go @@ -143,8 +143,6 @@ type mutex interface { Unlock() } -// TODO: this utility function is also used by the product framework. We should move this to mattermost-server and share -// the code to maintain consistent behavior. Ticket: MM-44953 func (b *BotService) ensureBot(m mutex, bot *model.Bot, options ...EnsureBotOption) (string, error) { err := ensureServerVersion(b.api, "5.10.0") if err != nil {