[MM-53156] Remove Multi-Product architecture (#25669)

Этот коммит содержится в:
Ben Schumacher
2024-02-15 13:01:44 +01:00
коммит произвёл GitHub
родитель d3b799eaa5
Коммит de3e5aab25
42 изменённых файлов: 86 добавлений и 2240 удалений

Просмотреть файл

@@ -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()
}

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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 <username> --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)
}

Просмотреть файл

@@ -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):

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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,

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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() {

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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))

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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,
}

Просмотреть файл

@@ -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()
}

Просмотреть файл

@@ -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.
//

Просмотреть файл

@@ -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,

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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)
})
}

Просмотреть файл

@@ -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()

Просмотреть файл

@@ -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()

Просмотреть файл

@@ -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()