[MM-53156] Remove Multi-Product architecture (#25669)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
d3b799eaa5
Коммит
de3e5aab25
@@ -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()
|
||||
|
||||
Ссылка в новой задаче
Block a user