diff --git a/.circleci/config.yml b/.circleci/config.yml index 903d44dfc3..5063a7b3f3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: setup: working_directory: ~/mattermost/mattermost-server docker: - - image: mattermost/mattermost-build-webapp:20210524_node-16 + - image: mattermost/mattermost-build-webapp:20220802_node-16.10.0@sha256:3272aa759f10c2ef1719ed08cc82ddb07224bec5be86f09800c72f5e2a623c3d resource_class: xlarge # Use `--retry-all-errors` instead of `until` after curl version >= 7.71.0; `retry` will not work, since it only retries on transient errors, 403 is not one of them. steps: diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 9168983bbf..f2577e6ab9 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -117,7 +117,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent if includeCache { // Adds the cache layer to the test store options = append(options, app.StoreOverride(func(s *app.Server) store.Store { - lcl, err2 := localcachelayer.NewLocalCacheLayer(dbStore, s.Metrics, s.Cluster, s.CacheProvider) + lcl, err2 := localcachelayer.NewLocalCacheLayer(dbStore, s.GetMetrics(), s.Cluster, s.CacheProvider) if err2 != nil { panic(err2) } @@ -218,7 +218,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent return th } -func SetupEnterprise(tb testing.TB) *TestHelper { +func SetupEnterprise(tb testing.TB, options ...app.Option) *TestHelper { if testing.Short() { tb.SkipNow() } @@ -232,7 +232,7 @@ func SetupEnterprise(tb testing.TB) *TestHelper { dbStore.MarkSystemRanUnitTests() mainHelper.PreloadMigrations() searchEngine := mainHelper.GetSearchEngine() - th := setupTestHelper(dbStore, searchEngine, true, true, nil, nil) + th := setupTestHelper(dbStore, searchEngine, true, true, nil, options) th.InitLogin() return th } @@ -322,8 +322,8 @@ func SetupWithStoreMock(tb testing.TB) *TestHelper { return th } -func SetupEnterpriseWithStoreMock(tb testing.TB) *TestHelper { - th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, true, false, nil, nil) +func SetupEnterpriseWithStoreMock(tb testing.TB, options ...app.Option) *TestHelper { + th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, true, false, nil, options) statusMock := mocks.StatusStore{} statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil) statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil) diff --git a/api4/bot.go b/api4/bot.go index 569d51b793..62c2bc38cf 100644 --- a/api4/bot.go +++ b/api4/bot.go @@ -187,7 +187,7 @@ func getBots(c *Context, w http.ResponseWriter, r *http.Request) { } if err := json.NewEncoder(w).Encode(bots); err != nil { - mlog.Warn("Error while writing response", mlog.Err(err)) + c.Logger.Warn("Error while writing response", mlog.Err(err)) } } @@ -227,7 +227,7 @@ func updateBotActive(c *Context, w http.ResponseWriter, active bool) { auditRec.AddEventObjectType("bot") if err := json.NewEncoder(w).Encode(bot); err != nil { - mlog.Warn("Error while writing response", mlog.Err(err)) + c.Logger.Warn("Error while writing response", mlog.Err(err)) } } diff --git a/api4/license_test.go b/api4/license_test.go index 919d04156c..af3b6fb8df 100644 --- a/api4/license_test.go +++ b/api4/license_test.go @@ -212,14 +212,6 @@ func TestRequestTrialLicense(t *testing.T) { CheckForbiddenStatus(t, resp) }) - t.Run("blank site url", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "" }) - defer th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065/" }) - resp, err := th.SystemAdminClient.RequestTrialLicense(1000) - CheckErrorID(t, err, "api.license.request_trial_license.no-site-url.app_error") - CheckBadRequestStatus(t, resp) - }) - t.Run("trial license user count less than current users", func(t *testing.T) { nUsers := 1 license := model.NewTestLicense() diff --git a/api4/user.go b/api4/user.go index 56323550f4..c1d682e560 100644 --- a/api4/user.go +++ b/api4/user.go @@ -3172,11 +3172,6 @@ func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.ThreadId, model.PermissionReadChannel) { - c.SetPermissionError(model.PermissionReadChannel) - return - } - err := c.App.UpdateThreadFollowForUser(c.Params.UserId, c.Params.TeamId, c.Params.ThreadId, false) if err != nil { c.Err = err diff --git a/app/app.go b/app/app.go index ac7cf62dd1..4ac48823f5 100644 --- a/app/app.go +++ b/app/app.go @@ -117,7 +117,7 @@ func (a *App) MessageExport() einterfaces.MessageExportInterface { return a.ch.MessageExport } func (a *App) Metrics() einterfaces.MetricsInterface { - return a.ch.srv.Metrics + return a.ch.srv.GetMetrics() } func (a *App) Notification() einterfaces.NotificationInterface { return a.ch.Notification diff --git a/app/app_iface.go b/app/app_iface.go index d935543640..3d34c490b8 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -37,12 +37,12 @@ import ( // AppIface is extracted from App struct and contains all it's exported methods. It's provided to allow partial interface passing and app layers creation. type AppIface interface { // @openTracingParams args - ExecuteCommand(c *request.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) + ExecuteCommand(c request.CTX, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) // @openTracingParams teamID // previous ListCommands now ListAutocompleteCommands ListAutocompleteCommands(teamID string, T i18n.TranslateFunc) ([]*model.Command, *model.AppError) // @openTracingParams teamID, skipSlackParsing - CreateCommandPost(c *request.Context, post *model.Post, teamID string, response *model.CommandResponse, skipSlackParsing bool) (*model.Post, *model.AppError) + CreateCommandPost(c request.CTX, post *model.Post, teamID string, response *model.CommandResponse, skipSlackParsing bool) (*model.Post, *model.AppError) // AddChannelMember adds a user to a channel. It is a wrapper over AddUserToChannel. AddChannelMember(c request.CTX, userID string, channel *model.Channel, opts ChannelMemberOpts) (*model.ChannelMember, *model.AppError) // AddCursorIdsForPostList adds NextPostId and PrevPostId as cursor to the PostList. @@ -84,7 +84,7 @@ type AppIface interface { // ConvertUserToBot converts a user to bot. ConvertUserToBot(user *model.User) (*model.Bot, *model.AppError) // CreateBot creates the given bot and corresponding user. - CreateBot(c *request.Context, bot *model.Bot) (*model.Bot, *model.AppError) + CreateBot(c request.CTX, bot *model.Bot) (*model.Bot, *model.AppError) // CreateChannelScheme creates a new Scheme of scope channel and assigns it to the channel. CreateChannelScheme(c request.CTX, channel *model.Channel) (*model.Scheme, *model.AppError) // CreateDefaultMemberships adds users to teams and channels based on their group memberships and how those groups @@ -132,7 +132,7 @@ type AppIface interface { // 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(c *request.Context, productID string, bot *model.Bot) (string, error) + EnsureBot(c request.CTX, productID 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 @@ -250,7 +250,7 @@ type AppIface interface { MentionsToPublicChannels(c request.CTX, message, teamID string) model.ChannelMentionMap // MentionsToTeamMembers returns all the @ mentions found in message that // belong to users in the specified team, linking them to their users - MentionsToTeamMembers(message, teamID string) model.UserMentionMap + MentionsToTeamMembers(c request.CTX, message, teamID string) model.UserMentionMap // MoveChannel method is prone to data races if someone joins to channel during the move process. However this // function is only exposed to sysadmins and the possibility of this edge case is relatively small. MoveChannel(c request.CTX, team *model.Team, channel *model.Channel, user *model.User) *model.AppError @@ -328,7 +328,7 @@ type AppIface interface { SyncPlugins() *model.AppError // SyncRolesAndMembership updates the SchemeAdmin status and membership of all of the members of the given // syncable. - SyncRolesAndMembership(c *request.Context, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) + SyncRolesAndMembership(c request.CTX, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) // SyncSyncableRoles updates the SchemeAdmin field value of the given syncable's members based on the configuration of // the member's group memberships and the configuration of those groups to the syncable. This method should only // be invoked on group-synced (aka group-constrained) syncables. @@ -354,7 +354,7 @@ type AppIface interface { // This to be used for places we check the users password when they are already logged in DoubleCheckPassword(user *model.User, password string) *model.AppError // UpdateBotActive marks a bot as active or inactive, along with its corresponding user. - UpdateBotActive(c *request.Context, botUserId string, active bool) (*model.Bot, *model.AppError) + UpdateBotActive(c request.CTX, botUserId string, active bool) (*model.Bot, *model.AppError) // UpdateBotOwner changes a bot's owner to the given value. UpdateBotOwner(botUserId, newOwnerId string) (*model.Bot, *model.AppError) // UpdateChannel updates a given channel by its Id. It also publishes the CHANNEL_UPDATED event. @@ -374,7 +374,7 @@ type AppIface interface { // UpdateWebConnUserActivity sets the LastUserActivityAt of the hub for the given session. UpdateWebConnUserActivity(session model.Session, activityAt int64) // UploadFile uploads a single file in form of a completely constructed byte array for a channel. - UploadFile(c *request.Context, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) + UploadFile(c request.CTX, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) // UploadFileX uploads a single file as specified in t. It applies the upload // constraints, executes plugins and image processing logic as needed. It // returns a filled-out FileInfo and an optional error. A plugin may reject the @@ -402,12 +402,12 @@ type AppIface interface { AddSessionToCache(session *model.Session) AddStatusCache(status *model.Status) AddStatusCacheSkipClusterSend(status *model.Status) - AddTeamMember(c *request.Context, teamID, userID string) (*model.TeamMember, *model.AppError) + AddTeamMember(c request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) AddTeamMemberByInviteId(c *request.Context, inviteId, userID string) (*model.TeamMember, *model.AppError) AddTeamMemberByToken(c *request.Context, userID, tokenID string) (*model.TeamMember, *model.AppError) AddTeamMembers(c *request.Context, teamID string, userIDs []string, userRequestorId string, graceful bool) ([]*model.TeamMemberWithError, *model.AppError) AddTeamsToRetentionPolicy(policyID string, teamIDs []string) *model.AppError - AddUserToTeam(c *request.Context, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) + AddUserToTeam(c request.CTX, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) AddUserToTeamByInviteId(c *request.Context, inviteId string, userID string) (*model.Team, *model.TeamMember, *model.AppError) AddUserToTeamByTeamId(c *request.Context, teamID string, user *model.User) *model.AppError AddUserToTeamByToken(c *request.Context, userID string, tokenID string) (*model.Team, *model.TeamMember, *model.AppError) @@ -428,10 +428,10 @@ type AppIface interface { AutocompleteUsersInChannel(teamID string, channelID string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) AutocompleteUsersInTeam(teamID string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInTeam, *model.AppError) BroadcastStatus(status *model.Status) - BuildPostReactions(postID string) (*[]ReactionImportData, *model.AppError) + BuildPostReactions(ctx request.CTX, postID string) (*[]ReactionImportData, *model.AppError) BuildPushNotificationMessage(c request.CTX, contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError) BuildSamlMetadataObject(idpMetadata []byte) (*model.SamlMetadataResponse, *model.AppError) - BulkExport(writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError + BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError BulkImport(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int) (*model.AppError, int) BulkImportWithPath(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) CancelJob(jobId string) *model.AppError @@ -449,13 +449,13 @@ type AppIface interface { CheckUserPostflightAuthenticationCriteria(user *model.User) *model.AppError CheckUserPreflightAuthenticationCriteria(user *model.User, mfaToken string) *model.AppError CheckWebConn(userID, connectionID string) *CheckConnResult - ClearChannelMembersCache(c request.CTX, channelID string) + ClearChannelMembersCache(c request.CTX, channelID string) error ClearLatestVersionCache() ClearSessionCacheForAllUsers() ClearSessionCacheForAllUsersSkipClusterSend() ClearSessionCacheForUser(userID string) ClearSessionCacheForUserSkipClusterSend(userID string) - ClearTeamMembersCache(teamID string) + ClearTeamMembersCache(teamID string) error ClientConfig() map[string]string ClientConfigHash() string Cloud() einterfaces.CloudInterface @@ -484,7 +484,7 @@ type AppIface interface { CreateOutgoingWebhook(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError) CreatePasswordRecoveryToken(userID, email string) (*model.Token, *model.AppError) CreatePost(c request.CTX, post *model.Post, channel *model.Channel, triggerWebhooks, setOnline bool) (savedPost *model.Post, err *model.AppError) - CreatePostAsUser(c *request.Context, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) + CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) CreatePostMissingChannel(c request.CTX, post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) CreateRole(role *model.Role) (*model.Role, *model.AppError) @@ -545,7 +545,7 @@ type AppIface interface { DoPostActionWithCookie(c *request.Context, postID, actionId, userID, selectedOption string, cookie *model.PostActionCookie) (string, *model.AppError) DoSystemConsoleRolesCreationMigration() DoUploadFile(c *request.Context, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) - DoUploadFileExpectModification(c *request.Context, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) + DoUploadFileExpectModification(c request.CTX, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) DownloadFromURL(downloadURL string) ([]byte, error) EnableUserAccessToken(token *model.UserAccessToken) *model.AppError EnvironmentConfig(filter func(reflect.StructField) bool) map[string]any @@ -837,8 +837,8 @@ type AppIface interface { GetWarnMetricsStatus() (map[string]*model.WarnMetricStatus, *model.AppError) HTTPService() httpservice.HTTPService Handle404(w http.ResponseWriter, r *http.Request) - HandleCommandResponse(c *request.Context, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) - HandleCommandResponsePost(c *request.Context, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError) + HandleCommandResponse(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) + HandleCommandResponsePost(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError) HandleCommandWebhook(c *request.Context, hookID string, response *model.CommandResponse) *model.AppError HandleImages(previewPathList []string, thumbnailPathList []string, fileData [][]byte) HandleIncomingWebhook(c *request.Context, hookID string, req *model.IncomingWebhookRequest) *model.AppError @@ -874,7 +874,7 @@ type AppIface interface { JoinUserToTeam(c request.CTX, team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) Ldap() einterfaces.LdapInterface LeaveChannel(c request.CTX, channelID string, userID string) *model.AppError - LeaveTeam(c *request.Context, team *model.Team, user *model.User, requestorId string) *model.AppError + LeaveTeam(c request.CTX, team *model.Team, user *model.User, requestorId string) *model.AppError License() *model.License LimitedClientConfig() map[string]string ListAllCommands(teamID string, T i18n.TranslateFunc) ([]*model.Command, *model.AppError) @@ -955,7 +955,7 @@ type AppIface interface { RemoveTeamIcon(teamID string) *model.AppError RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *model.AppError RemoveUserFromChannel(c request.CTX, userIDToRemove string, removerUserId string, channel *model.Channel) *model.AppError - RemoveUserFromTeam(c *request.Context, teamID string, userID string, requestorId string) *model.AppError + RemoveUserFromTeam(c request.CTX, teamID string, userID string, requestorId string) *model.AppError RemoveUsersFromChannelNotMemberOfTeam(c request.CTX, remover *model.User, channel *model.Channel, team *model.Team) *model.AppError RequestLicenseAndAckWarnMetric(c *request.Context, warnMetricId string, isBot bool) *model.AppError ResetPasswordFromToken(c request.CTX, userSuppliedTokenString, newPassword string) *model.AppError @@ -1077,7 +1077,7 @@ type AppIface interface { TotalWebsocketConnections() int TriggerWebhook(c request.CTX, payload *model.OutgoingWebhookPayload, hook *model.OutgoingWebhook, post *model.Post, channel *model.Channel) UnregisterPluginCommand(pluginID, teamID, trigger string) - UpdateActive(c *request.Context, user *model.User, active bool) (*model.User, *model.AppError) + UpdateActive(c request.CTX, user *model.User, active bool) (*model.User, *model.AppError) UpdateChannelMemberNotifyProps(c request.CTX, data map[string]string, channelID string, userID string) (*model.ChannelMember, *model.AppError) UpdateChannelMemberRoles(c request.CTX, channelID string, userID string, newRoles string) (*model.ChannelMember, *model.AppError) UpdateChannelMemberSchemeRoles(c request.CTX, channelID string, userID string, isSchemeGuest bool, isSchemeUser bool, isSchemeAdmin bool) (*model.ChannelMember, *model.AppError) @@ -1122,7 +1122,7 @@ type AppIface interface { UpdateThreadReadForUserByPost(c request.CTX, currentSessionId, userID, teamID, threadID, postID string) (*model.ThreadResponse, *model.AppError) UpdateThreadsReadForUser(userID, teamID string) *model.AppError UpdateUser(c request.CTX, user *model.User, sendNotifications bool) (*model.User, *model.AppError) - UpdateUserActive(c *request.Context, userID string, active bool) *model.AppError + UpdateUserActive(c request.CTX, userID string, active bool) *model.AppError UpdateUserAsUser(c request.CTX, user *model.User, asAdmin bool) (*model.User, *model.AppError) UpdateUserAuth(userID string, userAuth *model.UserAuth) (*model.UserAuth, *model.AppError) UpdateUserRoles(c request.CTX, userID string, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError) diff --git a/app/bot.go b/app/bot.go index 5ec00cb093..d998f7a684 100644 --- a/app/bot.go +++ b/app/bot.go @@ -38,7 +38,7 @@ func (w *botServiceWrapper) EnsureBot(c *request.Context, productID string, bot // 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(c *request.Context, productID string, bot *model.Bot) (string, error) { +func (a *App) EnsureBot(c request.CTX, productID string, bot *model.Bot) (string, error) { if bot == nil { return "", errors.New("passed a nil bot") } @@ -77,7 +77,7 @@ func (a *App) EnsureBot(c *request.Context, productID string, bot *model.Bot) (s return "", fmt.Errorf("failed to set plugin key: %w", err) } } else { - a.Srv().Log.Error("Product attempted to use an account that already exists. Convert user to a bot "+ + c.Logger().Error("Product attempted to use an account that already exists. Convert user to a bot "+ "account in the CLI by running 'mattermost user convert --bot'. If the user is an "+ "existing user account you want to preserve, change its username and restart the Mattermost server, "+ "after which the plugin will create a bot account with that name. For more information about bot "+ @@ -103,7 +103,7 @@ func (a *App) EnsureBot(c *request.Context, productID string, bot *model.Bot) (s } // CreateBot creates the given bot and corresponding user. -func (a *App) CreateBot(c *request.Context, bot *model.Bot) (*model.Bot, *model.AppError) { +func (a *App) CreateBot(c request.CTX, bot *model.Bot) (*model.Bot, *model.AppError) { vErr := bot.IsValidCreate() if vErr != nil { return nil, vErr @@ -391,7 +391,7 @@ func (a *App) GetBots(options *model.BotGetOptions) (model.BotList, *model.AppEr } // UpdateBotActive marks a bot as active or inactive, along with its corresponding user. -func (a *App) UpdateBotActive(c *request.Context, botUserId string, active bool) (*model.Bot, *model.AppError) { +func (a *App) UpdateBotActive(c request.CTX, botUserId string, active bool) (*model.Bot, *model.AppError) { user, nErr := a.Srv().Store.User().Get(context.Background(), botUserId) if nErr != nil { var nfErr *store.ErrNotFound @@ -498,7 +498,7 @@ func (a *App) UpdateBotOwner(botUserId, newOwnerId string) (*model.Bot, *model.A } // disableUserBots disables all bots owned by the given user. -func (a *App) disableUserBots(c *request.Context, userID string) *model.AppError { +func (a *App) disableUserBots(c request.CTX, userID string) *model.AppError { perPage := 20 for { options := &model.BotGetOptions{ @@ -516,7 +516,7 @@ func (a *App) disableUserBots(c *request.Context, userID string) *model.AppError for _, bot := range userBots { _, err := a.UpdateBotActive(c, bot.UserId, false) if err != nil { - mlog.Warn("Unable to deactivate bot.", mlog.String("bot_user_id", bot.UserId), mlog.Err(err)) + c.Logger().Warn("Unable to deactivate bot.", mlog.String("bot_user_id", bot.UserId), mlog.Err(err)) } } @@ -531,7 +531,7 @@ func (a *App) disableUserBots(c *request.Context, userID string) *model.AppError return nil } -func (a *App) notifySysadminsBotOwnerDeactivated(c *request.Context, userID string) *model.AppError { +func (a *App) notifySysadminsBotOwnerDeactivated(c request.CTX, userID string) *model.AppError { perPage := 25 botOptions := &model.BotGetOptions{ OwnerId: userID, diff --git a/app/channel.go b/app/channel.go index 3021a42c8b..e5f0c7aefc 100644 --- a/app/channel.go +++ b/app/channel.go @@ -91,17 +91,10 @@ func (a *App) JoinDefaultChannels(c request.CTX, teamID string, user *model.User } } - var err *model.AppError for _, channelName := range a.DefaultChannelNames(c) { channel, channelErr := a.Srv().Store.Channel().GetByName(teamID, channelName, true) if channelErr != nil { - var nfErr *store.ErrNotFound - switch { - case errors.As(err, &nfErr): - err = model.NewAppError("JoinDefaultChannels", "app.channel.get_by_name.missing.app_error", nil, nfErr.Error(), http.StatusNotFound) - default: - err = model.NewAppError("JoinDefaultChannels", "app.channel.get_by_name.existing.app_error", nil, channelErr.Error(), http.StatusInternalServerError) - } + c.Logger().Warn("No default channel with this name", mlog.String("channelName", channelName), mlog.String("teamID", teamID), mlog.Err(channelErr)) continue } @@ -661,7 +654,7 @@ func (a *App) UpdateChannel(c request.CTX, channel *model.Channel) (*model.Chann messageWs := model.NewWebSocketEvent(model.WebsocketEventChannelUpdated, "", channel.Id, "", nil) channelJSON, jsonErr := json.Marshal(channel) if jsonErr != nil { - c.Logger().Warn("Failed to encode channel to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("UpdateChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } messageWs.Add("channel", string(channelJSON)) a.Publish(messageWs) @@ -1019,7 +1012,9 @@ func (a *App) PatchChannelModerationsForChannel(c request.CTX, channel *model.Ch if err != nil { return nil, err } - a.sendUpdatedRoleEvent(adminRole) + if appErr := a.sendUpdatedRoleEvent(adminRole); appErr != nil { + return nil, appErr + } message := model.NewWebSocketEvent(model.WebsocketEventChannelSchemeUpdated, "", channel.Id, "", nil) a.Publish(message) @@ -1285,7 +1280,7 @@ func (a *App) UpdateChannelMemberNotifyProps(c request.CTX, data map[string]stri evt := model.NewWebSocketEvent(model.WebsocketEventChannelMemberUpdated, "", "", member.UserId, nil) memberJSON, jsonErr := json.Marshal(member) if jsonErr != nil { - c.Logger().Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("UpdateChannelMemberNotifyProps", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } evt.Add("channelMember", string(memberJSON)) a.Publish(evt) @@ -1314,7 +1309,7 @@ func (a *App) updateChannelMember(c request.CTX, member *model.ChannelMember) (* evt := model.NewWebSocketEvent(model.WebsocketEventChannelMemberUpdated, "", "", member.UserId, nil) memberJSON, jsonErr := json.Marshal(member) if jsonErr != nil { - c.Logger().Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("updateChannelMember", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } evt.Add("channelMember", string(memberJSON)) a.Publish(evt) @@ -1587,7 +1582,7 @@ func (a *App) AddChannelMember(c request.CTX, userID string, channel *model.Chan if opts.UserRequestorID == "" || userID == opts.UserRequestorID { if err := a.postJoinChannelMessage(c, user, channel); err != nil { - mlog.Error("Failed to post join channel message", mlog.Err(err)) + return nil, err } } else { a.Srv().Go(func() { @@ -2513,11 +2508,9 @@ func (a *App) RemoveUserFromChannel(c request.CTX, userIDToRemove string, remove return err } } else { - a.Srv().Go(func() { - if err := a.postRemoveFromChannelMessage(c, removerUserId, user, channel); err != nil { - mlog.Error("Failed to post user removal message", mlog.Err(err)) - } - }) + if err := a.postRemoveFromChannelMessage(c, removerUserId, user, channel); err != nil { + c.Logger().Error("Failed to post user removal message", mlog.Err(err)) + } } return nil @@ -2701,7 +2694,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st if a.IsCRTEnabledForUser(c, userID) { payload, jsonErr := json.Marshal(thread) if jsonErr != nil { - c.Logger().Warn("Failed to encode thread to JSON") + return nil, model.NewAppError("MarkChannelAsUnreadFromPost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message := model.NewWebSocketEvent(model.WebsocketEventThreadUpdated, channel.TeamId, "", userID, nil) message.Add("thread", string(payload)) @@ -3268,7 +3261,7 @@ func (a *App) setChannelsMuted(c request.CTX, channelIDs []string, userID string memberJSON, jsonErr := json.Marshal(member) if jsonErr != nil { - c.Logger().Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("setChannelsMuted", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } evt.Add("channelMember", string(memberJSON)) @@ -3369,21 +3362,22 @@ func (a *App) forEachChannelMember(c request.CTX, channelID string, f func(model return nil } -func (a *App) ClearChannelMembersCache(c request.CTX, channelID string) { +func (a *App) ClearChannelMembersCache(c request.CTX, channelID string) error { clearSessionCache := func(channelMember model.ChannelMember) error { a.ClearSessionCacheForUser(channelMember.UserId) message := model.NewWebSocketEvent(model.WebsocketEventChannelMemberUpdated, "", "", channelMember.UserId, nil) memberJSON, jsonErr := json.Marshal(channelMember) if jsonErr != nil { - c.Logger().Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr)) + return jsonErr } message.Add("channelMember", string(memberJSON)) a.Publish(message) return nil } if err := a.forEachChannelMember(c, channelID, clearSessionCache); err != nil { - c.Logger().Warn("error clearing cache for channel members", mlog.String("channel_id", channelID)) + return fmt.Errorf("error clearing cache for channel members: channel_id: %s, error: %v", channelID, err) } + return nil } func (a *App) GetMemberCountsByGroup(ctx context.Context, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError) { diff --git a/app/channel_category.go b/app/channel_category.go index 4cba5b9b17..ff7ef067d8 100644 --- a/app/channel_category.go +++ b/app/channel_category.go @@ -151,7 +151,7 @@ func (a *App) UpdateSidebarCategories(c request.CTX, userID, teamID string, cate updatedCategoriesJSON, jsonErr := json.Marshal(updatedCategories) if jsonErr != nil { - c.Logger().Warn("Failed to encode original categories to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("UpdateSidebarCategories", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("updatedCategories", string(updatedCategoriesJSON)) diff --git a/app/channel_test.go b/app/channel_test.go index 50a6768ffe..459423ab29 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -347,6 +347,33 @@ func TestJoinDefaultChannelsExperimentalDefaultChannels(t *testing.T) { } } +func TestJoinDefaultChannelsExperimentalDefaultChannelsMissing(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + basicChannel2 := th.CreateChannel(th.Context, th.BasicTeam) + defer th.App.PermanentDeleteChannel(th.Context, basicChannel2) + defaultChannelList := []string{th.BasicChannel.Name, basicChannel2.Name, "thischanneldoesnotexist", basicChannel2.Name} + th.App.Config().TeamSettings.ExperimentalDefaultChannels = defaultChannelList + + user := th.CreateUser() + require.Nil(t, th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, "")) + + for _, channelName := range defaultChannelList { + if channelName == "thischanneldoesnotexist" { + continue // skip the non-existent channel + } + + channel, err := th.App.GetChannelByName(th.Context, channelName, th.BasicTeam.Id, false) + require.Nil(t, err, "Expected nil, didn't receive nil") + + member, err := th.App.GetChannelMember(th.Context, channel.Id, user.Id) + + require.NotNil(t, member, "Expected member object, got nil") + require.Nil(t, err, "Expected nil object, didn't receive nil") + } +} + func TestCreateChannelPublicCreatesChannelMemberHistoryRecord(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -2065,7 +2092,7 @@ func TestClearChannelMembersCache(t *testing.T) { mockStore.On("Channel").Return(&mockChannelStore) mockStore.On("GetDBSchemaVersion").Return(1, nil) - th.App.ClearChannelMembersCache(th.Context, "channelID") + require.NoError(t, th.App.ClearChannelMembersCache(th.Context, "channelID")) } func TestGetMemberCountsByGroup(t *testing.T) { diff --git a/app/command.go b/app/command.go index 038979752f..7d4ac80551 100644 --- a/app/command.go +++ b/app/command.go @@ -33,7 +33,7 @@ var atMentionRegexp = regexp.MustCompile(`\B@[[:alnum:]][[:alnum:]\.\-_:]*`) type CommandProvider interface { GetTrigger() string GetCommand(a *App, T i18n.TranslateFunc) *model.Command - DoCommand(a *App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse + DoCommand(a *App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse } var commandProviders = make(map[string]CommandProvider) @@ -52,7 +52,7 @@ func GetCommandProvider(name string) CommandProvider { } // @openTracingParams teamID, skipSlackParsing -func (a *App) CreateCommandPost(c *request.Context, post *model.Post, teamID string, response *model.CommandResponse, skipSlackParsing bool) (*model.Post, *model.AppError) { +func (a *App) CreateCommandPost(c request.CTX, post *model.Post, teamID string, response *model.CommandResponse, skipSlackParsing bool) (*model.Post, *model.AppError) { if skipSlackParsing { post.Message = response.Text } else { @@ -180,7 +180,7 @@ func (a *App) ListAllCommands(teamID string, T i18n.TranslateFunc) ([]*model.Com } // @openTracingParams args -func (a *App) ExecuteCommand(c *request.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { +func (a *App) ExecuteCommand(c request.CTX, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { trigger := "" message := "" index := strings.IndexFunc(args.Command, unicode.IsSpace) @@ -198,7 +198,7 @@ func (a *App) ExecuteCommand(c *request.Context, args *model.CommandArgs) (*mode clientTriggerId, triggerId, appErr := model.GenerateTriggerId(args.UserId, a.AsymmetricSigningKey()) if appErr != nil { - mlog.Warn("error occurred in generating trigger Id for a user ", mlog.Err(appErr)) + c.Logger().Warn("error occurred in generating trigger Id for a user ", mlog.Err(appErr)) } args.TriggerId = triggerId @@ -235,7 +235,7 @@ func (a *App) ExecuteCommand(c *request.Context, args *model.CommandArgs) (*mode // MentionsToTeamMembers returns all the @ mentions found in message that // belong to users in the specified team, linking them to their users -func (a *App) MentionsToTeamMembers(message, teamID string) model.UserMentionMap { +func (a *App) MentionsToTeamMembers(c request.CTX, message, teamID string) model.UserMentionMap { type mentionMapItem struct { Name string Id string @@ -253,7 +253,7 @@ func (a *App) MentionsToTeamMembers(message, teamID string) model.UserMentionMap var nfErr *store.ErrNotFound if nErr != nil && !errors.As(nErr, &nfErr) { - mlog.Warn("Failed to retrieve user @"+mention, mlog.Err(nErr)) + c.Logger().Warn("Failed to retrieve user @"+mention, mlog.Err(nErr)) return } @@ -347,7 +347,7 @@ func (a *App) MentionsToPublicChannels(c request.CTX, message, teamID string) mo // tryExecuteBuiltInCommand attempts to run a built in command based on the given arguments. If no such command can be // found, returns nil for all arguments. -func (a *App) tryExecuteBuiltInCommand(c *request.Context, args *model.CommandArgs, trigger string, message string) (*model.Command, *model.CommandResponse) { +func (a *App) tryExecuteBuiltInCommand(c request.CTX, args *model.CommandArgs, trigger string, message string) (*model.Command, *model.CommandResponse) { provider := GetCommandProvider(trigger) if provider == nil { return nil, nil @@ -443,7 +443,7 @@ func (a *App) tryExecuteCustomCommand(c request.CTX, args *model.CommandArgs, tr return nil, nil, nil } - mlog.Debug("Executing command", mlog.String("command", trigger), mlog.String("user_id", args.UserId)) + c.Logger().Debug("Executing command", mlog.String("command", trigger), mlog.String("user_id", args.UserId)) p := url.Values{} p.Set("token", cmd.Token) @@ -462,7 +462,7 @@ func (a *App) tryExecuteCustomCommand(c request.CTX, args *model.CommandArgs, tr p.Set("trigger_id", args.TriggerId) - userMentionMap := a.MentionsToTeamMembers(message, team.Id) + userMentionMap := a.MentionsToTeamMembers(c, message, team.Id) for key, values := range userMentionMap.ToURLValues() { p[key] = values } @@ -536,7 +536,7 @@ func (a *App) DoCommandRequest(cmd *model.Command, p url.Values) (*model.Command return cmd, response, nil } -func (a *App) HandleCommandResponse(c *request.Context, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) { +func (a *App) HandleCommandResponse(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) { trigger := "" if args.Command != "" { parts := strings.Split(args.Command, " ") @@ -570,7 +570,7 @@ func (a *App) HandleCommandResponse(c *request.Context, command *model.Command, return response, nil } -func (a *App) HandleCommandResponsePost(c *request.Context, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError) { +func (a *App) HandleCommandResponsePost(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError) { post := &model.Post{} post.ChannelId = args.ChannelId post.RootId = args.RootId diff --git a/app/command_autocomplete_test.go b/app/command_autocomplete_test.go index a8e2a5567b..0411664902 100644 --- a/app/command_autocomplete_test.go +++ b/app/command_autocomplete_test.go @@ -659,7 +659,7 @@ func (p *testCommandProvider) GetCommand(a *App, T i18n.TranslateFunc) *model.Co } } -func (p *testCommandProvider) DoCommand(a *App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (p *testCommandProvider) DoCommand(a *App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { return &model.CommandResponse{ Text: "I do nothing!", ResponseType: model.CommandResponseTypeEphemeral, diff --git a/app/config.go b/app/config.go index bbbc92ff44..6aacab9405 100644 --- a/app/config.go +++ b/app/config.go @@ -79,12 +79,12 @@ func (w *configWrapper) SaveConfig(newCfg *model.Config, sendConfigChangeCluster } if w.srv.startMetrics && *w.Config().MetricsSettings.Enable { - if w.srv.Metrics != nil { - w.srv.Metrics.Register() + if w.srv.GetMetrics() != nil { + w.srv.GetMetrics().Register() } - w.srv.SetupMetricsServer() + w.srv.platform.RestartMetrics() // TODO: remove when this moved to the platform service } else { - w.srv.StopMetricsServer() + w.srv.platform.ShutdownMetrics() // TODO: remove when this moved to the platform service } if w.srv.Cluster != nil { diff --git a/app/emoji.go b/app/emoji.go index 14f58d9128..496868dff3 100644 --- a/app/emoji.go +++ b/app/emoji.go @@ -82,7 +82,7 @@ func (a *App) CreateEmoji(sessionUserId string, emoji *model.Emoji, multiPartIma message := model.NewWebSocketEvent(model.WebsocketEventEmojiAdded, "", "", "", nil) emojiJSON, jsonErr := json.Marshal(emoji) if jsonErr != nil { - mlog.Warn("Failed to encode emoji to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("CreateEmoji", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("emoji", string(emojiJSON)) a.Publish(message) diff --git a/app/enterprise.go b/app/enterprise.go index d7a656b9fb..bb34bb1d8e 100644 --- a/app/enterprise.go +++ b/app/enterprise.go @@ -112,10 +112,6 @@ func RegisterLicenseInterface(f func(*Server) einterfaces.LicenseInterface) { } func (s *Server) initEnterprise() { - if metricsInterface != nil { - s.Metrics = metricsInterface(s) - } - if clusterInterface != nil && s.Cluster == nil { s.Cluster = clusterInterface(s) } diff --git a/app/export.go b/app/export.go index 456bb7ea08..7aad4a597d 100644 --- a/app/export.go +++ b/app/export.go @@ -15,6 +15,7 @@ import ( "github.com/pkg/errors" + "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/store" @@ -61,7 +62,7 @@ var exportablePreferences = map[ComparablePreference]string{{ }: "EmailInterval", } -func (a *App) BulkExport(writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError { +func (a *App) BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError { var zipWr *zip.Writer if opts.CreateArchive { var err error @@ -74,52 +75,52 @@ func (a *App) BulkExport(writer io.Writer, outPath string, opts model.BulkExport } } - mlog.Info("Bulk export: exporting version") + ctx.Logger().Info("Bulk export: exporting version") if err := a.exportVersion(writer); err != nil { return err } - mlog.Info("Bulk export: exporting teams") + ctx.Logger().Info("Bulk export: exporting teams") teamNames, err := a.exportAllTeams(writer) if err != nil { return err } - mlog.Info("Bulk export: exporting channels") + ctx.Logger().Info("Bulk export: exporting channels") if err = a.exportAllChannels(writer, teamNames); err != nil { return err } - mlog.Info("Bulk export: exporting users") + ctx.Logger().Info("Bulk export: exporting users") if err = a.exportAllUsers(writer); err != nil { return err } - mlog.Info("Bulk export: exporting posts") - attachments, err := a.exportAllPosts(writer, opts.IncludeAttachments) + ctx.Logger().Info("Bulk export: exporting posts") + attachments, err := a.exportAllPosts(ctx, writer, opts.IncludeAttachments) if err != nil { return err } - mlog.Info("Bulk export: exporting emoji") + ctx.Logger().Info("Bulk export: exporting emoji") emojiPaths, err := a.exportCustomEmoji(writer, outPath, "exported_emoji", !opts.CreateArchive) if err != nil { return err } - mlog.Info("Bulk export: exporting direct channels") + ctx.Logger().Info("Bulk export: exporting direct channels") if err = a.exportAllDirectChannels(writer); err != nil { return err } - mlog.Info("Bulk export: exporting direct posts") - directAttachments, err := a.exportAllDirectPosts(writer, opts.IncludeAttachments) + ctx.Logger().Info("Bulk export: exporting direct posts") + directAttachments, err := a.exportAllDirectPosts(ctx, writer, opts.IncludeAttachments) if err != nil { return err } if opts.IncludeAttachments { - mlog.Info("Bulk export: exporting file attachments") + ctx.Logger().Info("Bulk export: exporting file attachments") for _, attachment := range attachments { if err := a.exportFile(outPath, *attachment.Path, zipWr); err != nil { return err @@ -383,7 +384,7 @@ func (a *App) buildUserNotifyProps(notifyProps model.StringMap) *UserNotifyProps } } -func (a *App) exportAllPosts(writer io.Writer, withAttachments bool) ([]AttachmentImportData, *model.AppError) { +func (a *App) exportAllPosts(ctx request.CTX, writer io.Writer, withAttachments bool) ([]AttachmentImportData, *model.AppError) { var attachments []AttachmentImportData afterId := strings.Repeat("0", 26) @@ -407,7 +408,7 @@ func (a *App) exportAllPosts(writer io.Writer, withAttachments bool) ([]Attachme postLine := ImportLineForPost(post) - replies, replyAttachments, err := a.buildPostReplies(post.Id, withAttachments) + replies, replyAttachments, err := a.buildPostReplies(ctx, post.Id, withAttachments) if err != nil { return nil, err } @@ -419,7 +420,7 @@ func (a *App) exportAllPosts(writer io.Writer, withAttachments bool) ([]Attachme postLine.Post.Replies = &replies postLine.Post.Reactions = &[]ReactionImportData{} if post.HasReactions { - postLine.Post.Reactions, err = a.BuildPostReactions(post.Id) + postLine.Post.Reactions, err = a.BuildPostReactions(ctx, post.Id) if err != nil { return nil, err } @@ -444,7 +445,7 @@ func (a *App) exportAllPosts(writer io.Writer, withAttachments bool) ([]Attachme } } -func (a *App) buildPostReplies(postID string, withAttachments bool) ([]ReplyImportData, []AttachmentImportData, *model.AppError) { +func (a *App) buildPostReplies(ctx request.CTX, postID string, withAttachments bool) ([]ReplyImportData, []AttachmentImportData, *model.AppError) { var replies []ReplyImportData var attachments []AttachmentImportData @@ -457,7 +458,7 @@ func (a *App) buildPostReplies(postID string, withAttachments bool) ([]ReplyImpo replyImportObject := ImportReplyFromPost(reply) if reply.HasReactions { var appErr *model.AppError - replyImportObject.Reactions, appErr = a.BuildPostReactions(reply.Id) + replyImportObject.Reactions, appErr = a.BuildPostReactions(ctx, reply.Id) if appErr != nil { return nil, nil, appErr } @@ -479,7 +480,7 @@ func (a *App) buildPostReplies(postID string, withAttachments bool) ([]ReplyImpo return replies, attachments, nil } -func (a *App) BuildPostReactions(postID string) (*[]ReactionImportData, *model.AppError) { +func (a *App) BuildPostReactions(ctx request.CTX, postID string) (*[]ReactionImportData, *model.AppError) { var reactionsOfPost []ReactionImportData reactions, nErr := a.Srv().Store.Reaction().GetForPost(postID, true) @@ -492,7 +493,7 @@ func (a *App) BuildPostReactions(postID string) (*[]ReactionImportData, *model.A if err != nil { var nfErr *store.ErrNotFound if errors.As(err, &nfErr) { // this is a valid case, the user that reacted might've been deleted by now - mlog.Info("Skipping reactions by user since the entity doesn't exist anymore", mlog.String("user_id", reaction.UserId)) + ctx.Logger().Info("Skipping reactions by user since the entity doesn't exist anymore", mlog.String("user_id", reaction.UserId)) continue } return nil, model.NewAppError("BuildPostReactions", "app.user.get.app_error", nil, err.Error(), http.StatusInternalServerError) @@ -634,7 +635,7 @@ func (a *App) exportAllDirectChannels(writer io.Writer) *model.AppError { return nil } -func (a *App) exportAllDirectPosts(writer io.Writer, withAttachments bool) ([]AttachmentImportData, *model.AppError) { +func (a *App) exportAllDirectPosts(ctx request.CTX, writer io.Writer, withAttachments bool) ([]AttachmentImportData, *model.AppError) { var attachments []AttachmentImportData afterId := strings.Repeat("0", 26) for { @@ -670,7 +671,7 @@ func (a *App) exportAllDirectPosts(writer io.Writer, withAttachments bool) ([]At } // Do the Replies. - replies, replyAttachments, err := a.buildPostReplies(post.Id, withAttachments) + replies, replyAttachments, err := a.buildPostReplies(ctx, post.Id, withAttachments) if err != nil { return nil, err } diff --git a/app/export_test.go b/app/export_test.go index c7711afc0c..e9231a3af4 100644 --- a/app/export_test.go +++ b/app/export_test.go @@ -42,7 +42,7 @@ func TestReactionsOfPost(t *testing.T) { th.App.SaveReactionForPost(th.Context, &reactionObject) th.App.SaveReactionForPost(th.Context, &reactionObjectDeleted) - reactionsOfPost, err := th.App.BuildPostReactions(post.Id) + reactionsOfPost, err := th.App.BuildPostReactions(th.Context, post.Id) require.Nil(t, err) assert.Equal(t, reactionObject.EmojiName, *(*reactionsOfPost)[0].EmojiName) @@ -179,7 +179,7 @@ func TestExportAllUsers(t *testing.T) { require.Nil(t, err) var b bytes.Buffer - err = th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err = th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) th2 := Setup(t) @@ -227,7 +227,7 @@ func TestExportDMChannel(t *testing.T) { th1.CreateDmChannel(th1.BasicUser2) var b bytes.Buffer - err := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) channels, nErr := th1.App.Srv().Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000") @@ -268,7 +268,7 @@ func TestExportDMChannel(t *testing.T) { th1.App.PermanentDeleteUser(th1.Context, th1.BasicUser) var b bytes.Buffer - err := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) th2 := Setup(t).InitBasic() @@ -292,7 +292,7 @@ func TestExportDMChannelToSelf(t *testing.T) { th1.CreateDmChannel(th1.BasicUser) var b bytes.Buffer - err := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) channels, nErr := th1.App.Srv().Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000") @@ -330,7 +330,7 @@ func TestExportGMChannel(t *testing.T) { th1.CreateGroupChannel(th1.Context, user1, user2) var b bytes.Buffer - err := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) channels, nErr := th1.App.Srv().Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000") @@ -362,7 +362,7 @@ func TestExportGMandDMChannels(t *testing.T) { th1.CreateGroupChannel(th1.Context, user1, user2) var b bytes.Buffer - err := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) channels, nErr := th1.App.Srv().Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000") @@ -445,7 +445,7 @@ func TestExportDMandGMPost(t *testing.T) { assert.Equal(t, 4, len(posts)) var b bytes.Buffer - appErr := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + appErr := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, appErr) th1.TearDown() @@ -520,7 +520,7 @@ func TestExportPostWithProps(t *testing.T) { require.NotEmpty(t, posts[1].Props) var b bytes.Buffer - appErr := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + appErr := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, appErr) th1.TearDown() @@ -558,7 +558,7 @@ func TestExportDMPostWithSelf(t *testing.T) { th1.CreatePost(dmChannel) var b bytes.Buffer - err := th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) posts, nErr := th1.App.Srv().Store.Post().GetDirectPostParentsForExportAfter(1000, "0000000") @@ -626,7 +626,7 @@ func TestBulkExport(t *testing.T) { IncludeAttachments: true, CreateArchive: true, } - appErr = th.App.BulkExport(exportFile, dir, opts) + appErr = th.App.BulkExport(th.Context, exportFile, dir, opts) require.Nil(t, appErr) th.TearDown() @@ -663,7 +663,7 @@ func TestBuildPostReplies(t *testing.T) { } t.Run("basic post", func(t *testing.T) { - data, attachments, err := th.App.buildPostReplies(th.BasicPost.Id, true) + data, attachments, err := th.App.buildPostReplies(th.Context, th.BasicPost.Id, true) require.Nil(t, err) require.Empty(t, data) require.Empty(t, attachments) @@ -671,7 +671,7 @@ func TestBuildPostReplies(t *testing.T) { t.Run("root post with attachments and no replies", func(t *testing.T) { post := createPostWithAttachments(th, 5, "") - data, attachments, err := th.App.buildPostReplies(post.Id, true) + data, attachments, err := th.App.buildPostReplies(th.Context, post.Id, true) require.Nil(t, err) require.Empty(t, data) require.Empty(t, attachments) @@ -680,7 +680,7 @@ func TestBuildPostReplies(t *testing.T) { t.Run("root post with attachments and a reply", func(t *testing.T) { post := createPostWithAttachments(th, 5, "") createPostWithAttachments(th, 0, post.Id) - data, attachments, err := th.App.buildPostReplies(post.Id, true) + data, attachments, err := th.App.buildPostReplies(th.Context, post.Id, true) require.Nil(t, err) require.Len(t, data, 1) require.Empty(t, attachments) @@ -690,7 +690,7 @@ func TestBuildPostReplies(t *testing.T) { post := createPostWithAttachments(th, 5, "") reply1 := createPostWithAttachments(th, 2, post.Id) reply2 := createPostWithAttachments(th, 3, post.Id) - data, attachments, err := th.App.buildPostReplies(post.Id, true) + data, attachments, err := th.App.buildPostReplies(th.Context, post.Id, true) require.Nil(t, err) require.Len(t, data, 2) require.Len(t, attachments, 5) @@ -717,7 +717,7 @@ func TestExportDeletedTeams(t *testing.T) { require.Nil(t, err) var b bytes.Buffer - err = th1.App.BulkExport(&b, "somePath", model.BulkExportOpts{}) + err = th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{}) require.Nil(t, err) th2 := Setup(t) diff --git a/app/file.go b/app/file.go index 3ec52cb698..5b19e0c6b8 100644 --- a/app/file.go +++ b/app/file.go @@ -453,7 +453,7 @@ func GeneratePublicLinkHash(fileID, salt string) string { } // UploadFile uploads a single file in form of a completely constructed byte array for a channel. -func (a *App) UploadFile(c *request.Context, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) { +func (a *App) UploadFile(c request.CTX, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) { _, err := a.GetChannel(c, channelID) if err != nil && channelID != "" { return nil, model.NewAppError("UploadFile", "api.file.upload_file.incorrect_channelId.app_error", @@ -853,7 +853,7 @@ func (t UploadFileTask) newAppError(id string, httpStatus int, extra ...any) *mo return model.NewAppError("uploadFileTask", id, params, "", httpStatus) } -func (a *App) DoUploadFileExpectModification(c *request.Context, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) { +func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) { filename := filepath.Base(rawFilename) teamID := filepath.Base(rawTeamId) channelID := filepath.Base(rawChannelId) diff --git a/app/group.go b/app/group.go index b00a017fa3..686c2d6c0c 100644 --- a/app/group.go +++ b/app/group.go @@ -9,7 +9,6 @@ import ( "net/http" "github.com/mattermost/mattermost-server/v6/model" - "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/store" ) @@ -139,7 +138,7 @@ func (a *App) CreateGroupWithUserIds(group *model.GroupWithUserIds) (*model.Grou case errors.As(err, &invErr): return nil, model.NewAppError("CreateGroupWithUserIds", "app.group.id.app_error", nil, invErr.Error(), http.StatusBadRequest) case errors.As(err, &dupKey): - return nil, model.NewAppError("CreateGroup", "app.custom_group.unique_name", nil, dupKey.Error(), http.StatusBadRequest) + return nil, model.NewAppError("CreateGroupWithUserIds", "app.custom_group.unique_name", nil, dupKey.Error(), http.StatusBadRequest) default: return nil, model.NewAppError("CreateGroupWithUserIds", "app.insert_error", nil, err.Error(), http.StatusInternalServerError) } @@ -153,7 +152,7 @@ func (a *App) CreateGroupWithUserIds(group *model.GroupWithUserIds) (*model.Grou group.MemberCount = model.NewInt(int(count)) groupJSON, jsonErr := json.Marshal(newGroup) if jsonErr != nil { - mlog.Warn("Failed to encode group to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("CreateGroupWithUserIds", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } messageWs.Add("group", string(groupJSON)) a.Publish(messageWs) @@ -172,13 +171,13 @@ func (a *App) UpdateGroup(group *model.Group) (*model.Group, *model.AppError) { if err == nil { count, countErr := a.Srv().Store.Group().GetMemberCount(updatedGroup.Id) if countErr != nil { - return nil, model.NewAppError("CreateGroupWithUserIds", "app.group.id.app_error", nil, countErr.Error(), http.StatusBadRequest) + return nil, model.NewAppError("UpdateGroup", "app.group.id.app_error", nil, countErr.Error(), http.StatusBadRequest) } updatedGroup.MemberCount = model.NewInt(int(count)) messageWs := model.NewWebSocketEvent(model.WebsocketEventReceivedGroup, "", "", "", nil) groupJSON, jsonErr := json.Marshal(updatedGroup) if jsonErr != nil { - mlog.Warn("Failed to encode group to JSON", mlog.Err(jsonErr)) + return nil, model.NewAppError("UpdateGroup", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } messageWs.Add("group", string(groupJSON)) a.Publish(messageWs) @@ -272,7 +271,9 @@ func (a *App) UpsertGroupMember(groupID string, userID string) (*model.GroupMemb } } - a.publishGroupMemberEvent(model.WebsocketEventGroupMemberAdd, groupMember) + if appErr := a.publishGroupMemberEvent(model.WebsocketEventGroupMemberAdd, groupMember); appErr != nil { + return nil, appErr + } return groupMember, nil } @@ -289,7 +290,9 @@ func (a *App) DeleteGroupMember(groupID string, userID string) (*model.GroupMemb } } - a.publishGroupMemberEvent(model.WebsocketEventGroupMemberDelete, groupMember) + if appErr := a.publishGroupMemberEvent(model.WebsocketEventGroupMemberDelete, groupMember); appErr != nil { + return nil, appErr + } return groupMember, nil } @@ -743,7 +746,9 @@ func (a *App) UpsertGroupMembers(groupID string, userIDs []string) ([]*model.Gro } for _, groupMember := range members { - a.publishGroupMemberEvent(model.WebsocketEventGroupMemberAdd, groupMember) + if appErr := a.publishGroupMemberEvent(model.WebsocketEventGroupMemberAdd, groupMember); appErr != nil { + return nil, appErr + } } return members, nil @@ -765,18 +770,21 @@ func (a *App) DeleteGroupMembers(groupID string, userIDs []string) ([]*model.Gro } for _, groupMember := range members { - a.publishGroupMemberEvent(model.WebsocketEventGroupMemberDelete, groupMember) + if appErr := a.publishGroupMemberEvent(model.WebsocketEventGroupMemberDelete, groupMember); appErr != nil { + return nil, appErr + } } return members, nil } -func (a *App) publishGroupMemberEvent(eventName string, groupMember *model.GroupMember) { +func (a *App) publishGroupMemberEvent(eventName string, groupMember *model.GroupMember) *model.AppError { messageWs := model.NewWebSocketEvent(eventName, "", "", groupMember.UserId, nil) groupMemberJSON, jsonErr := json.Marshal(groupMember) if jsonErr != nil { - mlog.Warn("failed to encode group member to JSON", mlog.Err(jsonErr)) + return model.NewAppError("publishGroupMemberEvent", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } messageWs.Add("group_member", string(groupMemberJSON)) a.Publish(messageWs) + return nil } diff --git a/app/helper_test.go b/app/helper_test.go index cafec901cf..3cbaa72497 100644 --- a/app/helper_test.go +++ b/app/helper_test.go @@ -70,7 +70,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo if includeCacheLayer { // Adds the cache layer to the test store options = append(options, StoreOverride(func(s *Server) store.Store { - lcl, err2 := localcachelayer.NewLocalCacheLayer(dbStore, s.Metrics, s.Cluster, s.CacheProvider) + lcl, err2 := localcachelayer.NewLocalCacheLayer(dbStore, s.GetMetrics(), s.Cluster, s.CacheProvider) if err2 != nil { panic(err2) } diff --git a/app/import_functions.go b/app/import_functions.go index 75ec8ad364..44f89737a5 100644 --- a/app/import_functions.go +++ b/app/import_functions.go @@ -521,7 +521,7 @@ func (a *App) importUser(c request.CTX, data *UserImportData, dryRun bool) *mode pref := model.Preference{UserId: savedUser.Id, Category: model.PreferenceCategoryTutorialSteps, Name: savedUser.Id, Value: "0"} if err := a.Srv().Store.Preference().Save(model.Preferences{pref}); err != nil { - mlog.Warn("Encountered error saving tutorial preference", mlog.Err(err)) + c.Logger().Warn("Encountered error saving tutorial preference", mlog.Err(err)) } } else { @@ -584,14 +584,14 @@ func (a *App) importUser(c request.CTX, data *UserImportData, dryRun bool) *mode } if err != nil { - mlog.Warn("Unable to open the profile image.", mlog.Err(err)) + c.Logger().Warn("Unable to open the profile image.", mlog.Err(err)) } else { defer file.Close() if limitErr := checkImageLimits(file, *a.Config().FileSettings.MaxImageResolution); limitErr != nil { return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.check_image_limits.app_error", nil, "", http.StatusBadRequest) } if err := a.SetProfileImageFromFile(c, savedUser.Id, file); err != nil { - mlog.Warn("Unable to set the profile image from a file.", mlog.Err(err)) + c.Logger().Warn("Unable to set the profile image from a file.", mlog.Err(err)) } } } @@ -1137,7 +1137,7 @@ func (a *App) importReplies(c *request.Context, data []ReplyImportData, post *mo reply.Message = *replyData.Message reply.CreateAt = *replyData.CreateAt if reply.CreateAt < post.CreateAt { - mlog.Warn("Reply CreateAt is before parent post CreateAt, setting it to parent post CreateAt", mlog.Int64("reply_create_at", reply.CreateAt), mlog.Int64("parent_create_at", post.CreateAt)) + c.Logger().Warn("Reply CreateAt is before parent post CreateAt, setting it to parent post CreateAt", mlog.Int64("reply_create_at", reply.CreateAt), mlog.Int64("parent_create_at", post.CreateAt)) reply.CreateAt = post.CreateAt } if replyData.Type != nil { diff --git a/app/license.go b/app/license.go index 5f4b1a79d4..c6617bd76f 100644 --- a/app/license.go +++ b/app/license.go @@ -71,10 +71,6 @@ func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, term } } - if *w.srv.Config().ServiceSettings.SiteURL == "" { - return model.NewAppError("RequestTrialLicense", "api.license.request_trial_license.no-site-url.app_error", nil, "", http.StatusBadRequest) - } - trialLicenseRequest := &model.TrialLicenseRequest{ ServerID: w.srv.TelemetryId(), Name: requester.GetDisplayName(model.ShowFullName), diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 7cc9dd26b7..1ee94abb24 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -383,7 +383,7 @@ func (a *OpenTracingAppLayer) AddStatusCacheSkipClusterSend(status *model.Status a.app.AddStatusCacheSkipClusterSend(status) } -func (a *OpenTracingAppLayer) AddTeamMember(c *request.Context, teamID string, userID string) (*model.TeamMember, *model.AppError) { +func (a *OpenTracingAppLayer) AddTeamMember(c request.CTX, teamID string, userID string) (*model.TeamMember, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddTeamMember") @@ -515,7 +515,7 @@ func (a *OpenTracingAppLayer) AddUserToChannel(c request.CTX, user *model.User, return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) AddUserToTeam(c *request.Context, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) { +func (a *OpenTracingAppLayer) AddUserToTeam(c request.CTX, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddUserToTeam") @@ -951,7 +951,7 @@ func (a *OpenTracingAppLayer) BroadcastStatus(status *model.Status) { a.app.BroadcastStatus(status) } -func (a *OpenTracingAppLayer) BuildPostReactions(postID string) (*[]app.ReactionImportData, *model.AppError) { +func (a *OpenTracingAppLayer) BuildPostReactions(ctx request.CTX, postID string) (*[]app.ReactionImportData, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.BuildPostReactions") @@ -963,7 +963,7 @@ func (a *OpenTracingAppLayer) BuildPostReactions(postID string) (*[]app.Reaction }() defer span.Finish() - resultVar0, resultVar1 := a.app.BuildPostReactions(postID) + resultVar0, resultVar1 := a.app.BuildPostReactions(ctx, postID) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) @@ -1017,7 +1017,7 @@ func (a *OpenTracingAppLayer) BuildSamlMetadataObject(idpMetadata []byte) (*mode return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) BulkExport(writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError { +func (a *OpenTracingAppLayer) BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.BulkExport") @@ -1029,7 +1029,7 @@ func (a *OpenTracingAppLayer) BulkExport(writer io.Writer, outPath string, opts }() defer span.Finish() - resultVar0 := a.app.BulkExport(writer, outPath, opts) + resultVar0 := a.app.BulkExport(ctx, writer, outPath, opts) if resultVar0 != nil { span.LogFields(spanlog.Error(resultVar0)) @@ -1469,7 +1469,7 @@ func (a *OpenTracingAppLayer) CheckWebConn(userID string, connectionID string) * return resultVar0 } -func (a *OpenTracingAppLayer) ClearChannelMembersCache(c request.CTX, channelID string) { +func (a *OpenTracingAppLayer) ClearChannelMembersCache(c request.CTX, channelID string) error { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ClearChannelMembersCache") @@ -1481,7 +1481,14 @@ func (a *OpenTracingAppLayer) ClearChannelMembersCache(c request.CTX, channelID }() defer span.Finish() - a.app.ClearChannelMembersCache(c, channelID) + resultVar0 := a.app.ClearChannelMembersCache(c, channelID) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 } func (a *OpenTracingAppLayer) ClearLatestVersionCache() { @@ -1559,7 +1566,7 @@ func (a *OpenTracingAppLayer) ClearSessionCacheForUserSkipClusterSend(userID str a.app.ClearSessionCacheForUserSkipClusterSend(userID) } -func (a *OpenTracingAppLayer) ClearTeamMembersCache(teamID string) { +func (a *OpenTracingAppLayer) ClearTeamMembersCache(teamID string) error { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ClearTeamMembersCache") @@ -1571,7 +1578,14 @@ func (a *OpenTracingAppLayer) ClearTeamMembersCache(teamID string) { }() defer span.Finish() - a.app.ClearTeamMembersCache(teamID) + resultVar0 := a.app.ClearTeamMembersCache(teamID) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 } func (a *OpenTracingAppLayer) ClientConfig() map[string]string { @@ -1857,7 +1871,7 @@ func (a *OpenTracingAppLayer) CopyFileInfos(userID string, fileIDs []string) ([] return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) CreateBot(c *request.Context, bot *model.Bot) (*model.Bot, *model.AppError) { +func (a *OpenTracingAppLayer) CreateBot(c request.CTX, bot *model.Bot) (*model.Bot, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateBot") @@ -1967,7 +1981,7 @@ func (a *OpenTracingAppLayer) CreateCommand(cmd *model.Command) (*model.Command, return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) CreateCommandPost(c *request.Context, post *model.Post, teamID string, response *model.CommandResponse, skipSlackParsing bool) (*model.Post, *model.AppError) { +func (a *OpenTracingAppLayer) CreateCommandPost(c request.CTX, post *model.Post, teamID string, response *model.CommandResponse, skipSlackParsing bool) (*model.Post, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateCommandPost") @@ -2323,7 +2337,7 @@ func (a *OpenTracingAppLayer) CreatePost(c request.CTX, post *model.Post, channe return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) CreatePostAsUser(c *request.Context, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) { +func (a *OpenTracingAppLayer) CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreatePostAsUser") @@ -3838,7 +3852,7 @@ func (a *OpenTracingAppLayer) DoUploadFile(c *request.Context, now time.Time, ra return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) DoUploadFileExpectModification(c *request.Context, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) { +func (a *OpenTracingAppLayer) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DoUploadFileExpectModification") @@ -3948,7 +3962,7 @@ func (a *OpenTracingAppLayer) EnableUserAccessToken(token *model.UserAccessToken return resultVar0 } -func (a *OpenTracingAppLayer) EnsureBot(c *request.Context, productID string, bot *model.Bot) (string, error) { +func (a *OpenTracingAppLayer) EnsureBot(c request.CTX, productID string, bot *model.Bot) (string, error) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.EnsureBot") @@ -3987,7 +4001,7 @@ func (a *OpenTracingAppLayer) EnvironmentConfig(filter func(reflect.StructField) return resultVar0 } -func (a *OpenTracingAppLayer) ExecuteCommand(c *request.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { +func (a *OpenTracingAppLayer) ExecuteCommand(c request.CTX, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ExecuteCommand") @@ -10997,7 +11011,7 @@ func (a *OpenTracingAppLayer) Handle404(w http.ResponseWriter, r *http.Request) a.app.Handle404(w, r) } -func (a *OpenTracingAppLayer) HandleCommandResponse(c *request.Context, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) { +func (a *OpenTracingAppLayer) HandleCommandResponse(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.HandleCommandResponse") @@ -11019,7 +11033,7 @@ func (a *OpenTracingAppLayer) HandleCommandResponse(c *request.Context, command return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) HandleCommandResponsePost(c *request.Context, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError) { +func (a *OpenTracingAppLayer) HandleCommandResponsePost(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.HandleCommandResponsePost") @@ -11753,7 +11767,7 @@ func (a *OpenTracingAppLayer) LeaveChannel(c request.CTX, channelID string, user return resultVar0 } -func (a *OpenTracingAppLayer) LeaveTeam(c *request.Context, team *model.Team, user *model.User, requestorId string) *model.AppError { +func (a *OpenTracingAppLayer) LeaveTeam(c request.CTX, team *model.Team, user *model.User, requestorId string) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.LeaveTeam") @@ -12173,7 +12187,7 @@ func (a *OpenTracingAppLayer) MentionsToPublicChannels(c request.CTX, message st return resultVar0 } -func (a *OpenTracingAppLayer) MentionsToTeamMembers(message string, teamID string) model.UserMentionMap { +func (a *OpenTracingAppLayer) MentionsToTeamMembers(c request.CTX, message string, teamID string) model.UserMentionMap { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.MentionsToTeamMembers") @@ -12185,7 +12199,7 @@ func (a *OpenTracingAppLayer) MentionsToTeamMembers(message string, teamID strin }() defer span.Finish() - resultVar0 := a.app.MentionsToTeamMembers(message, teamID) + resultVar0 := a.app.MentionsToTeamMembers(c, message, teamID) return resultVar0 } @@ -13708,7 +13722,7 @@ func (a *OpenTracingAppLayer) RemoveUserFromChannel(c request.CTX, userIDToRemov return resultVar0 } -func (a *OpenTracingAppLayer) RemoveUserFromTeam(c *request.Context, teamID string, userID string, requestorId string) *model.AppError { +func (a *OpenTracingAppLayer) RemoveUserFromTeam(c request.CTX, teamID string, userID string, requestorId string) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveUserFromTeam") @@ -16175,7 +16189,7 @@ func (a *OpenTracingAppLayer) SyncPlugins() *model.AppError { return resultVar0 } -func (a *OpenTracingAppLayer) SyncRolesAndMembership(c *request.Context, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) { +func (a *OpenTracingAppLayer) SyncRolesAndMembership(c request.CTX, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SyncRolesAndMembership") @@ -16496,7 +16510,7 @@ func (a *OpenTracingAppLayer) UnregisterPluginCommand(pluginID string, teamID st a.app.UnregisterPluginCommand(pluginID, teamID, trigger) } -func (a *OpenTracingAppLayer) UpdateActive(c *request.Context, user *model.User, active bool) (*model.User, *model.AppError) { +func (a *OpenTracingAppLayer) UpdateActive(c request.CTX, user *model.User, active bool) (*model.User, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateActive") @@ -16518,7 +16532,7 @@ func (a *OpenTracingAppLayer) UpdateActive(c *request.Context, user *model.User, return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) UpdateBotActive(c *request.Context, botUserId string, active bool) (*model.Bot, *model.AppError) { +func (a *OpenTracingAppLayer) UpdateBotActive(c request.CTX, botUserId string, active bool) (*model.Bot, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateBotActive") @@ -17585,7 +17599,7 @@ func (a *OpenTracingAppLayer) UpdateUser(c request.CTX, user *model.User, sendNo return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) UpdateUserActive(c *request.Context, userID string, active bool) *model.AppError { +func (a *OpenTracingAppLayer) UpdateUserActive(c request.CTX, userID string, active bool) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateUserActive") @@ -17791,7 +17805,7 @@ func (a *OpenTracingAppLayer) UploadEmojiImage(id string, imageData *multipart.F return resultVar0 } -func (a *OpenTracingAppLayer) UploadFile(c *request.Context, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) { +func (a *OpenTracingAppLayer) UploadFile(c request.CTX, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UploadFile") diff --git a/app/platform/config.go b/app/platform/config.go index 2713166849..20c5a823d5 100644 --- a/app/platform/config.go +++ b/app/platform/config.go @@ -3,14 +3,34 @@ package platform +import ( + "errors" + + "github.com/mattermost/mattermost-server/v6/config" + "github.com/mattermost/mattermost-server/v6/einterfaces" + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + // ServiceConfig is used to initialize the PlatformService. // The mandatory fields will be checked during the initialization of the service. type ServiceConfig struct { // Mandatory fields + ConfigStore *config.Store + Logger *mlog.Logger + StartMetrics bool // TODO: find an elegant way to start/stop metrics server by default // Optional fields + Metrics einterfaces.MetricsInterface + Cluster einterfaces.ClusterInterface } func (c *ServiceConfig) validate() error { // Mandatory fields need to be checked here + if c.ConfigStore == nil { + return errors.New("ConfigStore is required") + } + + if c.Logger == nil { + return errors.New("Logger is required") + } return nil } diff --git a/app/platform/metrics.go b/app/platform/metrics.go new file mode 100644 index 0000000000..4ace86b156 --- /dev/null +++ b/app/platform/metrics.go @@ -0,0 +1,184 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package platform + +import ( + "context" + "fmt" + "net" + "net/http" + "net/http/pprof" + "runtime" + "sync" + "text/template" + "time" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/mattermost/mattermost-server/v6/einterfaces" + "github.com/mattermost/mattermost-server/v6/model" + "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/pkg/errors" +) + +const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second + +type platformMetrics struct { + server *http.Server + router *mux.Router + lock sync.Mutex + logger *mlog.Logger + + metricsImpl einterfaces.MetricsInterface + + cfgFn func() *model.Config +} + +// resetMetrics resets the metrics server. Clears the metrics if the metrics are disabled by the config. +func (ps *PlatformService) resetMetrics(metricsImpl einterfaces.MetricsInterface, cfgFn func() *model.Config) error { + if !*cfgFn().MetricsSettings.Enable { + if ps.metrics != nil { + return ps.metrics.stopMetricsServer() + } + return nil + } + + if ps.metrics != nil { + if err := ps.metrics.stopMetricsServer(); err != nil { + return err + } + } + + ps.metrics = &platformMetrics{ + cfgFn: cfgFn, + metricsImpl: metricsImpl, + logger: ps.logger, + } + + if err := ps.metrics.initMetricsRouter(); err != nil { + return err + } + + if metricsImpl != nil { + metricsImpl.Register() + } + + return ps.metrics.startMetricsServer() +} + +func (pm *platformMetrics) stopMetricsServer() error { + pm.lock.Lock() + defer pm.lock.Unlock() + + if pm.server != nil { + ctx, cancel := context.WithTimeout(context.Background(), TimeToWaitForConnectionsToCloseOnServerShutdown) + defer cancel() + + if err := pm.server.Shutdown(ctx); err != nil { + return fmt.Errorf("could not shutdown metrics server: %v", err) + } + + pm.logger.Info("Metrics and profiling server is stopped") + } + + return nil +} + +func (pm *platformMetrics) startMetricsServer() error { + var notify chan struct{} + pm.lock.Lock() + defer func() { + if notify != nil { + <-notify + } + pm.lock.Unlock() + }() + + l, err := net.Listen("tcp", *pm.cfgFn().MetricsSettings.ListenAddress) + if err != nil { + return err + } + + notify = make(chan struct{}) + pm.server = &http.Server{ + Handler: handlers.RecoveryHandler(handlers.PrintRecoveryStack(true))(pm.router), + ReadTimeout: time.Duration(*pm.cfgFn().ServiceSettings.ReadTimeout) * time.Second, + WriteTimeout: time.Duration(*pm.cfgFn().ServiceSettings.WriteTimeout) * time.Second, + } + + go func() { + close(notify) + if err := pm.server.Serve(l); err != nil && err != http.ErrServerClosed { + pm.logger.Critical(err.Error()) + } + }() + + pm.logger.Info("Metrics and profiling server is started", mlog.String("address", l.Addr().String())) + return nil +} + +func (pm *platformMetrics) initMetricsRouter() error { + pm.router = mux.NewRouter() + runtime.SetBlockProfileRate(*pm.cfgFn().MetricsSettings.BlockProfileRate) + + metricsPage := ` + + {{if .}} +
Metrics
{{end}} +
Profiling Root
+
Profiling Command Line
+
Profiling Symbols
+
Profiling Goroutines
+
Profiling Heap
+
Profiling Threads
+
Profiling Blocking
+
Profiling Execution Trace
+
Profiling CPU
+ + + ` + metricsPageTmpl, err := template.New("page").Parse(metricsPage) + if err != nil { + return errors.Wrap(err, "failed to create template") + } + + rootHandler := func(w http.ResponseWriter, r *http.Request) { + metricsPageTmpl.Execute(w, pm.metricsImpl != nil) + } + + pm.router.HandleFunc("/", rootHandler) + pm.router.StrictSlash(true) + + pm.router.Handle("/debug", http.RedirectHandler("/", http.StatusMovedPermanently)) + pm.router.HandleFunc("/debug/pprof/", pprof.Index) + pm.router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + pm.router.HandleFunc("/debug/pprof/profile", pprof.Profile) + pm.router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + pm.router.HandleFunc("/debug/pprof/trace", pprof.Trace) + + // Manually add support for paths linked to by index page at /debug/pprof/ + pm.router.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) + pm.router.Handle("/debug/pprof/heap", pprof.Handler("heap")) + pm.router.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) + pm.router.Handle("/debug/pprof/block", pprof.Handler("block")) + + return nil +} + +func (ps *PlatformService) HandleMetrics(route string, h http.Handler) { + if ps.metrics != nil { + ps.metrics.router.Handle(route, h) + } +} + +func (ps *PlatformService) RestartMetrics() error { + return ps.resetMetrics(ps.serviceConfig.Metrics, ps.serviceConfig.ConfigStore.Get) +} + +func (ps *PlatformService) Metrics() einterfaces.MetricsInterface { + if ps.metrics == nil { + return nil + } + return ps.metrics.metricsImpl +} diff --git a/app/platform/service.go b/app/platform/service.go index a9eda866f7..baa4b1362e 100644 --- a/app/platform/service.go +++ b/app/platform/service.go @@ -3,17 +3,49 @@ package platform +import ( + "github.com/mattermost/mattermost-server/v6/config" + "github.com/mattermost/mattermost-server/v6/einterfaces" + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + // PlatformService is the service for the platform related tasks. It is // responsible for non-entity related functionalities that are required // by a product such as database access, configuration access, licensing etc. type PlatformService struct { + serviceConfig ServiceConfig + configStore *config.Store + logger *mlog.Logger + + metrics *platformMetrics + + cluster einterfaces.ClusterInterface } // New creates a new PlatformService. -func New(c ServiceConfig) (*PlatformService, error) { - if err := c.validate(); err != nil { +func New(sc ServiceConfig) (*PlatformService, error) { + if err := sc.validate(); err != nil { return nil, err } - return &PlatformService{}, nil + ps := &PlatformService{ + serviceConfig: sc, + configStore: sc.ConfigStore, + logger: sc.Logger, + cluster: sc.Cluster, + } + + if err := ps.resetMetrics(sc.Metrics, ps.configStore.Get); err != nil { + return nil, err + } + + return ps, nil +} + +func (ps *PlatformService) ShutdownMetrics() error { + if ps.metrics != nil { + return ps.metrics.stopMetricsServer() + } + + return nil } diff --git a/app/plugin.go b/app/plugin.go index 4fb0abc501..38be469d17 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -226,7 +226,7 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s return New(ServerConnector(ch)).NewPluginAPI(c, manifest) } - env, err := plugin.NewEnvironment(newAPIFunc, NewDriverImpl(ch.srv), pluginDir, webappPluginDir, ch.srv.Log, ch.srv.Metrics) + env, err := plugin.NewEnvironment(newAPIFunc, NewDriverImpl(ch.srv), pluginDir, webappPluginDir, ch.srv.Log, ch.srv.GetMetrics()) if err != nil { mlog.Error("Failed to start up plugins", mlog.Err(err)) return diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 03cfce39bb..6bb40a78cd 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -1821,7 +1821,7 @@ func (*MockSlashCommandProvider) GetCommand(a *App, T i18n.TranslateFunc) *model } } -func (mscp *MockSlashCommandProvider) DoCommand(a *App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (mscp *MockSlashCommandProvider) DoCommand(a *App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { mscp.Args = args mscp.Message = message return &model.CommandResponse{ diff --git a/app/plugin_commands.go b/app/plugin_commands.go index 3caa1547ab..e70c69a38a 100644 --- a/app/plugin_commands.go +++ b/app/plugin_commands.go @@ -117,7 +117,7 @@ func (a *App) PluginCommandsForTeam(teamID string) []*model.Command { // tryExecutePluginCommand attempts to run a command provided by a plugin based on the given arguments. If no such // command can be found, returns nil for all arguments. -func (a *App) tryExecutePluginCommand(c *request.Context, args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) { +func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) { parts := strings.Split(args.Command, " ") trigger := parts[0][1:] trigger = strings.ToLower(trigger) @@ -150,7 +150,7 @@ func (a *App) tryExecutePluginCommand(c *request.Context, args *model.CommandArg return matched.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError) } - for username, userID := range a.MentionsToTeamMembers(args.Command, args.TeamId) { + for username, userID := range a.MentionsToTeamMembers(c, args.Command, args.TeamId) { args.AddUserMention(username, userID) } diff --git a/app/post.go b/app/post.go index 983871680b..beabd25da0 100644 --- a/app/post.go +++ b/app/post.go @@ -46,7 +46,7 @@ func (s *postServiceWrapper) CreatePost(ctx *request.Context, post *model.Post) return s.app.CreatePostMissingChannel(ctx, post, true) } -func (a *App) CreatePostAsUser(c *request.Context, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) { +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) if errCh != nil { @@ -83,7 +83,7 @@ func (a *App) CreatePostAsUser(c *request.Context, post *model.Post, currentSess isCRTReply := post.RootId != "" && a.IsCRTEnabledForUser(c, post.UserId) if !fromWebhook && !fromBot && !isCRTReply { if _, err := a.MarkChannelsAsViewed(c, []string{post.ChannelId}, post.UserId, currentSessionId, true); err != nil { - mlog.Warn( + c.Logger().Warn( "Encountered error updating last viewed", mlog.String("channel_id", post.ChannelId), mlog.String("user_id", post.UserId), @@ -1251,7 +1251,7 @@ func (a *App) DeletePost(c request.CTX, postID, deleteByID string) (*model.Post, postJSON, jsonErr := json.Marshal(post) if jsonErr != nil { - mlog.Warn("Failed to encode post to JSON") + return nil, model.NewAppError("DeletePost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } userMessage := model.NewWebSocketEvent(model.WebsocketEventPostDeleted, "", post.ChannelId, "", nil) diff --git a/app/preference.go b/app/preference.go index 23df83426c..843976d122 100644 --- a/app/preference.go +++ b/app/preference.go @@ -9,7 +9,6 @@ import ( "net/http" "github.com/mattermost/mattermost-server/v6/model" - "github.com/mattermost/mattermost-server/v6/shared/mlog" ) func (a *App) GetPreferencesForUser(userID string) (model.Preferences, *model.AppError) { @@ -69,7 +68,7 @@ func (a *App) UpdatePreferences(userID string, preferences model.Preferences) *m message = model.NewWebSocketEvent(model.WebsocketEventPreferencesChanged, "", "", userID, nil) prefsJSON, jsonErr := json.Marshal(preferences) if jsonErr != nil { - mlog.Warn("Failed to encode to JSON", mlog.Err(jsonErr)) + return model.NewAppError("UpdatePreferences", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("preferences", string(prefsJSON)) a.Publish(message) @@ -103,7 +102,7 @@ func (a *App) DeletePreferences(userID string, preferences model.Preferences) *m message = model.NewWebSocketEvent(model.WebsocketEventPreferencesDeleted, "", "", userID, nil) prefsJSON, jsonErr := json.Marshal(preferences) if jsonErr != nil { - mlog.Warn("Failed to encode to JSON", mlog.Err(jsonErr)) + return model.NewAppError("DeletePreferences", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("preferences", string(prefsJSON)) a.Publish(message) diff --git a/app/role.go b/app/role.go index 846d9d83f7..d0b946acf7 100644 --- a/app/role.go +++ b/app/role.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/mattermost/mattermost-server/v6/model" - "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/store" "github.com/mattermost/mattermost-server/v6/utils" ) @@ -138,7 +137,9 @@ func (a *App) PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, return nil, err } - a.sendUpdatedRoleEvent(role) + if appErr := a.sendUpdatedRoleEvent(role); appErr != nil { + return nil, appErr + } return role, err } @@ -225,7 +226,10 @@ func (a *App) UpdateRole(role *model.Role) (*model.Role, *model.AppError) { for _, ir := range impactedRoles { if ir.Name != role.Name { - a.sendUpdatedRoleEvent(ir) + appErr = a.sendUpdatedRoleEvent(ir) + if appErr != nil { + return nil, appErr + } } } @@ -254,17 +258,15 @@ func (a *App) CheckRolesExist(roleNames []string) *model.AppError { return nil } -func (a *App) sendUpdatedRoleEvent(role *model.Role) { +func (a *App) sendUpdatedRoleEvent(role *model.Role) *model.AppError { message := model.NewWebSocketEvent(model.WebsocketEventRoleUpdated, "", "", "", nil) roleJSON, jsonErr := json.Marshal(role) if jsonErr != nil { - mlog.Warn("Failed to encode role to JSON", mlog.Err(jsonErr)) + return model.NewAppError("sendUpdatedRoleEvent", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("role", string(roleJSON)) - - a.Srv().Go(func() { - a.Publish(message) - }) + a.Publish(message) + return nil } func RemoveRoles(rolesToRemove []string, roles string) string { diff --git a/app/server.go b/app/server.go index e363e76ca6..48b99eea35 100644 --- a/app/server.go +++ b/app/server.go @@ -9,10 +9,8 @@ import ( "crypto/tls" "fmt" "hash/maphash" - "html/template" "net" "net/http" - "net/http/pprof" "net/url" "os" "os/exec" @@ -27,7 +25,6 @@ import ( "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" - "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/rs/cors" @@ -35,6 +32,7 @@ import ( "github.com/mattermost/mattermost-server/v6/app/email" "github.com/mattermost/mattermost-server/v6/app/featureflag" + "github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/app/teams" "github.com/mattermost/mattermost-server/v6/app/users" @@ -131,10 +129,6 @@ type Server struct { localModeServer *http.Server - metricsServer *http.Server - metricsRouter *mux.Router - metricsLock sync.Mutex - didFinishListen chan struct{} goroutineCount int32 @@ -177,6 +171,7 @@ type Server struct { configStore *configWrapper filestore filestore.FileBackend + platform *platform.PlatformService telemetryService *telemetry.TelemetryService userService *users.UserService teamService *teams.TeamService @@ -200,7 +195,6 @@ type Server struct { Cluster einterfaces.ClusterInterface Cloud einterfaces.CloudInterface - Metrics einterfaces.MetricsInterface LicenseManager einterfaces.LicenseInterface CacheProvider cache.Provider @@ -286,6 +280,22 @@ func NewServer(options ...Option) (*Server, error) { // Depends on step 3 (s.SearchEngine must be non-nil) s.initEnterprise() + platformCfg := platform.ServiceConfig{ + ConfigStore: s.configStore.Store, + Logger: s.Log, + StartMetrics: s.startMetrics, + Cluster: s.Cluster, + } + if metricsInterface != nil { + platformCfg.Metrics = metricsInterface(s) + } + + ps, sErr := platform.New(platformCfg) + if sErr != nil { + return nil, errors.Wrap(sErr, "failed to initialize platform") + } + s.platform = ps + // Step 5: Cache provider. // At the moment we only have this implementation // in the future the cache provider will be built based on the loaded config @@ -298,11 +308,11 @@ func NewServer(options ...Option) (*Server, error) { // Depends on Step 1 (config), 4 (metrics, cluster) and 5 (cacheProvider). if s.newStore == nil { s.newStore = func() (store.Store, error) { - s.sqlStore = sqlstore.New(s.Config().SqlSettings, s.Metrics) + s.sqlStore = sqlstore.New(s.Config().SqlSettings, s.GetMetrics()) lcl, err2 := localcachelayer.NewLocalCacheLayer( retrylayer.New(s.sqlStore), - s.Metrics, + s.GetMetrics(), s.Cluster, s.CacheProvider, ) @@ -327,7 +337,7 @@ func NewServer(options ...Option) (*Server, error) { return timerlayer.New( searchStore, - s.Metrics, + s.GetMetrics(), ), nil } } @@ -343,7 +353,7 @@ func NewServer(options ...Option) (*Server, error) { SessionStore: s.Store.Session(), OAuthStore: s.Store.OAuth(), ConfigFn: s.Config, - Metrics: s.Metrics, + Metrics: s.GetMetrics(), Cluster: s.Cluster, LicenseFn: s.License, }) @@ -620,7 +630,9 @@ func NewServer(options ...Option) (*Server, error) { } if s.startMetrics { - s.SetupMetricsServer() + if err := s.platform.RestartMetrics(); err != nil { + return nil, errors.Wrap(err, "failed to start metrics") + } } s.AddLicenseListener(func(oldLicense, newLicense *model.License) { @@ -632,7 +644,9 @@ func NewServer(options ...Option) (*Server, error) { return } - s.SetupMetricsServer() + if err := s.platform.RestartMetrics(); err != nil { + s.Log.Error("Failed to reset metrics server", mlog.Err(err)) + } }) s.SearchEngine.UpdateConfig(s.Config()) @@ -701,24 +715,6 @@ func NewServer(options ...Option) (*Server, error) { return s, nil } -func (s *Server) SetupMetricsServer() { - if !*s.Config().MetricsSettings.Enable { - return - } - - s.StopMetricsServer() - - if err := s.InitMetricsRouter(); err != nil { - mlog.Error("Error initiating metrics router.", mlog.Err(err)) - } - - if s.Metrics != nil { - s.Metrics.Register() - } - - s.startMetricsServer() -} - func maxInt(a, b int) int { if a > b { return a @@ -951,11 +947,11 @@ func (s *Server) startInterClusterServices(license *model.License) error { } func (s *Server) enableLoggingMetrics() { - if s.Metrics == nil { + if s.GetMetrics() == nil { return } - s.Log.SetMetricsCollector(s.Metrics.GetLoggerMetricsCollector(), mlog.DefaultMetricsUpdateFreqMillis) + s.Log.SetMetricsCollector(s.GetMetrics().GetLoggerMetricsCollector(), mlog.DefaultMetricsUpdateFreqMillis) // logging config needs to be reloaded when metrics collector is added or changed. if err := s.initLogging(); err != nil { @@ -991,7 +987,7 @@ func (s *Server) StopHTTPServer() { } func (s *Server) Shutdown() { - mlog.Info("Stopping Server...") + s.Log.Info("Stopping Server...") defer sentry.Flush(2 * time.Second) @@ -1002,24 +998,24 @@ func (s *Server) Shutdown() { if s.tracer != nil { if err := s.tracer.Close(); err != nil { - mlog.Warn("Unable to cleanly shutdown opentracing client", mlog.Err(err)) + s.Log.Warn("Unable to cleanly shutdown opentracing client", mlog.Err(err)) } } err := s.telemetryService.Shutdown() if err != nil { - mlog.Warn("Unable to cleanly shutdown telemetry client", mlog.Err(err)) + s.Log.Warn("Unable to cleanly shutdown telemetry client", mlog.Err(err)) } s.serviceMux.RLock() if s.sharedChannelService != nil { if err = s.sharedChannelService.Shutdown(); err != nil { - mlog.Error("Error shutting down shared channel services", mlog.Err(err)) + s.Log.Error("Error shutting down shared channel services", mlog.Err(err)) } } if s.remoteClusterService != nil { if err = s.remoteClusterService.Shutdown(); err != nil { - mlog.Error("Error shutting down intercluster services", mlog.Err(err)) + s.Log.Error("Error shutting down intercluster services", mlog.Err(err)) } } s.serviceMux.RUnlock() @@ -1046,7 +1042,9 @@ func (s *Server) Shutdown() { s.Cluster.StopInterNodeCommunication() } - s.StopMetricsServer() + if err = s.platform.ShutdownMetrics(); err != nil { + s.Log.Warn("Failed to stop metrics server", mlog.Err(err)) + } // This must be done after the cluster is stopped. if s.Jobs != nil { @@ -1054,10 +1052,10 @@ func (s *Server) Shutdown() { // before stopping them as both calls essentially become no-ops // if nothing is running. if err = s.Jobs.StopWorkers(); err != nil && !errors.Is(err, jobs.ErrWorkersNotRunning) { - mlog.Warn("Failed to stop job server workers", mlog.Err(err)) + s.Log.Warn("Failed to stop job server workers", mlog.Err(err)) } if err = s.Jobs.StopSchedulers(); err != nil && !errors.Is(err, jobs.ErrSchedulersNotRunning) { - mlog.Warn("Failed to stop job server schedulers", mlog.Err(err)) + s.Log.Warn("Failed to stop job server schedulers", mlog.Err(err)) } } @@ -1066,7 +1064,7 @@ func (s *Server) Shutdown() { // on parent services. for name, product := range s.products { if err2 := product.Stop(); err2 != nil { - mlog.Warn("Unable to cleanly stop product", mlog.String("name", name), mlog.Err(err2)) + s.Log.Warn("Unable to cleanly stop product", mlog.String("name", name), mlog.Err(err2)) } } @@ -1076,11 +1074,11 @@ func (s *Server) Shutdown() { if s.CacheProvider != nil { if err = s.CacheProvider.Close(); err != nil { - mlog.Warn("Unable to cleanly shutdown cache", mlog.Err(err)) + s.Log.Warn("Unable to cleanly shutdown cache", mlog.Err(err)) } } - mlog.Info("Server stopped") + s.Log.Info("Server stopped") // shutdown main and notification loggers which will flush any remaining log records. timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), time.Second*15) @@ -1630,104 +1628,8 @@ func doConfigCleanup(s *Server) { } } -func (s *Server) StopMetricsServer() { - s.metricsLock.Lock() - defer s.metricsLock.Unlock() - - if s.metricsServer != nil { - ctx, cancel := context.WithTimeout(context.Background(), TimeToWaitForConnectionsToCloseOnServerShutdown) - defer cancel() - - s.metricsServer.Shutdown(ctx) - s.Log.Info("Metrics and profiling server is stopping") - } -} - func (s *Server) HandleMetrics(route string, h http.Handler) { - if s.metricsRouter != nil { - s.metricsRouter.Handle(route, h) - } -} - -func (s *Server) InitMetricsRouter() error { - s.metricsRouter = mux.NewRouter() - runtime.SetBlockProfileRate(*s.Config().MetricsSettings.BlockProfileRate) - - metricsPage := ` - - {{if .}} -
Metrics
{{end}} -
Profiling Root
-
Profiling Command Line
-
Profiling Symbols
-
Profiling Goroutines
-
Profiling Heap
-
Profiling Threads
-
Profiling Blocking
-
Profiling Execution Trace
-
Profiling CPU
- - - ` - metricsPageTmpl, err := template.New("page").Parse(metricsPage) - if err != nil { - return errors.Wrap(err, "failed to create template") - } - - rootHandler := func(w http.ResponseWriter, r *http.Request) { - metricsPageTmpl.Execute(w, s.Metrics != nil) - } - - s.metricsRouter.HandleFunc("/", rootHandler) - s.metricsRouter.StrictSlash(true) - - s.metricsRouter.Handle("/debug", http.RedirectHandler("/", http.StatusMovedPermanently)) - s.metricsRouter.HandleFunc("/debug/pprof/", pprof.Index) - s.metricsRouter.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - s.metricsRouter.HandleFunc("/debug/pprof/profile", pprof.Profile) - s.metricsRouter.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - s.metricsRouter.HandleFunc("/debug/pprof/trace", pprof.Trace) - - // Manually add support for paths linked to by index page at /debug/pprof/ - s.metricsRouter.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) - s.metricsRouter.Handle("/debug/pprof/heap", pprof.Handler("heap")) - s.metricsRouter.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) - s.metricsRouter.Handle("/debug/pprof/block", pprof.Handler("block")) - - return nil -} - -func (s *Server) startMetricsServer() { - var notify chan struct{} - s.metricsLock.Lock() - defer func() { - if notify != nil { - <-notify - } - s.metricsLock.Unlock() - }() - - l, err := net.Listen("tcp", *s.Config().MetricsSettings.ListenAddress) - if err != nil { - mlog.Error(err.Error()) - return - } - - notify = make(chan struct{}) - s.metricsServer = &http.Server{ - Handler: handlers.RecoveryHandler(handlers.PrintRecoveryStack(true))(s.metricsRouter), - ReadTimeout: time.Duration(*s.Config().ServiceSettings.ReadTimeout) * time.Second, - WriteTimeout: time.Duration(*s.Config().ServiceSettings.WriteTimeout) * time.Second, - } - - go func() { - close(notify) - if err := s.metricsServer.Serve(l); err != nil && err != http.ErrServerClosed { - mlog.Critical(err.Error()) - } - }() - - s.Log.Info("Metrics and profiling server is started", mlog.String("address", l.Addr().String())) + s.platform.HandleMetrics(route, h) } func (s *Server) sendLicenseUpForRenewalEmail(users map[string]*model.User, license *model.License) *model.AppError { @@ -1956,7 +1858,7 @@ func (ch *Channels) ClientConfigHash() string { } func (s *Server) initJobs() { - s.Jobs = jobs.NewJobServer(s, s.Store, s.Metrics) + s.Jobs = jobs.NewJobServer(s, s.Store, s.GetMetrics()) if jobsDataRetentionJobInterface != nil { builder := jobsDataRetentionJobInterface(s) @@ -2039,7 +1941,7 @@ func (s *Server) initJobs() { s.Jobs.RegisterJobType( model.JobTypeActiveUsers, - active_users.MakeWorker(s.Jobs, s.Store, func() einterfaces.MetricsInterface { return s.Metrics }), + active_users.MakeWorker(s.Jobs, s.Store, func() einterfaces.MetricsInterface { return s.GetMetrics() }), active_users.MakeScheduler(s.Jobs), ) @@ -2106,7 +2008,10 @@ func (s *Server) GetSharedChannelSyncService() SharedChannelServiceIFace { // GetMetrics returns the server's Metrics interface. Exposing via a method // allows interfaces to be created with subsets of server APIs. func (s *Server) GetMetrics() einterfaces.MetricsInterface { - return s.Metrics + if s.platform == nil { + return nil + } + return s.platform.Metrics() } // SetRemoteClusterService sets the `RemoteClusterService` to be used by the server. diff --git a/app/slashcommands/auto_channels.go b/app/slashcommands/auto_channels.go index 7a4791db24..53726177cf 100644 --- a/app/slashcommands/auto_channels.go +++ b/app/slashcommands/auto_channels.go @@ -36,7 +36,7 @@ func NewAutoChannelCreator(a *app.App, team *model.Team, userID string) *AutoCha } } -func (cfg *AutoChannelCreator) createRandomChannel(c *request.Context) (*model.Channel, error) { +func (cfg *AutoChannelCreator) createRandomChannel(c request.CTX) (*model.Channel, error) { var displayName string if cfg.Fuzzy { displayName = utils.FuzzName() @@ -60,7 +60,7 @@ func (cfg *AutoChannelCreator) createRandomChannel(c *request.Context) (*model.C return channel, nil } -func (cfg *AutoChannelCreator) CreateTestChannels(c *request.Context, num utils.Range) ([]*model.Channel, error) { +func (cfg *AutoChannelCreator) CreateTestChannels(c request.CTX, num utils.Range) ([]*model.Channel, error) { numChannels := utils.RandIntFromRange(num) channels := make([]*model.Channel, numChannels) @@ -75,7 +75,7 @@ func (cfg *AutoChannelCreator) CreateTestChannels(c *request.Context, num utils. return channels, nil } -func (cfg *AutoChannelCreator) CreateTestDMs(c *request.Context, num utils.Range) ([]*model.Channel, error) { +func (cfg *AutoChannelCreator) CreateTestDMs(c request.CTX, num utils.Range) ([]*model.Channel, error) { numDMs := utils.RandIntFromRange(num) dms := make([]*model.Channel, numDMs) diff --git a/app/slashcommands/auto_environment.go b/app/slashcommands/auto_environment.go index 50870decf3..fcd1c92084 100644 --- a/app/slashcommands/auto_environment.go +++ b/app/slashcommands/auto_environment.go @@ -18,7 +18,7 @@ type TestEnvironment struct { Environments []TeamEnvironment } -func CreateTestEnvironmentWithTeams(a *app.App, c *request.Context, client *model.Client4, rangeTeams utils.Range, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TestEnvironment, error) { +func CreateTestEnvironmentWithTeams(a *app.App, c request.CTX, client *model.Client4, rangeTeams utils.Range, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TestEnvironment, error) { rand.Seed(time.Now().UTC().UnixNano()) teamCreator := NewAutoTeamCreator(client) @@ -48,7 +48,7 @@ func CreateTestEnvironmentWithTeams(a *app.App, c *request.Context, client *mode return environment, nil } -func CreateTestEnvironmentInTeam(a *app.App, c *request.Context, client *model.Client4, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, error) { +func CreateTestEnvironmentInTeam(a *app.App, c request.CTX, client *model.Client4, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, error) { rand.Seed(time.Now().UTC().UnixNano()) // We need to create at least one user diff --git a/app/slashcommands/auto_posts.go b/app/slashcommands/auto_posts.go index 3c78b09988..666bbea5a4 100644 --- a/app/slashcommands/auto_posts.go +++ b/app/slashcommands/auto_posts.go @@ -45,7 +45,7 @@ func NewAutoPostCreator(a *app.App, channelid, userid string) *AutoPostCreator { } } -func (cfg *AutoPostCreator) UploadTestFile(c *request.Context) ([]string, error) { +func (cfg *AutoPostCreator) UploadTestFile(c request.CTX) ([]string, error) { filename := cfg.ImageFilenames[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(cfg.ImageFilenames) - 1})] path, _ := fileutils.FindDir("tests") @@ -69,11 +69,11 @@ func (cfg *AutoPostCreator) UploadTestFile(c *request.Context) ([]string, error) return []string{fileResp.Id}, nil } -func (cfg *AutoPostCreator) CreateRandomPost(c *request.Context) (*model.Post, error) { +func (cfg *AutoPostCreator) CreateRandomPost(c request.CTX) (*model.Post, error) { return cfg.CreateRandomPostNested(c, "") } -func (cfg *AutoPostCreator) CreateRandomPostNested(c *request.Context, rootId string) (*model.Post, error) { +func (cfg *AutoPostCreator) CreateRandomPostNested(c request.CTX, rootId string) (*model.Post, error) { var fileIDs []string if cfg.HasImage { var err error diff --git a/app/slashcommands/auto_users.go b/app/slashcommands/auto_users.go index d100f32420..f7a9c588e0 100644 --- a/app/slashcommands/auto_users.go +++ b/app/slashcommands/auto_users.go @@ -78,7 +78,7 @@ func CreateBasicUser(a *app.App, client *model.Client4) error { return nil } -func (cfg *AutoUserCreator) createRandomUser(c *request.Context) (*model.User, error) { +func (cfg *AutoUserCreator) createRandomUser(c request.CTX) (*model.User, error) { var userEmail string var userName string if cfg.Fuzzy { @@ -113,7 +113,7 @@ func (cfg *AutoUserCreator) createRandomUser(c *request.Context) (*model.User, e return ruser, nil } -func (cfg *AutoUserCreator) CreateTestUsers(c *request.Context, num utils.Range) ([]*model.User, error) { +func (cfg *AutoUserCreator) CreateTestUsers(c request.CTX, num utils.Range) ([]*model.User, error) { numUsers := utils.RandIntFromRange(num) users := make([]*model.User, numUsers) diff --git a/app/slashcommands/command_away.go b/app/slashcommands/command_away.go index c49877031b..7ae0327849 100644 --- a/app/slashcommands/command_away.go +++ b/app/slashcommands/command_away.go @@ -34,7 +34,7 @@ func (*AwayProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*AwayProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*AwayProvider) DoCommand(a *app.App, _ request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { a.SetStatusAwayIfNeeded(args.UserId, true) return &model.CommandResponse{ResponseType: model.CommandResponseTypeEphemeral, Text: args.T("api.command_away.success")} diff --git a/app/slashcommands/command_channel_header.go b/app/slashcommands/command_channel_header.go index 4096174190..2c5f2555a8 100644 --- a/app/slashcommands/command_channel_header.go +++ b/app/slashcommands/command_channel_header.go @@ -35,7 +35,7 @@ func (*HeaderProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comma } } -func (*HeaderProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*HeaderProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { channel, err := a.GetChannel(c, args.ChannelId) if err != nil { return &model.CommandResponse{ diff --git a/app/slashcommands/command_channel_purpose.go b/app/slashcommands/command_channel_purpose.go index dfe0824470..32307f1c50 100644 --- a/app/slashcommands/command_channel_purpose.go +++ b/app/slashcommands/command_channel_purpose.go @@ -35,7 +35,7 @@ func (*PurposeProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comm } } -func (*PurposeProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*PurposeProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { channel, err := a.GetChannel(c, args.ChannelId) if err != nil { return &model.CommandResponse{ diff --git a/app/slashcommands/command_channel_rename.go b/app/slashcommands/command_channel_rename.go index ec0655b3a9..8b3009c918 100644 --- a/app/slashcommands/command_channel_rename.go +++ b/app/slashcommands/command_channel_rename.go @@ -38,7 +38,7 @@ func (*RenameProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comma } } -func (*RenameProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*RenameProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { channel, err := a.GetChannel(c, args.ChannelId) if err != nil { return &model.CommandResponse{ diff --git a/app/slashcommands/command_code.go b/app/slashcommands/command_code.go index 0add5d18ee..ca15636d07 100644 --- a/app/slashcommands/command_code.go +++ b/app/slashcommands/command_code.go @@ -37,7 +37,7 @@ func (*CodeProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*CodeProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*CodeProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if message == "" { return &model.CommandResponse{Text: args.T("api.command_code.message.app_error"), ResponseType: model.CommandResponseTypeEphemeral} } diff --git a/app/slashcommands/command_custom_status.go b/app/slashcommands/command_custom_status.go index 74749b5f85..0353d46132 100644 --- a/app/slashcommands/command_custom_status.go +++ b/app/slashcommands/command_custom_status.go @@ -41,7 +41,7 @@ func (*CustomStatusProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model } } -func (*CustomStatusProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*CustomStatusProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if !*a.Config().TeamSettings.EnableCustomUserStatuses { return nil } diff --git a/app/slashcommands/command_dnd.go b/app/slashcommands/command_dnd.go index 7a2d4aa01f..14e85503f2 100644 --- a/app/slashcommands/command_dnd.go +++ b/app/slashcommands/command_dnd.go @@ -34,7 +34,7 @@ func (*DndProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*DndProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*DndProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { a.SetStatusDoNotDisturb(args.UserId) return &model.CommandResponse{ResponseType: model.CommandResponseTypeEphemeral, Text: args.T("api.command_dnd.success")} diff --git a/app/slashcommands/command_echo.go b/app/slashcommands/command_echo.go index 3b66a73384..fff24ff886 100644 --- a/app/slashcommands/command_echo.go +++ b/app/slashcommands/command_echo.go @@ -42,7 +42,7 @@ func (*EchoProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*EchoProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*EchoProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if message == "" { return &model.CommandResponse{Text: args.T("api.command_echo.message.app_error"), ResponseType: model.CommandResponseTypeEphemeral} } diff --git a/app/slashcommands/command_expand_collapse.go b/app/slashcommands/command_expand_collapse.go index 056725e440..e9c6414d95 100644 --- a/app/slashcommands/command_expand_collapse.go +++ b/app/slashcommands/command_expand_collapse.go @@ -11,7 +11,6 @@ import ( "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/i18n" - "github.com/mattermost/mattermost-server/v6/shared/mlog" ) type ExpandProvider struct { @@ -56,11 +55,11 @@ func (*CollapseProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Com } } -func (*ExpandProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*ExpandProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { return setCollapsePreference(a, args, false) } -func (*CollapseProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*CollapseProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { return setCollapsePreference(a, args, true) } @@ -73,14 +72,14 @@ func setCollapsePreference(a *app.App, args *model.CommandArgs, isCollapse bool) } if err := a.Srv().Store.Preference().Save(model.Preferences{pref}); err != nil { - return &model.CommandResponse{Text: args.T("api.command_expand_collapse.fail.app_error"), ResponseType: model.CommandResponseTypeEphemeral} + return &model.CommandResponse{Text: args.T("api.command_expand_collapse.fail.app_error") + err.Error(), ResponseType: model.CommandResponseTypeEphemeral} } socketMessage := model.NewWebSocketEvent(model.WebsocketEventPreferenceChanged, "", "", args.UserId, nil) prefJSON, jsonErr := json.Marshal(pref) if jsonErr != nil { - mlog.Warn("Failed to encode to JSON", mlog.Err(jsonErr)) + return &model.CommandResponse{Text: args.T("api.marshal_error") + jsonErr.Error(), ResponseType: model.CommandResponseTypeEphemeral} } socketMessage.Add("preference", string(prefJSON)) a.Publish(socketMessage) diff --git a/app/slashcommands/command_groupmsg.go b/app/slashcommands/command_groupmsg.go index c728c2330b..2d57b9bf3b 100644 --- a/app/slashcommands/command_groupmsg.go +++ b/app/slashcommands/command_groupmsg.go @@ -39,7 +39,7 @@ func (*groupmsgProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Com } } -func (*groupmsgProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*groupmsgProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { targetUsers := map[string]*model.User{} targetUsersSlice := []string{args.UserId} invalidUsernames := []string{} diff --git a/app/slashcommands/command_help.go b/app/slashcommands/command_help.go index 2e627be706..a319e7f6be 100644 --- a/app/slashcommands/command_help.go +++ b/app/slashcommands/command_help.go @@ -34,7 +34,7 @@ func (h *HelpProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comma } } -func (h *HelpProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (h *HelpProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { helpLink := *a.Config().SupportSettings.HelpLink if helpLink == "" { diff --git a/app/slashcommands/command_invite.go b/app/slashcommands/command_invite.go index 60f217cd3e..24c569c8a3 100644 --- a/app/slashcommands/command_invite.go +++ b/app/slashcommands/command_invite.go @@ -38,7 +38,7 @@ func (*InviteProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comma } } -func (*InviteProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*InviteProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if message == "" { return &model.CommandResponse{ Text: args.T("api.command_invite.missing_message.app_error"), diff --git a/app/slashcommands/command_invite_people.go b/app/slashcommands/command_invite_people.go index 6ec723f6f2..b9acedaac9 100644 --- a/app/slashcommands/command_invite_people.go +++ b/app/slashcommands/command_invite_people.go @@ -42,7 +42,7 @@ func (*InvitePeopleProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model } } -func (*InvitePeopleProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*InvitePeopleProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if !a.HasPermissionToTeam(args.UserId, args.TeamId, model.PermissionInviteUser) { return &model.CommandResponse{Text: args.T("api.command_invite_people.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral} } diff --git a/app/slashcommands/command_join.go b/app/slashcommands/command_join.go index d15493217d..504d631630 100644 --- a/app/slashcommands/command_join.go +++ b/app/slashcommands/command_join.go @@ -37,7 +37,7 @@ func (*JoinProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*JoinProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*JoinProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { channelName := strings.ToLower(message) if strings.HasPrefix(message, "~") { diff --git a/app/slashcommands/command_leave.go b/app/slashcommands/command_leave.go index 2010fc305d..2e9047d29a 100644 --- a/app/slashcommands/command_leave.go +++ b/app/slashcommands/command_leave.go @@ -34,7 +34,7 @@ func (*LeaveProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comman } } -func (*LeaveProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*LeaveProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { var channel *model.Channel var noChannelErr *model.AppError if channel, noChannelErr = a.GetChannel(c, args.ChannelId); noChannelErr != nil { diff --git a/app/slashcommands/command_loadtest.go b/app/slashcommands/command_loadtest.go index 3fcf0d1037..d953ddd9fb 100644 --- a/app/slashcommands/command_loadtest.go +++ b/app/slashcommands/command_loadtest.go @@ -115,16 +115,16 @@ func (*LoadTestProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Com } } -func (lt *LoadTestProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (lt *LoadTestProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { commandResponse, err := lt.doCommand(a, c, args, message) if err != nil { - mlog.Error("failed command /"+CmdTest, mlog.Err(err)) + c.Logger().Error("failed command /"+CmdTest, mlog.Err(err)) } return commandResponse } -func (lt *LoadTestProvider) doCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (lt *LoadTestProvider) doCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { //This command is only available when EnableTesting is true if !*a.Config().ServiceSettings.EnableTesting { return &model.CommandResponse{}, nil @@ -181,7 +181,7 @@ func (*LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *m return &model.CommandResponse{Text: usage, ResponseType: model.CommandResponseTypeEphemeral} } -func (*LoadTestProvider) SetupCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) SetupCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { tokens := strings.Fields(strings.TrimPrefix(message, "setup")) doTeams := contains(tokens, "teams") doFuzz := contains(tokens, "fuzz") @@ -242,10 +242,10 @@ func (*LoadTestProvider) SetupCommand(a *app.App, c *request.Context, args *mode return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.CommandResponseTypeEphemeral}, err } - mlog.Info("Testing environment created") + c.Logger().Info("Testing environment created") for i := 0; i < len(environment.Teams); i++ { - mlog.Info("Team Created: " + environment.Teams[i].Name) - mlog.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + UserPassword) + c.Logger().Info("Team Created: " + environment.Teams[i].Name) + c.Logger().Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + UserPassword) } } else { team, err := a.Srv().Store.Team().Get(args.TeamId) @@ -267,7 +267,7 @@ func (*LoadTestProvider) SetupCommand(a *app.App, c *request.Context, args *mode return &model.CommandResponse{Text: "Created environment", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) ActivateUserCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) ActivateUserCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { user_id := strings.TrimSpace(strings.TrimPrefix(message, "activate_user")) if err := a.UpdateUserActive(c, user_id, true); err != nil { return &model.CommandResponse{Text: "Failed to activate user", ResponseType: model.CommandResponseTypeEphemeral}, err @@ -276,7 +276,7 @@ func (*LoadTestProvider) ActivateUserCommand(a *app.App, c *request.Context, arg return &model.CommandResponse{Text: "Activated user", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) DeActivateUserCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) DeActivateUserCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { user_id := strings.TrimSpace(strings.TrimPrefix(message, "deactivate_user")) if err := a.UpdateUserActive(c, user_id, false); err != nil { return &model.CommandResponse{Text: "Failed to deactivate user", ResponseType: model.CommandResponseTypeEphemeral}, err @@ -285,7 +285,7 @@ func (*LoadTestProvider) DeActivateUserCommand(a *app.App, c *request.Context, a return &model.CommandResponse{Text: "DeActivated user", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) UsersCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) UsersCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "users")) doFuzz := false @@ -314,7 +314,7 @@ func (*LoadTestProvider) UsersCommand(a *app.App, c *request.Context, args *mode return &model.CommandResponse{Text: "Added users", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) ChannelsCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) ChannelsCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels")) doFuzz := false @@ -342,7 +342,7 @@ func (*LoadTestProvider) ChannelsCommand(a *app.App, c *request.Context, args *m return &model.CommandResponse{Text: "Added channels", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) DMsCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) DMsCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "dms")) channelsr, ok := parseRange(cmd, "") @@ -358,7 +358,7 @@ func (*LoadTestProvider) DMsCommand(a *app.App, c *request.Context, args *model. return &model.CommandResponse{Text: "Added DMs", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) ThreadedPostCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) ThreadedPostCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { var usernames []string options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000} if profileUsers, err := a.Srv().Store.User().GetProfiles(options); err == nil { @@ -384,7 +384,7 @@ func (*LoadTestProvider) ThreadedPostCommand(a *app.App, c *request.Context, arg return &model.CommandResponse{Text: "Added threaded post", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) PostsCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) PostsCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) doFuzz := false @@ -486,7 +486,7 @@ func (*LoadTestProvider) PostCommand(a *app.App, c request.CTX, args *model.Comm return &model.CommandResponse{Text: "Added a post to " + channel.DisplayName, ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) URLCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) URLCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { url := strings.TrimSpace(strings.TrimPrefix(message, "url")) if url == "" { return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.CommandResponseTypeEphemeral}, nil @@ -540,7 +540,7 @@ func (*LoadTestProvider) URLCommand(a *app.App, c *request.Context, args *model. return &model.CommandResponse{Text: "Loaded data", ResponseType: model.CommandResponseTypeEphemeral}, nil } -func (*LoadTestProvider) JsonCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) (*model.CommandResponse, error) { +func (*LoadTestProvider) JsonCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { url := strings.TrimSpace(strings.TrimPrefix(message, "json")) if url == "" { return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.CommandResponseTypeEphemeral}, nil diff --git a/app/slashcommands/command_logout.go b/app/slashcommands/command_logout.go index 645a8534a3..40bfed52d0 100644 --- a/app/slashcommands/command_logout.go +++ b/app/slashcommands/command_logout.go @@ -35,7 +35,7 @@ func (*LogoutProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comma } } -func (*LogoutProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*LogoutProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { // Actual logout is handled client side. return &model.CommandResponse{GotoLocation: "/login"} } diff --git a/app/slashcommands/command_me.go b/app/slashcommands/command_me.go index a6f3a2db37..a583e3390a 100644 --- a/app/slashcommands/command_me.go +++ b/app/slashcommands/command_me.go @@ -35,7 +35,7 @@ func (*MeProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command { } } -func (*MeProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*MeProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeInChannel, Type: model.PostTypeMe, diff --git a/app/slashcommands/command_msg.go b/app/slashcommands/command_msg.go index e64379ee23..b801c4a34c 100644 --- a/app/slashcommands/command_msg.go +++ b/app/slashcommands/command_msg.go @@ -40,7 +40,7 @@ func (*msgProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*msgProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*msgProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { splitMessage := strings.SplitN(message, " ", 2) parsedMessage := "" diff --git a/app/slashcommands/command_mute.go b/app/slashcommands/command_mute.go index 96319b86fc..747f86d1e7 100644 --- a/app/slashcommands/command_mute.go +++ b/app/slashcommands/command_mute.go @@ -37,7 +37,7 @@ func (*MuteProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*MuteProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*MuteProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { var channel *model.Channel var noChannelErr *model.AppError diff --git a/app/slashcommands/command_offline.go b/app/slashcommands/command_offline.go index f89cb85c3f..b532bb91bd 100644 --- a/app/slashcommands/command_offline.go +++ b/app/slashcommands/command_offline.go @@ -34,7 +34,7 @@ func (*OfflineProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comm } } -func (*OfflineProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*OfflineProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { a.SetStatusOffline(args.UserId, true) return &model.CommandResponse{ResponseType: model.CommandResponseTypeEphemeral, Text: args.T("api.command_offline.success")} diff --git a/app/slashcommands/command_online.go b/app/slashcommands/command_online.go index ab43cb07b5..5fb45c1d46 100644 --- a/app/slashcommands/command_online.go +++ b/app/slashcommands/command_online.go @@ -34,7 +34,7 @@ func (*OnlineProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comma } } -func (*OnlineProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*OnlineProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { a.SetStatusOnline(args.UserId, true) return &model.CommandResponse{ResponseType: model.CommandResponseTypeEphemeral, Text: args.T("api.command_online.success")} diff --git a/app/slashcommands/command_remote.go b/app/slashcommands/command_remote.go index a17737a8ae..bc0b3d03e2 100644 --- a/app/slashcommands/command_remote.go +++ b/app/slashcommands/command_remote.go @@ -69,7 +69,7 @@ func (rp *RemoteProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Co } } -func (rp *RemoteProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (rp *RemoteProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if !a.HasPermissionTo(args.UserId, model.PermissionManageSecureConnections) { return responsef(args.T("api.command_remote.permission_required", map[string]any{"Permission": "manage_secure_connections"})) } diff --git a/app/slashcommands/command_remove.go b/app/slashcommands/command_remove.go index ce95e67a58..935136f8fe 100644 --- a/app/slashcommands/command_remove.go +++ b/app/slashcommands/command_remove.go @@ -57,15 +57,15 @@ func (*KickProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command } } -func (*RemoveProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*RemoveProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { return doCommand(a, c, args, message) } -func (*KickProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*KickProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { return doCommand(a, c, args, message) } -func doCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func doCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { channel, err := a.GetChannel(c, args.ChannelId) if err != nil { return &model.CommandResponse{ diff --git a/app/slashcommands/command_search.go b/app/slashcommands/command_search.go index 2bc8dc3644..025f3ce5b6 100644 --- a/app/slashcommands/command_search.go +++ b/app/slashcommands/command_search.go @@ -35,7 +35,7 @@ func (search *SearchProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *mode } } -func (search *SearchProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (search *SearchProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { // This command is handled client-side and shouldn't hit the server. return &model.CommandResponse{ Text: args.T("api.command_search.unsupported.app_error"), diff --git a/app/slashcommands/command_settings.go b/app/slashcommands/command_settings.go index 7904be535c..7d479954ee 100644 --- a/app/slashcommands/command_settings.go +++ b/app/slashcommands/command_settings.go @@ -35,7 +35,7 @@ func (settings *SettingsProvider) GetCommand(a *app.App, T i18n.TranslateFunc) * } } -func (settings *SettingsProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (settings *SettingsProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { // This command is handled client-side and shouldn't hit the server. return &model.CommandResponse{ Text: args.T("api.command_settings.unsupported.app_error"), diff --git a/app/slashcommands/command_share.go b/app/slashcommands/command_share.go index 5d526f8640..997411fa31 100644 --- a/app/slashcommands/command_share.go +++ b/app/slashcommands/command_share.go @@ -120,7 +120,7 @@ func (sp *ShareProvider) getAutoCompleteUnInviteRemote(a *app.App, _ *model.Comm } } -func (sp *ShareProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (sp *ShareProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { if !a.HasPermissionTo(args.UserId, model.PermissionManageSharedChannels) { return responsef(args.T("api.command_share.permission_required", map[string]any{"Permission": "manage_shared_channels"})) } diff --git a/app/slashcommands/command_shortcuts.go b/app/slashcommands/command_shortcuts.go index a4f06b028b..cae126476f 100644 --- a/app/slashcommands/command_shortcuts.go +++ b/app/slashcommands/command_shortcuts.go @@ -35,7 +35,7 @@ func (*ShortcutsProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Co } } -func (*ShortcutsProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*ShortcutsProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { // This command is handled client-side and shouldn't hit the server. return &model.CommandResponse{ Text: args.T("api.command_shortcuts.unsupported.app_error"), diff --git a/app/slashcommands/command_shrug.go b/app/slashcommands/command_shrug.go index 13a9654876..954b0f635a 100644 --- a/app/slashcommands/command_shrug.go +++ b/app/slashcommands/command_shrug.go @@ -35,7 +35,7 @@ func (*ShrugProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Comman } } -func (*ShrugProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse { +func (*ShrugProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { rmsg := `¯\\\_(ツ)\_/¯` if message != "" { rmsg = message + " " + rmsg diff --git a/app/slashcommands/command_test.go b/app/slashcommands/command_test.go index 0ed82c9778..720ea99380 100644 --- a/app/slashcommands/command_test.go +++ b/app/slashcommands/command_test.go @@ -525,7 +525,7 @@ func TestMentionsToTeamMembers(t *testing.T) { } for _, data := range fixture { - actualMap := th.App.MentionsToTeamMembers(data.message, data.inTeam) + actualMap := th.App.MentionsToTeamMembers(th.Context, data.message, data.inTeam) require.Equal(t, actualMap, data.expectedMap) } } diff --git a/app/slashcommands/helper_test.go b/app/slashcommands/helper_test.go index 32d9d4c4ce..c7fa8fcf3a 100644 --- a/app/slashcommands/helper_test.go +++ b/app/slashcommands/helper_test.go @@ -65,7 +65,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo options = append(options, app.ConfigStore(memoryStore)) if includeCacheLayer { options = append(options, app.StoreOverride(func(s *app.Server) store.Store { - lcl, err2 := localcachelayer.NewLocalCacheLayer(dbStore, s.Metrics, s.Cluster, s.CacheProvider) + lcl, err2 := localcachelayer.NewLocalCacheLayer(dbStore, s.GetMetrics(), s.Cluster, s.CacheProvider) if err2 != nil { panic(err2) } diff --git a/app/syncables.go b/app/syncables.go index 4362991713..8b0b4cdc43 100644 --- a/app/syncables.go +++ b/app/syncables.go @@ -18,7 +18,7 @@ import ( // only that channel's members are created. If channelID is nil all channel memberships are created. // If includeRemovedMembers is true, then channel members who left or were removed from the channel will // be re-added; otherwise, they will not be re-added. -func (a *App) createDefaultChannelMemberships(c *request.Context, since int64, channelID *string, includeRemovedMembers bool) error { +func (a *App) createDefaultChannelMemberships(c request.CTX, since int64, channelID *string, includeRemovedMembers bool) error { channelMembers, appErr := a.ChannelMembersToAdd(since, channelID, includeRemovedMembers) if appErr != nil { return appErr @@ -40,7 +40,7 @@ func (a *App) createDefaultChannelMemberships(c *request.Context, since int64, c _, err = a.AddTeamMember(c, channel.TeamId, userChannel.UserID) if err != nil { if err.Id == "api.team.join_user_to_team.allowed_domains.app_error" { - a.Log().Info("User not added to channel - the domain associated with the user is not in the list of allowed team domains", + c.Logger().Info("User not added to channel - the domain associated with the user is not in the list of allowed team domains", mlog.String("user_id", userChannel.UserID), mlog.String("channel_id", userChannel.ChannelID), mlog.String("team_id", channel.TeamId), @@ -49,7 +49,7 @@ func (a *App) createDefaultChannelMemberships(c *request.Context, since int64, c } return err } - a.Log().Info("added teammember", + c.Logger().Info("added teammember", mlog.String("user_id", userChannel.UserID), mlog.String("team_id", channel.TeamId), ) @@ -60,7 +60,7 @@ func (a *App) createDefaultChannelMemberships(c *request.Context, since int64, c }) if err != nil { if err.Id == "api.channel.add_user.to.channel.failed.deleted.app_error" { - a.Log().Info("Not adding user to channel because they have already left the team", + c.Logger().Info("Not adding user to channel because they have already left the team", mlog.String("user_id", userChannel.UserID), mlog.String("channel_id", userChannel.ChannelID), ) @@ -69,7 +69,7 @@ func (a *App) createDefaultChannelMemberships(c *request.Context, since int64, c } } - a.Log().Info("added channelmember", + c.Logger().Info("added channelmember", mlog.String("user_id", userChannel.UserID), mlog.String("channel_id", userChannel.ChannelID), ) @@ -83,7 +83,7 @@ func (a *App) createDefaultChannelMemberships(c *request.Context, since int64, c // only that team's members are created. If teamID is nil all team memberships are created. // If includeRemovedMembers is true, then team members who left or were removed from the team will // be re-added; otherwise, they will not be re-added. -func (a *App) createDefaultTeamMemberships(c *request.Context, since int64, teamID *string, includeRemovedMembers bool) error { +func (a *App) createDefaultTeamMemberships(c request.CTX, since int64, teamID *string, includeRemovedMembers bool) error { teamMembers, appErr := a.TeamMembersToAdd(since, teamID, includeRemovedMembers) if appErr != nil { return appErr @@ -93,7 +93,7 @@ func (a *App) createDefaultTeamMemberships(c *request.Context, since int64, team _, err := a.AddTeamMember(c, userTeam.TeamID, userTeam.UserID) if err != nil { if err.Id == "api.team.join_user_to_team.allowed_domains.app_error" { - a.Log().Info("User not added to team - the domain associated with the user is not in the list of allowed team domains", + c.Logger().Info("User not added to team - the domain associated with the user is not in the list of allowed team domains", mlog.String("user_id", userTeam.UserID), mlog.String("team_id", userTeam.TeamID), ) @@ -102,7 +102,7 @@ func (a *App) createDefaultTeamMemberships(c *request.Context, since int64, team return err } - a.Log().Info("added teammember", + c.Logger().Info("added teammember", mlog.String("user_id", userTeam.UserID), mlog.String("team_id", userTeam.TeamID), ) @@ -148,7 +148,7 @@ func (a *App) DeleteGroupConstrainedMemberships(c *request.Context) error { // deleteGroupConstrainedTeamMemberships deletes team memberships of users who aren't members of the allowed // groups of the given group-constrained team. If a teamID is given then the procedure is scoped to the given team, // if teamID is nil then the procedure affects all teams. -func (a *App) deleteGroupConstrainedTeamMemberships(c *request.Context, teamID *string) error { +func (a *App) deleteGroupConstrainedTeamMemberships(c request.CTX, teamID *string) error { teamMembers, appErr := a.TeamMembersToRemove(teamID) if appErr != nil { return appErr @@ -160,7 +160,7 @@ func (a *App) deleteGroupConstrainedTeamMemberships(c *request.Context, teamID * return err } - a.Log().Info("removed teammember", + c.Logger().Info("removed teammember", mlog.String("user_id", userTeam.UserId), mlog.String("team_id", userTeam.TeamId), ) @@ -172,7 +172,7 @@ func (a *App) deleteGroupConstrainedTeamMemberships(c *request.Context, teamID * // deleteGroupConstrainedChannelMemberships deletes channel memberships of users who aren't members of the allowed // groups of the given group-constrained channel. If a channelID is given then the procedure is scoped to the given team, // if channelID is nil then the procedure affects all teams. -func (a *App) deleteGroupConstrainedChannelMemberships(c *request.Context, channelID *string) error { +func (a *App) deleteGroupConstrainedChannelMemberships(c request.CTX, channelID *string) error { channelMembers, appErr := a.ChannelMembersToRemove(channelID) if appErr != nil { return appErr @@ -233,7 +233,7 @@ func (a *App) SyncSyncableRoles(syncableID string, syncableType model.GroupSynca // SyncRolesAndMembership updates the SchemeAdmin status and membership of all of the members of the given // syncable. -func (a *App) SyncRolesAndMembership(c *request.Context, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) { +func (a *App) SyncRolesAndMembership(c request.CTX, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) { a.SyncSyncableRoles(syncableID, syncableType) lastJob, _ := a.Srv().Store.Job().GetNewestJobByStatusAndType(model.JobStatusSuccess, model.JobTypeLdapSync) @@ -246,10 +246,14 @@ func (a *App) SyncRolesAndMembership(c *request.Context, syncableID string, sync case model.GroupSyncableTypeTeam: a.createDefaultTeamMemberships(c, since, &syncableID, includeRemovedMembers) a.deleteGroupConstrainedTeamMemberships(c, &syncableID) - a.ClearTeamMembersCache(syncableID) + if err := a.ClearTeamMembersCache(syncableID); err != nil { + c.Logger().Warn("Error clearing team members cache", mlog.Err(err)) + } case model.GroupSyncableTypeChannel: a.createDefaultChannelMemberships(c, since, &syncableID, includeRemovedMembers) a.deleteGroupConstrainedChannelMemberships(c, &syncableID) - a.ClearChannelMembersCache(c, syncableID) + if err := a.ClearChannelMembersCache(c, syncableID); err != nil { + c.Logger().Warn("Error clearing channel members cache", mlog.Err(err)) + } } } diff --git a/app/team.go b/app/team.go index 6240f4b932..bf9425c145 100644 --- a/app/team.go +++ b/app/team.go @@ -231,7 +231,9 @@ func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) { } } - a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeam) + if appErr := a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeam); appErr != nil { + return nil, appErr + } return oldTeam, nil } @@ -301,9 +303,14 @@ func (a *App) UpdateTeamScheme(team *model.Team) (*model.Team, *model.AppError) } } - a.ClearTeamMembersCache(team.Id) + nErr = a.ClearTeamMembersCache(team.Id) + if nErr != nil { + return nil, model.NewAppError("UpdateTeamScheme", "app.team.clear_cache.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + } - a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeamScheme) + if appErr := a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeamScheme); appErr != nil { + return nil, appErr + } return oldTeam, nil } @@ -336,7 +343,9 @@ func (a *App) UpdateTeamPrivacy(teamID string, teamType string, allowOpenInvite } } - a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeam) + if appErr := a.sendTeamEvent(oldTeam, model.WebsocketEventUpdateTeam); appErr != nil { + return appErr + } return nil } @@ -362,7 +371,9 @@ func (a *App) PatchTeam(teamID string, patch *model.TeamPatch) (*model.Team, *mo } } - a.sendTeamEvent(team, model.WebsocketEventUpdateTeam) + if appErr := a.sendTeamEvent(team, model.WebsocketEventUpdateTeam); appErr != nil { + return nil, appErr + } return team, nil } @@ -389,12 +400,14 @@ func (a *App) RegenerateTeamInviteId(teamID string) (*model.Team, *model.AppErro } } - a.sendTeamEvent(updatedTeam, model.WebsocketEventUpdateTeam) + if appErr := a.sendTeamEvent(updatedTeam, model.WebsocketEventUpdateTeam); appErr != nil { + return nil, appErr + } return updatedTeam, nil } -func (a *App) sendTeamEvent(team *model.Team, event string) { +func (a *App) sendTeamEvent(team *model.Team, event string) *model.AppError { sanitizedTeam := &model.Team{} *sanitizedTeam = *team sanitizedTeam.Sanitize() @@ -407,10 +420,11 @@ func (a *App) sendTeamEvent(team *model.Team, event string) { message := model.NewWebSocketEvent(event, teamID, "", "", nil) teamJSON, jsonErr := json.Marshal(team) if jsonErr != nil { - mlog.Warn("Failed to encode team to JSON", mlog.Err(jsonErr)) + return model.NewAppError("sendTeamEvent", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("team", string(teamJSON)) a.Publish(message) + return nil } func (a *App) GetSchemeRolesForTeam(teamID string) (string, string, string, *model.AppError) { @@ -507,7 +521,9 @@ func (a *App) UpdateTeamMemberRoles(teamID string, userID string, newRoles strin a.ClearSessionCacheForUser(userID) - a.sendUpdatedMemberRoleEvent(userID, member) + if appErr := a.sendUpdatedMemberRoleEvent(userID, member); appErr != nil { + return nil, appErr + } return member, nil } @@ -544,22 +560,25 @@ func (a *App) UpdateTeamMemberSchemeRoles(teamID string, userID string, isScheme a.ClearSessionCacheForUser(userID) - a.sendUpdatedMemberRoleEvent(userID, member) + if appErr := a.sendUpdatedMemberRoleEvent(userID, member); appErr != nil { + return nil, appErr + } return member, nil } -func (a *App) sendUpdatedMemberRoleEvent(userID string, member *model.TeamMember) { +func (a *App) sendUpdatedMemberRoleEvent(userID string, member *model.TeamMember) *model.AppError { message := model.NewWebSocketEvent(model.WebsocketEventMemberroleUpdated, "", "", userID, nil) tmJSON, jsonErr := json.Marshal(member) if jsonErr != nil { - mlog.Warn("Failed to encode team member to JSON", mlog.Err(jsonErr)) + return model.NewAppError("sendUpdatedMemberRoleEvent", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("member", string(tmJSON)) a.Publish(message) + return nil } -func (a *App) AddUserToTeam(c *request.Context, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) { +func (a *App) AddUserToTeam(c request.CTX, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError) { tchan := make(chan store.StoreResult, 1) go func() { team, err := a.Srv().Store.Team().Get(teamID) @@ -1059,7 +1078,7 @@ func (a *App) GetCommonTeamIDsForTwoUsers(userID, otherUserID string) ([]string, return teamIDs, nil } -func (a *App) AddTeamMember(c *request.Context, teamID, userID string) (*model.TeamMember, *model.AppError) { +func (a *App) AddTeamMember(c request.CTX, teamID, userID string) (*model.TeamMember, *model.AppError) { _, teamMember, err := a.AddUserToTeam(c, teamID, userID, "") if err != nil { return nil, err @@ -1152,7 +1171,7 @@ func (a *App) GetTeamUnread(teamID, userID string) (*model.TeamUnread, *model.Ap return teamUnread, nil } -func (a *App) RemoveUserFromTeam(c *request.Context, teamID string, userID string, requestorId string) *model.AppError { +func (a *App) RemoveUserFromTeam(c request.CTX, teamID string, userID string, requestorId string) *model.AppError { tchan := make(chan store.StoreResult, 1) go func() { team, err := a.Srv().Store.Team().Get(teamID) @@ -1245,7 +1264,7 @@ func (a *App) postProcessTeamMemberLeave(c request.CTX, teamMember *model.TeamMe return nil } -func (a *App) LeaveTeam(c *request.Context, team *model.Team, user *model.User, requestorId string) *model.AppError { +func (a *App) LeaveTeam(c request.CTX, team *model.Team, user *model.User, requestorId string) *model.AppError { teamMember, err := a.GetTeamMember(team.Id, user.Id) if err != nil { return model.NewAppError("LeaveTeam", "api.team.remove_user_from_team.missing.app_error", nil, err.Error(), http.StatusBadRequest) @@ -1288,11 +1307,11 @@ func (a *App) LeaveTeam(c *request.Context, team *model.Team, user *model.User, if requestorId == user.Id { if err = a.postLeaveTeamMessage(c, user, channel); err != nil { - mlog.Warn("Failed to post join/leave message", mlog.Err(err)) + c.Logger().Warn("Failed to post join/leave message", mlog.Err(err)) } } else { if err = a.postRemoveFromTeamMessage(c, user, channel); err != nil { - mlog.Warn("Failed to post join/leave message", mlog.Err(err)) + c.Logger().Warn("Failed to post join/leave message", mlog.Err(err)) } } } @@ -1308,7 +1327,7 @@ func (a *App) LeaveTeam(c *request.Context, team *model.Team, user *model.User, return nil } -func (a *App) postLeaveTeamMessage(c *request.Context, user *model.User, channel *model.Channel) *model.AppError { +func (a *App) postLeaveTeamMessage(c request.CTX, user *model.User, channel *model.Channel) *model.AppError { post := &model.Post{ ChannelId: channel.Id, Message: fmt.Sprintf(i18n.T("api.team.leave.left"), user.Username), @@ -1326,7 +1345,7 @@ func (a *App) postLeaveTeamMessage(c *request.Context, user *model.User, channel return nil } -func (a *App) postRemoveFromTeamMessage(c *request.Context, user *model.User, channel *model.Channel) *model.AppError { +func (a *App) postRemoveFromTeamMessage(c request.CTX, user *model.User, channel *model.Channel) *model.AppError { post := &model.Post{ ChannelId: channel.Id, Message: fmt.Sprintf(i18n.T("api.team.remove_user_from_team.removed"), user.Username), @@ -1797,7 +1816,9 @@ func (a *App) PermanentDeleteTeam(c request.CTX, team *model.Team) *model.AppErr return model.NewAppError("PermanentDeleteTeam", "app.team.permanent_delete.app_error", nil, err.Error(), http.StatusInternalServerError) } - a.sendTeamEvent(team, model.WebsocketEventDeleteTeam) + if appErr := a.sendTeamEvent(team, model.WebsocketEventDeleteTeam); appErr != nil { + return appErr + } return nil } @@ -1823,7 +1844,9 @@ func (a *App) SoftDeleteTeam(teamID string) *model.AppError { } } - a.sendTeamEvent(team, model.WebsocketEventDeleteTeam) + if appErr := a.sendTeamEvent(team, model.WebsocketEventDeleteTeam); appErr != nil { + return appErr + } return nil } @@ -1849,7 +1872,10 @@ func (a *App) RestoreTeam(teamID string) *model.AppError { } } - a.sendTeamEvent(team, model.WebsocketEventRestoreTeam) + if appErr := a.sendTeamEvent(team, model.WebsocketEventRestoreTeam); appErr != nil { + return appErr + } + return nil } @@ -2022,7 +2048,9 @@ func (a *App) SetTeamIconFromFile(team *model.Team, file io.Reader) *model.AppEr // manually set time to avoid possible cluster inconsistencies team.LastTeamIconUpdate = curTime - a.sendTeamEvent(team, model.WebsocketEventUpdateTeam) + if appErr := a.sendTeamEvent(team, model.WebsocketEventUpdateTeam); appErr != nil { + return appErr + } return nil } @@ -2039,7 +2067,9 @@ func (a *App) RemoveTeamIcon(teamID string) *model.AppError { team.LastTeamIconUpdate = 0 - a.sendTeamEvent(team, model.WebsocketEventUpdateTeam) + if appErr := a.sendTeamEvent(team, model.WebsocketEventUpdateTeam); appErr != nil { + return appErr + } return nil } @@ -2072,15 +2102,14 @@ func (a *App) InvalidateAllResendInviteEmailJobs() *model.AppError { return nil } -func (a *App) ClearTeamMembersCache(teamID string) { +func (a *App) ClearTeamMembersCache(teamID string) error { perPage := 100 page := 0 for { teamMembers, err := a.Srv().Store.Team().GetMembers(teamID, page*perPage, perPage, nil) if err != nil { - a.Log().Warn("error clearing cache for team members", mlog.String("team_id", teamID), mlog.String("err", err.Error())) - break + return fmt.Errorf("failed to get team members: %v", err) } for _, teamMember := range teamMembers { @@ -2089,7 +2118,7 @@ func (a *App) ClearTeamMembersCache(teamID string) { message := model.NewWebSocketEvent(model.WebsocketEventMemberroleUpdated, "", "", teamMember.UserId, nil) tmJSON, jsonErr := json.Marshal(teamMember) if jsonErr != nil { - mlog.Warn("Failed to encode team member to JSON", mlog.Err(jsonErr)) + return jsonErr } message.Add("member", string(tmJSON)) a.Publish(message) @@ -2102,4 +2131,5 @@ func (a *App) ClearTeamMembersCache(teamID string) { page++ } + return nil } diff --git a/app/team_test.go b/app/team_test.go index 9423927cec..bb69aa762c 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -1433,7 +1433,7 @@ func TestClearTeamMembersCache(t *testing.T) { mockStore.On("Team").Return(&mockTeamStore) mockStore.On("GetDBSchemaVersion").Return(1, nil) - th.App.ClearTeamMembersCache("teamID") + require.NoError(t, th.App.ClearTeamMembersCache("teamID")) } func TestInviteNewUsersToTeamGracefully(t *testing.T) { diff --git a/app/user.go b/app/user.go index edeef970ff..9d2a2eb3ac 100644 --- a/app/user.go +++ b/app/user.go @@ -883,7 +883,7 @@ func (a *App) UpdatePasswordAsUser(c request.CTX, userID, currentPassword, newPa return a.UpdatePasswordSendEmail(c, user, newPassword, T("api.user.update_password.menu")) } -func (a *App) userDeactivated(c *request.Context, userID string) *model.AppError { +func (a *App) userDeactivated(c request.CTX, userID string) *model.AppError { a.SetStatusOffline(userID, false) user, err := a.GetUser(userID) @@ -928,7 +928,7 @@ func (a *App) invalidateUserChannelMembersCaches(c request.CTX, userID string) * return nil } -func (a *App) UpdateActive(c *request.Context, user *model.User, active bool) (*model.User, *model.AppError) { +func (a *App) UpdateActive(c request.CTX, user *model.User, active bool) (*model.User, *model.AppError) { user.UpdateAt = model.GetMillis() if active { user.DeleteAt = 0 @@ -1220,7 +1220,7 @@ func (a *App) UpdateUser(c request.CTX, user *model.User, sendNotifications bool return userUpdate.New, nil } -func (a *App) UpdateUserActive(c *request.Context, userID string, active bool) *model.AppError { +func (a *App) UpdateUserActive(c request.CTX, userID string, active bool) *model.AppError { user, err := a.GetUser(userID) if err != nil { @@ -1416,7 +1416,6 @@ func (a *App) CreatePasswordRecoveryToken(userID, email string) (*model.Token, * email, } jsonData, err := json.Marshal(tokenExtra) - if err != nil { return nil, model.NewAppError("CreatePasswordRecoveryToken", "api.user.create_password_token.error", nil, "", http.StatusInternalServerError) } @@ -2196,7 +2195,7 @@ func (a *App) PromoteGuestToUser(c *request.Context, user *model.User, requestor evt := model.NewWebSocketEvent(model.WebsocketEventChannelMemberUpdated, "", "", user.Id, nil) memberJSON, jsonErr := json.Marshal(member) if jsonErr != nil { - c.Logger().Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr)) + return model.NewAppError("PromoteGuestToUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } evt.Add("channelMember", string(memberJSON)) a.Publish(evt) @@ -2241,7 +2240,7 @@ func (a *App) DemoteUserToGuest(c request.CTX, user *model.User) *model.AppError evt := model.NewWebSocketEvent(model.WebsocketEventChannelMemberUpdated, "", "", user.Id, nil) memberJSON, jsonErr := json.Marshal(member) if jsonErr != nil { - c.Logger().Warn("Failed to encode channel member to JSON", mlog.Err(jsonErr)) + return model.NewAppError("DemoteUserToGuest", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } evt.Add("channelMember", string(memberJSON)) a.Publish(evt) @@ -2518,7 +2517,7 @@ func (a *App) UpdateThreadFollowForUserFromChannelAdd(c request.CTX, userID, tea payload, jsonErr := json.Marshal(userThread) if jsonErr != nil { - c.Logger().Warn("Failed to encode thread to JSON") + return model.NewAppError("UpdateThreadFollowForUserFromChannelAdd", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) } message.Add("thread", string(payload)) message.Add("previous_unread_replies", int64(0)) diff --git a/app/web_hub.go b/app/web_hub.go index 703a27b278..a3dcf8d0cd 100644 --- a/app/web_hub.go +++ b/app/web_hub.go @@ -157,8 +157,8 @@ func (a *App) HubUnregister(webConn *WebConn) { } func (s *Server) Publish(message *model.WebSocketEvent) { - if s.Metrics != nil { - s.Metrics.IncrementWebsocketEvent(message.EventType()) + if s.GetMetrics() != nil { + s.GetMetrics().IncrementWebsocketEvent(message.EventType()) } s.PublishSkipClusterSend(message) @@ -357,7 +357,7 @@ func (h *Hub) Broadcast(message *model.WebSocketEvent) { // And possibly, we can look into doing the hub initialization inside // NewServer itself. if h != nil && message != nil { - if metrics := h.srv.Metrics; metrics != nil { + if metrics := h.srv.GetMetrics(); metrics != nil { metrics.IncrementWebSocketBroadcastBufferSize(strconv.Itoa(h.connectionIndex), 1) } select { @@ -525,7 +525,7 @@ func (h *Hub) Start() { connIndex.Remove(directMsg.conn) } case msg := <-h.broadcast: - if metrics := h.srv.Metrics; metrics != nil { + if metrics := h.srv.GetMetrics(); metrics != nil { metrics.DecrementWebSocketBroadcastBufferSize(strconv.Itoa(h.connectionIndex), 1) } msg = msg.PrecomputeJSON() diff --git a/cmd/mattermost/commands/export.go b/cmd/mattermost/commands/export.go index baac81757d..2fa5295986 100644 --- a/cmd/mattermost/commands/export.go +++ b/cmd/mattermost/commands/export.go @@ -10,6 +10,7 @@ import ( "path/filepath" "time" + "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/model" @@ -229,7 +230,7 @@ func bulkExportCmdF(command *cobra.Command, args []string) error { var opts model.BulkExportOpts opts.IncludeAttachments = attachments opts.CreateArchive = archive - if err := a.BulkExport(fileWriter, filepath.Dir(outPath), opts); err != nil { + if err := a.BulkExport(request.EmptyContext(a.Log()), fileWriter, filepath.Dir(outPath), opts); err != nil { CommandPrintErrorln(err.Error()) return err } diff --git a/i18n/en.json b/i18n/en.json index 2cc65b6ab7..f5e1f11fec 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2045,10 +2045,6 @@ "id": "api.license.request_trial_license.fail_get_user_count.app_error", "translation": "Unable to get a trial license, please try again or contact with support@mattermost.com. Cannot obtain the number of registered users." }, - { - "id": "api.license.request_trial_license.no-site-url.app_error", - "translation": "Unable to request a trial license. Please configure a Site URL in the web server section of the Mattermost System Console." - }, { "id": "api.license.upgrade_needed.app_error", "translation": "Feature requires an upgrade to Enterprise Edition." @@ -6155,6 +6151,10 @@ "id": "app.team.clear_all_custom_role_assignments.select.app_error", "translation": "Failed to retrieve the team members." }, + { + "id": "app.team.clear_cache.app_error", + "translation": "Error clearing team member cache" + }, { "id": "app.team.get.find.app_error", "translation": "Unable to find the existing team." diff --git a/jobs/export_process/worker.go b/jobs/export_process/worker.go index ba6f5ecfe8..de5ee69489 100644 --- a/jobs/export_process/worker.go +++ b/jobs/export_process/worker.go @@ -7,6 +7,7 @@ import ( "io" "path/filepath" + "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/jobs" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/services/configservice" @@ -18,7 +19,8 @@ const jobName = "ExportProcess" type AppIface interface { configservice.ConfigService WriteFile(fr io.Reader, path string) (int64, *model.AppError) - BulkExport(writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError + BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError + Log() *mlog.Logger } func MakeWorker(jobServer *jobs.JobServer, app AppIface) model.Worker { @@ -45,7 +47,7 @@ func MakeWorker(jobServer *jobs.JobServer, app AppIface) model.Worker { errCh <- appErr }() - appErr := app.BulkExport(wr, outPath, opts) + appErr := app.BulkExport(request.EmptyContext(app.Log()), wr, outPath, opts) if err := wr.Close(); err != nil { mlog.Warn("Worker: error closing writer") } diff --git a/model/feature_flags.go b/model/feature_flags.go index f2f14bd563..8007f34587 100644 --- a/model/feature_flags.go +++ b/model/feature_flags.go @@ -98,11 +98,11 @@ func (f *FeatureFlags) SetDefaults() { f.GraphQL = false f.InsightsEnabled = true f.CommandPalette = false - f.PostForwarding = false + f.PostForwarding = true f.AdvancedTextEditor = true f.CallsEnabled = true f.BoardsProduct = false - f.PlanUpgradeButtonText = "Upgrade" + f.PlanUpgradeButtonText = "upgrade" } func (f *FeatureFlags) Plugins() map[string]string { diff --git a/services/sharedchannel/mock_AppIface_test.go b/services/sharedchannel/mock_AppIface_test.go index 2a3613cad2..f95df3ac6e 100644 --- a/services/sharedchannel/mock_AppIface_test.go +++ b/services/sharedchannel/mock_AppIface_test.go @@ -269,13 +269,13 @@ func (_m *MockAppIface) InvalidateCacheForUser(userID string) { _m.Called(userID) } -// MentionsToTeamMembers provides a mock function with given fields: message, teamID -func (_m *MockAppIface) MentionsToTeamMembers(message string, teamID string) model.UserMentionMap { - ret := _m.Called(message, teamID) +// MentionsToTeamMembers provides a mock function with given fields: c, message, teamID +func (_m *MockAppIface) MentionsToTeamMembers(c request.CTX, message string, teamID string) model.UserMentionMap { + ret := _m.Called(c, message, teamID) var r0 model.UserMentionMap - if rf, ok := ret.Get(0).(func(string, string) model.UserMentionMap); ok { - r0 = rf(message, teamID) + if rf, ok := ret.Get(0).(func(request.CTX, string, string) model.UserMentionMap); ok { + r0 = rf(c, message, teamID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(model.UserMentionMap) diff --git a/services/sharedchannel/service.go b/services/sharedchannel/service.go index c8af3c02a1..f73b0350b7 100644 --- a/services/sharedchannel/service.go +++ b/services/sharedchannel/service.go @@ -59,7 +59,7 @@ type AppIface interface { PatchChannelModerationsForChannel(c request.CTX, channel *model.Channel, channelModerationsPatch []*model.ChannelModerationPatch) ([]*model.ChannelModeration, *model.AppError) CreateUploadSession(c request.CTX, us *model.UploadSession) (*model.UploadSession, *model.AppError) FileReader(path string) (filestore.ReadCloseSeeker, *model.AppError) - MentionsToTeamMembers(message, teamID string) model.UserMentionMap + MentionsToTeamMembers(c request.CTX, message, teamID string) model.UserMentionMap GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) InvalidateCacheForUser(userID string) NotifySharedChannelUserUpdate(user *model.User) diff --git a/services/sharedchannel/sync_send_remote.go b/services/sharedchannel/sync_send_remote.go index d6efd49abf..010970fff1 100644 --- a/services/sharedchannel/sync_send_remote.go +++ b/services/sharedchannel/sync_send_remote.go @@ -11,6 +11,7 @@ import ( "github.com/wiggin77/merror" + "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/services/remotecluster" "github.com/mattermost/mattermost-server/v6/shared/mlog" @@ -271,7 +272,7 @@ func (scs *Service) fetchPostUsersForSync(sd *syncData) error { userIDs[post.UserId] = p2mm{} // get mentions and users for each mention - mentionMap := scs.app.MentionsToTeamMembers(post.Message, sc.TeamId) + mentionMap := scs.app.MentionsToTeamMembers(request.EmptyContext(scs.server.GetLogger()), post.Message, sc.TeamId) for _, userID := range mentionMap { userIDs[userID] = p2mm{ post: post, diff --git a/shared/filestore/s3store.go b/shared/filestore/s3store.go index 06d6bec7ed..c8738a7b5a 100644 --- a/shared/filestore/s3store.go +++ b/shared/filestore/s3store.go @@ -368,7 +368,10 @@ func (b *S3FileBackend) WriteFile(fr io.Reader, path string) (int64, error) { options := s3PutOptions(b.encrypt, contentType) objSize := -1 - if buf, ok := fr.(*bytes.Buffer); ok { + isCloud := os.Getenv("MM_CLOUD_FILESTORE_BIFROST") != "" + // We pass an object size only in situations where bifrost is not + // used. Bifrost needs to run in HTTPS, which is not yet deployed. + if buf, ok := fr.(*bytes.Buffer); ok && !isCloud { objSize = buf.Len() } @@ -401,7 +404,10 @@ func (b *S3FileBackend) AppendFile(fr io.Reader, path string) (int64, error) { ctx2, cancel2 := context.WithTimeout(context.Background(), b.timeout) defer cancel2() objSize := -1 - if buf, ok := fr.(*bytes.Buffer); ok { + isCloud := os.Getenv("MM_CLOUD_FILESTORE_BIFROST") != "" + // We pass an object size only in situations where bifrost is not + // used. Bifrost needs to run in HTTPS, which is not yet deployed. + if buf, ok := fr.(*bytes.Buffer); ok && !isCloud { objSize = buf.Len() } info, err := b.client.PutObject(ctx2, b.bucket, partName, fr, int64(objSize), options) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index be309e4e3c..fc67b29599 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -784,6 +784,13 @@ func (s *SqlPostStore) Get(ctx context.Context, id string, opts model.GetPostsOp } for _, p := range posts { + if p.Id == id { + // Based on the conditions above such as sq.Or{ sq.Eq{"p.Id": rootId}, sq.Eq{"p.RootId": rootId}, } + // posts may contain the "id" post which has already been fetched and added in the "pl" + // So, skip the "id" to avoid duplicate entry of the post + continue + } + pl.AddPost(p) pl.AddOrder(p.Id) } @@ -2188,7 +2195,7 @@ func (s *SqlPostStore) AnalyticsPostCountsByDay(options *model.AnalyticsPostCoun func (s *SqlPostStore) AnalyticsPostCount(options *model.PostCountOptions) (int64, error) { query := s.getQueryBuilder(). - Select("COUNT(p.Id) AS Value"). + Select("COUNT(*) AS Value"). From("Posts p") if options.TeamId != "" { @@ -2840,8 +2847,23 @@ func (s *SqlPostStore) updateThreadAfterReplyDeletion(transaction *sqlxTxWrapper } } + lastReplyAtSubquery := sq.Select("COALESCE(MAX(CreateAt), 0)"). + From("Posts"). + Where(sq.Eq{ + "RootId": rootId, + "DeleteAt": 0, + }) + + lastReplyCountSubquery := sq.Select("Count(*)"). + From("Posts"). + Where(sq.Eq{ + "RootId": rootId, + "DeleteAt": 0, + }) + updateQueryString, updateArgs, err := updateQuery. - Set("ReplyCount", sq.Expr("ReplyCount - 1")). + Set("LastReplyAt", lastReplyAtSubquery). + Set("ReplyCount", lastReplyCountSubquery). Where(sq.And{ sq.Eq{"PostId": rootId}, sq.Gt{"ReplyCount": 0}, diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index 405172ad21..5a4502a12e 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -1500,17 +1500,20 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string for _, term := range terms { searchFields := []string{} termArgs := []any{} + var dbSpecificTerm string + for _, field := range fields { if isPostgreSQL { - searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower(?) escape '*' ", field)) + searchFields = append(searchFields, fmt.Sprintf("to_tsvector(lower(%[1]s)) @@ to_tsquery(concat(lower(?),':*'))", field)) + dbSpecificTerm = strings.TrimLeft(term, "@") } else { searchFields = append(searchFields, fmt.Sprintf("%s LIKE ? escape '*' ", field)) + dbSpecificTerm = fmt.Sprintf("%s%%", strings.TrimLeft(term, "@")) } - termArgs = append(termArgs, fmt.Sprintf("%s%%", strings.TrimLeft(term, "@"))) + termArgs = append(termArgs, dbSpecificTerm) } query = query.Where(fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")), termArgs...) } - return query } diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index f077182a01..e7e69d2cb3 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -569,6 +569,7 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) { }) t.Run("Pagination", func(t *testing.T) { + t.Skip("MM-46134") o1, err := ss.Post().Save(&model.Post{ChannelId: model.NewId(), UserId: model.NewId(), Message: NewTestId()}) require.NoError(t, err) _, err = ss.Post().Save(&model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestId(), RootId: o1.Id}) @@ -644,7 +645,7 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) { } r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId, map[string]bool{}) require.NoError(t, err) - assert.Len(t, r1.Order, 3) // including the root post + assert.Len(t, r1.Order, 2) // including the root post assert.True(t, r1.HasNext) lastPostID = r1.Order[len(r1.Order)-1] @@ -676,7 +677,7 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) { } r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId, map[string]bool{}) require.NoError(t, err) - assert.Len(t, r1.Order, 3) // including the root post + assert.Len(t, r1.Order, 2) // including the root post assert.LessOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, firstPostCreateAt) assert.False(t, r1.HasNext) @@ -980,6 +981,74 @@ func testPostStoreDelete(t *testing.T, ss store.Store) { _, err = ss.Post().Get(context.Background(), rootPost2.Id, model.GetPostsOptions{}, "", map[string]bool{}) require.NoError(t, err) }) + + t.Run("thread with multiple replies, update thread last reply at", func(t *testing.T) { + // Create a root post + rootPost1, err := ss.Post().Save(&model.Post{ + ChannelId: model.NewId(), + UserId: model.NewId(), + Message: NewTestId(), + }) + require.NoError(t, err) + + // Reply to that root post + replyPost1, err := ss.Post().Save(&model.Post{ + ChannelId: rootPost1.ChannelId, + UserId: model.NewId(), + Message: NewTestId(), + RootId: rootPost1.Id, + }) + require.NoError(t, err) + + // Reply to that root post a second time + replyPost2, err := ss.Post().Save(&model.Post{ + ChannelId: rootPost1.ChannelId, + UserId: model.NewId(), + Message: NewTestId(), + RootId: rootPost1.Id, + }) + require.NoError(t, err) + + // Reply to that root post a third time + replyPost3, err := ss.Post().Save(&model.Post{ + ChannelId: rootPost1.ChannelId, + UserId: model.NewId(), + Message: NewTestId(), + RootId: rootPost1.Id, + }) + require.NoError(t, err) + + thread, err := ss.Thread().Get(rootPost1.Id) + require.NoError(t, err) + require.Equal(t, replyPost3.CreateAt, thread.LastReplyAt) + + // Delete the reply previous to last + err = ss.Post().Delete(replyPost2.Id, model.GetMillis(), "") + require.NoError(t, err) + + thread, err = ss.Thread().Get(rootPost1.Id) + require.NoError(t, err) + // last reply at should be unchanged + require.Equal(t, replyPost3.CreateAt, thread.LastReplyAt) + + // Delete the last reply + err = ss.Post().Delete(replyPost3.Id, model.GetMillis(), "") + require.NoError(t, err) + + thread, err = ss.Thread().Get(rootPost1.Id) + require.NoError(t, err) + // last reply at should have changed + require.Equal(t, replyPost1.CreateAt, thread.LastReplyAt) + + // Delete the last reply + err = ss.Post().Delete(replyPost1.Id, model.GetMillis(), "") + require.NoError(t, err) + + thread, err = ss.Thread().Get(rootPost1.Id) + require.NoError(t, err) + // last reply at should be 0 + require.Equal(t, int64(0), thread.LastReplyAt) + }) } func testPostStorePermDelete1Level(t *testing.T, ss store.Store) { diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go index 7214b98b0b..84dba22cb4 100644 --- a/store/storetest/user_store.go +++ b/store/storetest/user_store.go @@ -94,6 +94,7 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlStore) { t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) }) t.Run("GetKnownUsers", func(t *testing.T) { testGetKnownUsers(t, ss) }) t.Run("GetUsersWithInvalidEmails", func(t *testing.T) { testGetUsersWithInvalidEmails(t, ss) }) + t.Run("SearchMultilingual", func(t *testing.T) { testUserStoreSearchUsersMultilingual(t, ss, s) }) } func testUserStoreSave(t *testing.T, ss store.Store) { @@ -6010,3 +6011,174 @@ func testGetUsersWithInvalidEmails(t *testing.T, ss store.Store) { require.NoError(t, err) assert.Len(t, users, 1) } + +func testUserStoreSearchUsersMultilingual(t *testing.T, ss store.Store, s SqlStore) { + u1 := &model.User{ + Username: "test1" + model.NewId(), + FirstName: "Inígo", + LastName: "Martínez", + Nickname: "Berridi", + Email: MakeEmail(), + } + _, err := ss.User().Save(u1) + require.NoError(t, err) + defer func() { require.NoError(t, ss.User().PermanentDelete(u1.Id)) }() + + u2 := &model.User{ + Username: "test2" + model.NewId(), + FirstName: "Zinëdìne", + LastName: "Zidanë", + Email: MakeEmail(), + } + _, err = ss.User().Save(u2) + require.NoError(t, err) + defer func() { require.NoError(t, ss.User().PermanentDelete(u2.Id)) }() + + u3 := &model.User{ + Username: "test3" + model.NewId(), + FirstName: "Thomas ", + LastName: "Müller", + Nickname: "Fußballspieler", + Email: MakeEmail(), + } + _, err = ss.User().Save(u3) + require.NoError(t, err) + defer func() { require.NoError(t, ss.User().PermanentDelete(u3.Id)) }() + + u4 := &model.User{ + Username: "test4" + model.NewId(), + FirstName: "Jérémie ", + LastName: "Jéry", + Email: MakeEmail(), + } + _, err = ss.User().Save(u4) + require.NoError(t, err) + defer func() { require.NoError(t, ss.User().PermanentDelete(u4.Id)) }() + + // The users returned from the database will have AuthData as an empty string. + nilAuthData := new(string) + *nilAuthData = "" + u1.AuthData = nilAuthData + u2.AuthData = nilAuthData + u3.AuthData = nilAuthData + u4.AuthData = nilAuthData + + testCases := []struct { + Description string + Term string + Options *model.UserSearchOptions + ExpectedPostgres []*model.User + ExpectedMysql []*model.User + Language string + }{ + { + "search test1 player", + "inig", + &model.UserSearchOptions{ + AllowFullNames: true, + Limit: model.UserSearchDefaultLimit, + }, + []*model.User{u1}, + []*model.User{u1}, + "spanish", + }, + { + "search test2 player", + "zine", + &model.UserSearchOptions{ + AllowFullNames: true, + Limit: model.UserSearchDefaultLimit, + }, + []*model.User{u2}, + []*model.User{u2}, + "french", + }, + { + "search test2 player", + "zidane", + &model.UserSearchOptions{ + AllowFullNames: true, + Limit: model.UserSearchDefaultLimit, + }, + []*model.User{u2}, + []*model.User{u2}, + "french", + }, + { + "search test3 player", + "muller", + &model.UserSearchOptions{ + AllowFullNames: true, + Limit: model.UserSearchDefaultLimit, + }, + []*model.User{u3}, + []*model.User{u3}, + "german", + }, + { + "search test3 player", + "muller", + &model.UserSearchOptions{ + AllowFullNames: true, + Limit: model.UserSearchDefaultLimit, + }, + []*model.User{}, + []*model.User{u3}, + "english", + }, + { + "search test4 player", + "jere", + &model.UserSearchOptions{ + AllowFullNames: true, + Limit: model.UserSearchDefaultLimit, + }, + []*model.User{u4}, + []*model.User{u4}, + "spanish", + }, + { + "search test4 player", + "jere", + &model.UserSearchOptions{ + AllowFullNames: true, + Limit: model.UserSearchDefaultLimit, + }, + []*model.User{}, + []*model.User{u4}, + "english", + }, + } + + var initialDefaultTextSearchConfig string + if s.DriverName() == model.DatabaseDriverPostgres { + error := s.GetMasterX().Get(&initialDefaultTextSearchConfig, `SHOW default_text_search_config`) + require.NoError(t, error) + } + + for _, testCase := range testCases { + if s.DriverName() == model.DatabaseDriverPostgres { + _, error := s.GetMasterX().Exec("SET default_text_search_config TO '" + testCase.Language + "'") + require.NoError(t, error) + } + t.Run(testCase.Description, func(t *testing.T) { + users, err := ss.User().SearchWithoutTeam( + testCase.Term, + testCase.Options, + ) + + if s.DriverName() != model.DatabaseDriverPostgres { + require.NoError(t, err) + assertUsers(t, testCase.ExpectedMysql, users) + } else { + require.NoError(t, err) + assertUsers(t, testCase.ExpectedPostgres, users) + } + }) + } + + if s.DriverName() == model.DatabaseDriverPostgres { + _, error := s.GetMasterX().Exec("SET default_text_search_config TO '" + initialDefaultTextSearchConfig + "'") + require.NoError(t, error) + } +} diff --git a/web/web_test.go b/web/web_test.go index 303c1840cd..8c79c4cd33 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -96,7 +96,7 @@ func setupTestHelper(tb testing.TB, includeCacheLayer bool) *TestHelper { } if includeCacheLayer { // Adds the cache layer to the test store - s.Store, err = localcachelayer.NewLocalCacheLayer(s.Store, s.Metrics, s.Cluster, s.CacheProvider) + s.Store, err = localcachelayer.NewLocalCacheLayer(s.Store, s.GetMetrics(), s.Cluster, s.CacheProvider) if err != nil { panic(err) }