From 562388501e48b0048fbbb1fb755e448d6b62e376 Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Fri, 29 Jul 2022 17:03:52 +0300 Subject: [PATCH 01/18] MM-43939: fixes LastReplyAt when deleting the last reply (#20615) * MM-43939: fixes lastreplyat when deleting the last reply Currently we are not updating the Threads.LastReplyAt when the last reply gets deleted. This can lead to threads appearing unread when actually there is no unread thread. This commit updates the value of Threads.LastReplyAt when a reply gets deleted, to the most recent post's timestamp in the thread. * Updates ReplyCount to current value on post delete * Addresses review comments Co-authored-by: Mattermod --- store/sqlstore/post_store.go | 17 ++++++++- store/storetest/post_store.go | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index 03ee58888e..044ee6ca27 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -2840,8 +2840,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/storetest/post_store.go b/store/storetest/post_store.go index 98efff5321..2489847905 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -979,6 +979,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) { From c7ae090dad6107e985d62530f0be611fa347c08c Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 29 Jul 2022 20:23:14 +0530 Subject: [PATCH 02/18] Revert "MM-43828: Pass object length for some image operations (#20711)" (#20740) Automatic Merge --- app/app_iface.go | 5 ++ app/file.go | 71 ++++++++++++++++++++++++++++ app/file_bench_test.go | 17 +++++++ app/opentracing/opentracing_layer.go | 44 +++++++++++++++++ app/plugin_hooks_test.go | 49 ++++++++++++------- i18n/en.json | 4 ++ shared/filestore/s3store.go | 15 +----- 7 files changed, 175 insertions(+), 30 deletions(-) diff --git a/app/app_iface.go b/app/app_iface.go index 5176813a34..fbc7c4805e 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -381,6 +381,10 @@ type AppIface interface { // upload, returning a rejection error. In this case FileInfo would have // contained the last "good" FileInfo before the execution of that plugin. UploadFileX(c *request.Context, channelID, name string, input io.Reader, opts ...func(*UploadFileTask)) (*model.FileInfo, *model.AppError) + // Uploads some files to the given team and channel as the given user. files and filenames should have + // the same length. clientIds should either not be provided or have the same length as files and filenames. + // The provided files should be closed by the caller so that they are not leaked. + UploadFiles(c *request.Context, teamID string, channelID string, userID string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) // UserIsInAdminRoleGroup returns true at least one of the user's groups are configured to set the members as // admins in the given syncable. UserIsInAdminRoleGroup(userID, syncableID string, syncableType model.GroupSyncableType) (bool, *model.AppError) @@ -1128,6 +1132,7 @@ type AppIface interface { UpdateUserRolesWithUser(c request.CTX, user *model.User, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError) UploadData(c *request.Context, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) UploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppError + UploadMultipartFiles(c *request.Context, teamID string, channelID string, userID string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError) UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) diff --git a/app/file.go b/app/file.go index 3ec52cb698..12c954de33 100644 --- a/app/file.go +++ b/app/file.go @@ -12,6 +12,7 @@ import ( "fmt" "image" "io" + "mime/multipart" "net/http" "net/url" "os" @@ -452,6 +453,76 @@ func GeneratePublicLinkHash(fileID, salt string) string { return base64.RawURLEncoding.EncodeToString(hash.Sum(nil)) } +func (a *App) UploadMultipartFiles(c *request.Context, teamID string, channelID string, userID string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { + files := make([]io.ReadCloser, len(fileHeaders)) + filenames := make([]string, len(fileHeaders)) + + for i, fileHeader := range fileHeaders { + file, fileErr := fileHeader.Open() + if fileErr != nil { + return nil, model.NewAppError("UploadFiles", "api.file.upload_file.read_request.app_error", + map[string]any{"Filename": fileHeader.Filename}, fileErr.Error(), http.StatusBadRequest) + } + + // Will be closed after UploadFiles returns + defer file.Close() + + files[i] = file + filenames[i] = fileHeader.Filename + } + + return a.UploadFiles(c, teamID, channelID, userID, files, filenames, clientIds, now) +} + +// Uploads some files to the given team and channel as the given user. files and filenames should have +// the same length. clientIds should either not be provided or have the same length as files and filenames. +// The provided files should be closed by the caller so that they are not leaked. +func (a *App) UploadFiles(c *request.Context, teamID string, channelID string, userID string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { + if *a.Config().FileSettings.DriverName == "" { + return nil, model.NewAppError("UploadFiles", "api.file.upload_file.storage.app_error", nil, "", http.StatusNotImplemented) + } + + if len(filenames) != len(files) || (len(clientIds) > 0 && len(clientIds) != len(files)) { + return nil, model.NewAppError("UploadFiles", "api.file.upload_file.incorrect_number_of_files.app_error", nil, "", http.StatusBadRequest) + } + + resStruct := &model.FileUploadResponse{ + FileInfos: []*model.FileInfo{}, + ClientIds: []string{}, + } + + previewPathList := []string{} + thumbnailPathList := []string{} + imageDataList := [][]byte{} + + for i, file := range files { + buf := bytes.NewBuffer(nil) + io.Copy(buf, file) + data := buf.Bytes() + + info, data, err := a.DoUploadFileExpectModification(c, now, teamID, channelID, userID, filenames[i], data) + if err != nil { + return nil, err + } + + if info.PreviewPath != "" || info.ThumbnailPath != "" { + previewPathList = append(previewPathList, info.PreviewPath) + thumbnailPathList = append(thumbnailPathList, info.ThumbnailPath) + imageDataList = append(imageDataList, data) + } + + resStruct.FileInfos = append(resStruct.FileInfos, info) + + if len(clientIds) > 0 { + resStruct.ClientIds = append(resStruct.ClientIds, clientIds[i]) + } + } + + a.HandleImages(previewPathList, thumbnailPathList, imageDataList) + + return resStruct, nil +} + // 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) { _, err := a.GetChannel(c, channelID) diff --git a/app/file_bench_test.go b/app/file_bench_test.go index 608bd1eb31..515f69cc03 100644 --- a/app/file_bench_test.go +++ b/app/file_bench_test.go @@ -9,6 +9,8 @@ import ( "image" "image/gif" "image/jpeg" + "io" + "io/ioutil" "math/rand" "testing" "time" @@ -128,6 +130,21 @@ func BenchmarkUploadFile(b *testing.B) { th.App.RemoveFile(info.Path) }, }, + { + title: "image UploadFiles", + f: func(b *testing.B, n int, data []byte, ext string) { + resp, err := th.App.UploadFiles(th.Context, teamID, channelID, userID, + []io.ReadCloser{ioutil.NopCloser(bytes.NewReader(data))}, + []string{fmt.Sprintf("BenchmarkDoUploadFiles-%d%s", n, ext)}, + []string{}, + time.Now()) + if err != nil { + b.Fatal(err) + } + th.App.Srv().Store.FileInfo().PermanentDelete(resp.FileInfos[0].Id) + th.App.RemoveFile(resp.FileInfos[0].Path) + }, + }, { title: "image UploadFileX Content-Length", f: func(b *testing.B, n int, data []byte, ext string) { diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 193d739f40..b2beba058e 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -17813,6 +17813,50 @@ func (a *OpenTracingAppLayer) UploadFileX(c *request.Context, channelID string, return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) UploadFiles(c *request.Context, teamID string, channelID string, userID string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UploadFiles") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.UploadFiles(c, teamID, channelID, userID, files, filenames, clientIds, now) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + +func (a *OpenTracingAppLayer) UploadMultipartFiles(c *request.Context, teamID string, channelID string, userID string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UploadMultipartFiles") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0, resultVar1 := a.app.UploadMultipartFiles(c, teamID, channelID, userID, fileHeaders, clientIds, now) + + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (a *OpenTracingAppLayer) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpsertGroupMember") diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index e4eaa4d23e..32945cfed7 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -462,12 +462,15 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - _, err := th.App.UploadFile(th.Context, - []byte("inputfile"), + _, err := th.App.UploadFiles(th.Context, + "noteam", th.BasicChannel.Id, - "testhook.txt", + th.BasicUser.Id, + []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, + []string{"testhook.txt"}, + []string{}, + time.Now(), ) - if assert.NotNil(t, err) { assert.Equal(t, "File rejected by plugin. rejected", err.Message) } @@ -512,12 +515,15 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - _, err := th.App.UploadFile(th.Context, - []byte("inputfile"), + _, err := th.App.UploadFiles(th.Context, + "noteam", th.BasicChannel.Id, - "testhook.txt", + th.BasicUser.Id, + []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, + []string{"testhook.txt"}, + []string{}, + time.Now(), ) - if assert.NotNil(t, err) { assert.Equal(t, "File rejected by plugin. rejected", err.Message) } @@ -556,16 +562,20 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - response, err := th.App.UploadFile(th.Context, - []byte("inputfile"), + response, err := th.App.UploadFiles(th.Context, + "noteam", th.BasicChannel.Id, - "testhook.txt", + th.BasicUser.Id, + []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, + []string{"testhook.txt"}, + []string{}, + time.Now(), ) - assert.Nil(t, err) assert.NotNil(t, response) + assert.Equal(t, 1, len(response.FileInfos)) - fileID := response.Id + fileID := response.FileInfos[0].Id fileInfo, err := th.App.GetFileInfo(fileID) assert.Nil(t, err) assert.NotNil(t, fileInfo) @@ -628,14 +638,19 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - response, err := th.App.UploadFile(th.Context, - []byte("inputfile"), + response, err := th.App.UploadFiles(th.Context, + "noteam", th.BasicChannel.Id, - "testhook.txt", + th.BasicUser.Id, + []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, + []string{"testhook.txt"}, + []string{}, + time.Now(), ) assert.Nil(t, err) assert.NotNil(t, response) - fileID := response.Id + assert.Equal(t, 1, len(response.FileInfos)) + fileID := response.FileInfos[0].Id fileInfo, err := th.App.GetFileInfo(fileID) assert.Nil(t, err) diff --git a/i18n/en.json b/i18n/en.json index 0899a44d24..0900d84156 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1857,6 +1857,10 @@ "id": "api.file.upload_file.incorrect_number_of_client_ids.app_error", "translation": "Unable to upload file(s). Have {{.NumClientIds}} client_ids for {{.NumFiles}} files." }, + { + "id": "api.file.upload_file.incorrect_number_of_files.app_error", + "translation": "Unable to upload files. Incorrect number of files specified." + }, { "id": "api.file.upload_file.large_image.app_error", "translation": "File above maximum dimensions could not be uploaded: {{.Filename}}" diff --git a/shared/filestore/s3store.go b/shared/filestore/s3store.go index 06d6bec7ed..229c3537a5 100644 --- a/shared/filestore/s3store.go +++ b/shared/filestore/s3store.go @@ -4,7 +4,6 @@ package filestore import ( - "bytes" "context" "crypto/tls" "io" @@ -366,13 +365,7 @@ func (b *S3FileBackend) WriteFile(fr io.Reader, path string) (int64, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() options := s3PutOptions(b.encrypt, contentType) - - objSize := -1 - if buf, ok := fr.(*bytes.Buffer); ok { - objSize = buf.Len() - } - - info, err := b.client.PutObject(ctx, b.bucket, path, fr, int64(objSize), options) + info, err := b.client.PutObject(ctx, b.bucket, path, fr, -1, options) if err != nil { return info.Size, errors.Wrapf(err, "unable write the data in the file %s", path) } @@ -400,11 +393,7 @@ func (b *S3FileBackend) AppendFile(fr io.Reader, path string) (int64, error) { partName := fp + ".part" ctx2, cancel2 := context.WithTimeout(context.Background(), b.timeout) defer cancel2() - objSize := -1 - if buf, ok := fr.(*bytes.Buffer); ok { - objSize = buf.Len() - } - info, err := b.client.PutObject(ctx2, b.bucket, partName, fr, int64(objSize), options) + info, err := b.client.PutObject(ctx2, b.bucket, partName, fr, -1, options) if err != nil { return 0, errors.Wrapf(err, "unable append the data in the file %s", path) } From 55f64195b1991f9d7409a618b349db18815e914e Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Mon, 1 Aug 2022 13:48:45 +0530 Subject: [PATCH 03/18] MM-45195: Use api4/bot and app/bot to use logger context (#20729) https://mattermost.atlassian.net/browse/MM-45195 ```release-note NONE ``` --- api4/bot.go | 4 ++-- app/app_iface.go | 10 +++++----- app/bot.go | 14 +++++++------- app/import_functions.go | 8 ++++---- app/opentracing/opentracing_layer.go | 10 +++++----- app/post.go | 4 ++-- app/user.go | 4 ++-- 7 files changed, 27 insertions(+), 27 deletions(-) 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/app/app_iface.go b/app/app_iface.go index fbc7c4805e..4f1205abc3 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -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 @@ -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. @@ -488,7 +488,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) @@ -1080,7 +1080,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) 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/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/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index b2beba058e..cd6c85fc5d 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -1857,7 +1857,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") @@ -2323,7 +2323,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") @@ -3948,7 +3948,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") @@ -16474,7 +16474,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") @@ -16496,7 +16496,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") diff --git a/app/post.go b/app/post.go index 797f1b6861..716b821984 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), diff --git a/app/user.go b/app/user.go index edeef970ff..844402d2f6 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 From 8444c4595976cb6486346718bd9cfd1d408b3e1c Mon Sep 17 00:00:00 2001 From: Dimitris Oik <73129503+dimoiko100@users.noreply.github.com> Date: Mon, 1 Aug 2022 13:58:31 +0300 Subject: [PATCH 04/18] MM-44576 autocomplete names including utf 8 chars (#20367) * Added unaccent extension * Changed comment * Deletes migrations, changes query method * Mionr changes * Creates test for multilingual queries * Minor formatting changes * Adds two more tests * Changes name in test function * Minor change * Retriggers tests * Removes % from Postgres query * Lint fix * Changes variable name * Removes SetDefaultTextSearchConfig method * Removes mocks * Adds error handling on test raw queries * Lint fix * Deletes unused generated file Co-authored-by: Mattermod --- store/sqlstore/user_store.go | 9 +- store/storetest/user_store.go | 172 ++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 3 deletions(-) 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/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) + } +} From fa768e0fa73beb37cbdf45cf6199e875c0b992cc Mon Sep 17 00:00:00 2001 From: Vishal Date: Tue, 2 Aug 2022 12:10:26 +0530 Subject: [PATCH 05/18] [MM-45726] Fix Sentry crash: nil deference in app/post_helpers.go:85 (#20654) * Avoid adding duplicate post --- store/sqlstore/post_store.go | 7 +++++++ store/storetest/post_store.go | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index 044ee6ca27..3659708401 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) } diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index 2489847905..63965897a4 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -643,7 +643,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] @@ -675,7 +675,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) From 276594608c39223b331caa5168be6b39c81c53ab Mon Sep 17 00:00:00 2001 From: Vishal Date: Tue, 2 Aug 2022 12:12:41 +0530 Subject: [PATCH 06/18] Remove the read_channel permission to unfollow a thread (#20662) --- api4/user.go | 5 ----- 1 file changed, 5 deletions(-) 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 From ac79a887a209df30e50124575a4dbb334b22a087 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Tue, 2 Aug 2022 10:59:29 +0300 Subject: [PATCH 07/18] Move metrics under platform service (continued) (#20732) * move metrics into platform Co-authored-by: Agniva De Sarker --- api4/apitestlib.go | 10 +- app/app.go | 2 +- app/config.go | 8 +- app/enterprise.go | 4 - app/helper_test.go | 2 +- app/platform/config.go | 20 ++++ app/platform/metrics.go | 184 +++++++++++++++++++++++++++++ app/platform/service.go | 38 +++++- app/plugin.go | 2 +- app/server.go | 195 ++++++++----------------------- app/slashcommands/helper_test.go | 2 +- app/web_hub.go | 8 +- web/web_test.go | 2 +- 13 files changed, 307 insertions(+), 170 deletions(-) create mode 100644 app/platform/metrics.go 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/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/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/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/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/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 .}} + {{end}} + + + + + + + + + + + + ` + 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/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 .}} - {{end}} - - - - - - - - - - - - ` - 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/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/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/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) } From 928bcf87ba48672a8c003a9865b1f69682634cf4 Mon Sep 17 00:00:00 2001 From: Akis Maziotis <36594156+phoinixgrr@users.noreply.github.com> Date: Tue, 2 Aug 2022 17:27:50 +0300 Subject: [PATCH 08/18] [chore] Update Node and Npm build image (#20752) This updates the build-images used for CI to: - node: 16.10.0 - npm: 7.24.0 Ticket: https://mattermost.atlassian.net/browse/CLD-3848 Signed-off-by: Akis Maziotis --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 5c7ed6a1b169e12789ea3536154166a8665f91da Mon Sep 17 00:00:00 2001 From: Allan Guwatudde Date: Tue, 2 Aug 2022 18:09:19 +0300 Subject: [PATCH 09/18] fix feature flag treatment (#20753) --- model/feature_flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/feature_flags.go b/model/feature_flags.go index f2f14bd563..f96dae0f5b 100644 --- a/model/feature_flags.go +++ b/model/feature_flags.go @@ -102,7 +102,7 @@ func (f *FeatureFlags) SetDefaults() { f.AdvancedTextEditor = true f.CallsEnabled = true f.BoardsProduct = false - f.PlanUpgradeButtonText = "Upgrade" + f.PlanUpgradeButtonText = "upgrade" } func (f *FeatureFlags) Plugins() map[string]string { From f07c31c5d93342eb7b623d5c9e64daf17fc14372 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Tue, 2 Aug 2022 20:48:54 +0530 Subject: [PATCH 10/18] MM-45196: Migration api4/command and app/cmd to logger context (#20730) ```release-note NONE ``` Co-authored-by: Mattermod --- app/app_iface.go | 20 ++++----- app/command.go | 22 +++++----- app/command_autocomplete_test.go | 2 +- app/export.go | 43 ++++++++++---------- app/export_test.go | 32 +++++++-------- app/file.go | 4 +- app/opentracing/opentracing_layer.go | 26 ++++++------ app/plugin_api_test.go | 2 +- app/plugin_commands.go | 4 +- app/slashcommands/auto_channels.go | 6 +-- app/slashcommands/auto_environment.go | 4 +- app/slashcommands/auto_posts.go | 6 +-- app/slashcommands/auto_users.go | 4 +- app/slashcommands/command_away.go | 2 +- app/slashcommands/command_channel_header.go | 2 +- app/slashcommands/command_channel_purpose.go | 2 +- app/slashcommands/command_channel_rename.go | 2 +- app/slashcommands/command_code.go | 2 +- app/slashcommands/command_custom_status.go | 2 +- app/slashcommands/command_dnd.go | 2 +- app/slashcommands/command_echo.go | 2 +- app/slashcommands/command_expand_collapse.go | 4 +- app/slashcommands/command_groupmsg.go | 2 +- app/slashcommands/command_help.go | 2 +- app/slashcommands/command_invite.go | 2 +- app/slashcommands/command_invite_people.go | 2 +- app/slashcommands/command_join.go | 2 +- app/slashcommands/command_leave.go | 2 +- app/slashcommands/command_loadtest.go | 32 +++++++-------- app/slashcommands/command_logout.go | 2 +- app/slashcommands/command_me.go | 2 +- app/slashcommands/command_msg.go | 2 +- app/slashcommands/command_mute.go | 2 +- app/slashcommands/command_offline.go | 2 +- app/slashcommands/command_online.go | 2 +- app/slashcommands/command_remote.go | 2 +- app/slashcommands/command_remove.go | 6 +-- app/slashcommands/command_search.go | 2 +- app/slashcommands/command_settings.go | 2 +- app/slashcommands/command_share.go | 2 +- app/slashcommands/command_shortcuts.go | 2 +- app/slashcommands/command_shrug.go | 2 +- app/slashcommands/command_test.go | 2 +- app/user.go | 2 +- cmd/mattermost/commands/export.go | 3 +- jobs/export_process/worker.go | 6 ++- services/sharedchannel/mock_AppIface_test.go | 10 ++--- services/sharedchannel/service.go | 2 +- services/sharedchannel/sync_send_remote.go | 3 +- 49 files changed, 151 insertions(+), 146 deletions(-) diff --git a/app/app_iface.go b/app/app_iface.go index 4f1205abc3..f0200424e5 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. @@ -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 @@ -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 @@ -432,10 +432,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 @@ -549,7 +549,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 @@ -840,8 +840,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 @@ -1125,7 +1125,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/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/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 12c954de33..1f0e557e18 100644 --- a/app/file.go +++ b/app/file.go @@ -524,7 +524,7 @@ func (a *App) UploadFiles(c *request.Context, teamID string, channelID string, u } // 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", @@ -924,7 +924,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/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index cd6c85fc5d..4abf8686ba 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -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)) @@ -1967,7 +1967,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") @@ -3838,7 +3838,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") @@ -3987,7 +3987,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") @@ -10975,7 +10975,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") @@ -10997,7 +10997,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") @@ -12151,7 +12151,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") @@ -12163,7 +12163,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 } @@ -17563,7 +17563,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") @@ -17769,7 +17769,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/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/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..ecfc832251 100644 --- a/app/slashcommands/command_expand_collapse.go +++ b/app/slashcommands/command_expand_collapse.go @@ -56,11 +56,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) } 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/user.go b/app/user.go index 844402d2f6..1dfa276012 100644 --- a/app/user.go +++ b/app/user.go @@ -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 { 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/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/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, From ee41162b4891d28682717bcafaa820783c973b6a Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Wed, 3 Aug 2022 04:42:40 +0530 Subject: [PATCH 11/18] MM-46134: Skip flaky test PostStore//GetForThread (#20755) https://mattermost.atlassian.net/browse/MM-46134 ```release-note NONE ``` --- store/storetest/post_store.go | 1 + 1 file changed, 1 insertion(+) diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index 63965897a4..da7c16137c 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -568,6 +568,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}) From 334b199e7af42f14c048f50f70441c4d4f336796 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Wed, 3 Aug 2022 10:31:18 +0530 Subject: [PATCH 12/18] Optimize AnalyticsPostCount (#20727) We use * instead of a column name to use index-only scan always even when other column filters are applied. Right now, index-only scan will only get applied in the basic query of "select count(p.id) as value from posts p". But it won't get applied if the query is "select count(p.id) as value from posts p where p.deleteat=0". So this is a minor optimization which improves some corner cases. This was found from the slow query monitoring. ```release-note NONE ``` --- store/sqlstore/post_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index 3659708401..a2e8c228e7 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -2195,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 != "" { From e7156476fab3e1bfb4969bb4cb63d5485c3ad944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20V=C3=A9lez=20Vidal?= Date: Wed, 3 Aug 2022 23:54:43 +0200 Subject: [PATCH 13/18] MM-45542 - do not fail if site url is not set when requesting trial (#20713) * MM-45542 - do not fail if site url is not set when requesting trial * remove unnecessary text Co-authored-by: Pablo Velez Vidal Co-authored-by: Mattermod --- api4/license_test.go | 8 -------- app/license.go | 4 ---- i18n/en.json | 4 ---- 3 files changed, 16 deletions(-) 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/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/i18n/en.json b/i18n/en.json index 0900d84156..985491ceef 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2053,10 +2053,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." From 6cc9933b58f0b6215b221e1191d1fd6d9e150c13 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 4 Aug 2022 11:21:51 +0530 Subject: [PATCH 14/18] Fix minio client breakage with bifrost round 2 (#20758) * Revert "Revert "MM-43828: Pass object length for some image operations (#20711)" (#20740)" This reverts commit c7ae090dad6107e985d62530f0be611fa347c08c. * Fix to work in bifrost mode ```release-note NONE ``` --- app/app_iface.go | 5 -- app/file.go | 71 ---------------------------- app/file_bench_test.go | 17 ------- app/opentracing/opentracing_layer.go | 44 ----------------- app/plugin_hooks_test.go | 49 +++++++------------ i18n/en.json | 4 -- shared/filestore/s3store.go | 21 +++++++- 7 files changed, 36 insertions(+), 175 deletions(-) diff --git a/app/app_iface.go b/app/app_iface.go index f0200424e5..384f3f95a3 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -381,10 +381,6 @@ type AppIface interface { // upload, returning a rejection error. In this case FileInfo would have // contained the last "good" FileInfo before the execution of that plugin. UploadFileX(c *request.Context, channelID, name string, input io.Reader, opts ...func(*UploadFileTask)) (*model.FileInfo, *model.AppError) - // Uploads some files to the given team and channel as the given user. files and filenames should have - // the same length. clientIds should either not be provided or have the same length as files and filenames. - // The provided files should be closed by the caller so that they are not leaked. - UploadFiles(c *request.Context, teamID string, channelID string, userID string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) // UserIsInAdminRoleGroup returns true at least one of the user's groups are configured to set the members as // admins in the given syncable. UserIsInAdminRoleGroup(userID, syncableID string, syncableType model.GroupSyncableType) (bool, *model.AppError) @@ -1132,7 +1128,6 @@ type AppIface interface { UpdateUserRolesWithUser(c request.CTX, user *model.User, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError) UploadData(c *request.Context, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) UploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppError - UploadMultipartFiles(c *request.Context, teamID string, channelID string, userID string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError) UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) diff --git a/app/file.go b/app/file.go index 1f0e557e18..5b19e0c6b8 100644 --- a/app/file.go +++ b/app/file.go @@ -12,7 +12,6 @@ import ( "fmt" "image" "io" - "mime/multipart" "net/http" "net/url" "os" @@ -453,76 +452,6 @@ func GeneratePublicLinkHash(fileID, salt string) string { return base64.RawURLEncoding.EncodeToString(hash.Sum(nil)) } -func (a *App) UploadMultipartFiles(c *request.Context, teamID string, channelID string, userID string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { - files := make([]io.ReadCloser, len(fileHeaders)) - filenames := make([]string, len(fileHeaders)) - - for i, fileHeader := range fileHeaders { - file, fileErr := fileHeader.Open() - if fileErr != nil { - return nil, model.NewAppError("UploadFiles", "api.file.upload_file.read_request.app_error", - map[string]any{"Filename": fileHeader.Filename}, fileErr.Error(), http.StatusBadRequest) - } - - // Will be closed after UploadFiles returns - defer file.Close() - - files[i] = file - filenames[i] = fileHeader.Filename - } - - return a.UploadFiles(c, teamID, channelID, userID, files, filenames, clientIds, now) -} - -// Uploads some files to the given team and channel as the given user. files and filenames should have -// the same length. clientIds should either not be provided or have the same length as files and filenames. -// The provided files should be closed by the caller so that they are not leaked. -func (a *App) UploadFiles(c *request.Context, teamID string, channelID string, userID string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { - if *a.Config().FileSettings.DriverName == "" { - return nil, model.NewAppError("UploadFiles", "api.file.upload_file.storage.app_error", nil, "", http.StatusNotImplemented) - } - - if len(filenames) != len(files) || (len(clientIds) > 0 && len(clientIds) != len(files)) { - return nil, model.NewAppError("UploadFiles", "api.file.upload_file.incorrect_number_of_files.app_error", nil, "", http.StatusBadRequest) - } - - resStruct := &model.FileUploadResponse{ - FileInfos: []*model.FileInfo{}, - ClientIds: []string{}, - } - - previewPathList := []string{} - thumbnailPathList := []string{} - imageDataList := [][]byte{} - - for i, file := range files { - buf := bytes.NewBuffer(nil) - io.Copy(buf, file) - data := buf.Bytes() - - info, data, err := a.DoUploadFileExpectModification(c, now, teamID, channelID, userID, filenames[i], data) - if err != nil { - return nil, err - } - - if info.PreviewPath != "" || info.ThumbnailPath != "" { - previewPathList = append(previewPathList, info.PreviewPath) - thumbnailPathList = append(thumbnailPathList, info.ThumbnailPath) - imageDataList = append(imageDataList, data) - } - - resStruct.FileInfos = append(resStruct.FileInfos, info) - - if len(clientIds) > 0 { - resStruct.ClientIds = append(resStruct.ClientIds, clientIds[i]) - } - } - - a.HandleImages(previewPathList, thumbnailPathList, imageDataList) - - return resStruct, nil -} - // UploadFile uploads a single file in form of a completely constructed byte array for a channel. func (a *App) UploadFile(c request.CTX, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) { _, err := a.GetChannel(c, channelID) diff --git a/app/file_bench_test.go b/app/file_bench_test.go index 515f69cc03..608bd1eb31 100644 --- a/app/file_bench_test.go +++ b/app/file_bench_test.go @@ -9,8 +9,6 @@ import ( "image" "image/gif" "image/jpeg" - "io" - "io/ioutil" "math/rand" "testing" "time" @@ -130,21 +128,6 @@ func BenchmarkUploadFile(b *testing.B) { th.App.RemoveFile(info.Path) }, }, - { - title: "image UploadFiles", - f: func(b *testing.B, n int, data []byte, ext string) { - resp, err := th.App.UploadFiles(th.Context, teamID, channelID, userID, - []io.ReadCloser{ioutil.NopCloser(bytes.NewReader(data))}, - []string{fmt.Sprintf("BenchmarkDoUploadFiles-%d%s", n, ext)}, - []string{}, - time.Now()) - if err != nil { - b.Fatal(err) - } - th.App.Srv().Store.FileInfo().PermanentDelete(resp.FileInfos[0].Id) - th.App.RemoveFile(resp.FileInfos[0].Path) - }, - }, { title: "image UploadFileX Content-Length", f: func(b *testing.B, n int, data []byte, ext string) { diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 4abf8686ba..71ae068d0c 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -17813,50 +17813,6 @@ func (a *OpenTracingAppLayer) UploadFileX(c *request.Context, channelID string, return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) UploadFiles(c *request.Context, teamID string, channelID string, userID string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UploadFiles") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.UploadFiles(c, teamID, channelID, userID, files, filenames, clientIds, now) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) UploadMultipartFiles(c *request.Context, teamID string, channelID string, userID string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UploadMultipartFiles") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.UploadMultipartFiles(c, teamID, channelID, userID, fileHeaders, clientIds, now) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpsertGroupMember") diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 32945cfed7..e4eaa4d23e 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -462,15 +462,12 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - _, err := th.App.UploadFiles(th.Context, - "noteam", + _, err := th.App.UploadFile(th.Context, + []byte("inputfile"), th.BasicChannel.Id, - th.BasicUser.Id, - []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, - []string{"testhook.txt"}, - []string{}, - time.Now(), + "testhook.txt", ) + if assert.NotNil(t, err) { assert.Equal(t, "File rejected by plugin. rejected", err.Message) } @@ -515,15 +512,12 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - _, err := th.App.UploadFiles(th.Context, - "noteam", + _, err := th.App.UploadFile(th.Context, + []byte("inputfile"), th.BasicChannel.Id, - th.BasicUser.Id, - []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, - []string{"testhook.txt"}, - []string{}, - time.Now(), + "testhook.txt", ) + if assert.NotNil(t, err) { assert.Equal(t, "File rejected by plugin. rejected", err.Message) } @@ -562,20 +556,16 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - response, err := th.App.UploadFiles(th.Context, - "noteam", + response, err := th.App.UploadFile(th.Context, + []byte("inputfile"), th.BasicChannel.Id, - th.BasicUser.Id, - []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, - []string{"testhook.txt"}, - []string{}, - time.Now(), + "testhook.txt", ) + assert.Nil(t, err) assert.NotNil(t, response) - assert.Equal(t, 1, len(response.FileInfos)) - fileID := response.FileInfos[0].Id + fileID := response.Id fileInfo, err := th.App.GetFileInfo(fileID) assert.Nil(t, err) assert.NotNil(t, fileInfo) @@ -638,19 +628,14 @@ func TestHookFileWillBeUploaded(t *testing.T) { }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) defer tearDown() - response, err := th.App.UploadFiles(th.Context, - "noteam", + response, err := th.App.UploadFile(th.Context, + []byte("inputfile"), th.BasicChannel.Id, - th.BasicUser.Id, - []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, - []string{"testhook.txt"}, - []string{}, - time.Now(), + "testhook.txt", ) assert.Nil(t, err) assert.NotNil(t, response) - assert.Equal(t, 1, len(response.FileInfos)) - fileID := response.FileInfos[0].Id + fileID := response.Id fileInfo, err := th.App.GetFileInfo(fileID) assert.Nil(t, err) diff --git a/i18n/en.json b/i18n/en.json index 985491ceef..d5918e62f3 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1857,10 +1857,6 @@ "id": "api.file.upload_file.incorrect_number_of_client_ids.app_error", "translation": "Unable to upload file(s). Have {{.NumClientIds}} client_ids for {{.NumFiles}} files." }, - { - "id": "api.file.upload_file.incorrect_number_of_files.app_error", - "translation": "Unable to upload files. Incorrect number of files specified." - }, { "id": "api.file.upload_file.large_image.app_error", "translation": "File above maximum dimensions could not be uploaded: {{.Filename}}" diff --git a/shared/filestore/s3store.go b/shared/filestore/s3store.go index 229c3537a5..c8738a7b5a 100644 --- a/shared/filestore/s3store.go +++ b/shared/filestore/s3store.go @@ -4,6 +4,7 @@ package filestore import ( + "bytes" "context" "crypto/tls" "io" @@ -365,7 +366,16 @@ func (b *S3FileBackend) WriteFile(fr io.Reader, path string) (int64, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() options := s3PutOptions(b.encrypt, contentType) - info, err := b.client.PutObject(ctx, b.bucket, path, fr, -1, options) + + objSize := -1 + 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(ctx, b.bucket, path, fr, int64(objSize), options) if err != nil { return info.Size, errors.Wrapf(err, "unable write the data in the file %s", path) } @@ -393,7 +403,14 @@ func (b *S3FileBackend) AppendFile(fr io.Reader, path string) (int64, error) { partName := fp + ".part" ctx2, cancel2 := context.WithTimeout(context.Background(), b.timeout) defer cancel2() - info, err := b.client.PutObject(ctx2, b.bucket, partName, fr, -1, options) + objSize := -1 + 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) if err != nil { return 0, errors.Wrapf(err, "unable append the data in the file %s", path) } From 34832e0a3f0cdc54c5d1666e18aa3adf8afc32d5 Mon Sep 17 00:00:00 2001 From: Ashish Dhama Date: Thu, 4 Aug 2022 11:25:35 +0530 Subject: [PATCH 15/18] change post forwarding feature flag default value to true (#20759) --- model/feature_flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/feature_flags.go b/model/feature_flags.go index f96dae0f5b..8007f34587 100644 --- a/model/feature_flags.go +++ b/model/feature_flags.go @@ -98,7 +98,7 @@ 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 From 2365f1286bcc1c7d1a1ddb2747fa240ec5d92c09 Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Thu, 4 Aug 2022 12:15:54 +0300 Subject: [PATCH 16/18] [MM-46054] Log missing default channels (#20738) --- app/channel.go | 9 +-------- app/channel_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/channel.go b/app/channel.go index 3021a42c8b..6287cbc165 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 } diff --git a/app/channel_test.go b/app/channel_test.go index 50a6768ffe..2d5a9b559d 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() From 151f295d8221a1b3ee6f333560ed6e460947e51e Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 4 Aug 2022 22:07:19 +0530 Subject: [PATCH 17/18] MM-45021: Fix flaky testGetFlaggedPostsForUser (#20769) The method a.postRemoveFromChannelMessage was being called from a goroutine. Therefore, when SystemAdminClient.GetFlaggedPostsForUser was being called later in the test with a mock post store, it would naturally fail because the store would now be a different store but the goroutine was supposed to be finished. A hacky solution would be to add a sleep before starting the mocked API call. But a deeper question is why was the method run in a goroutine in the first place. Removing a user from a channel is not a very common operation and even if we look at the method, if the user is trying to remove themselves, that message happens synchronously, but if they are removing another user, that runs in a goroutine. This seems like a very weird behavior. Therefore, to be consistent I have just removed the goroutine and made everything synchronous. The next step would be to stop logging an error and just return the error upwards instead. Because that's what happens in the other condition. But that would be exceeding the scope too much. Maybe in a separate PR. https://mattermost.atlassian.net/browse/MM-45021 ```release-note NONE ``` --- app/channel.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/channel.go b/app/channel.go index 6287cbc165..860b44f125 100644 --- a/app/channel.go +++ b/app/channel.go @@ -2506,11 +2506,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 From 14246abdef4ae7a5ea18cb2362361b2537c8b01e Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 5 Aug 2022 08:49:01 +0530 Subject: [PATCH 18/18] MM-45993: Return errors during sending websocket messages (#20760) During attaching an object to a websocket message, we would marshal it to json and attach the string output. But if the marshalling failed, we would just log a warning and move on. This would add an empty string to the message. But the client assumes that the object is correctly attached and would fail silently if it cannot find it. So we become more strict and return the error so that it reaches the caller. https://mattermost.atlassian.net/browse/MM-45993 ```release-note NONE ``` --- app/app_iface.go | 14 ++-- app/channel.go | 23 ++--- app/channel_category.go | 2 +- app/channel_test.go | 2 +- app/emoji.go | 2 +- app/group.go | 30 ++++--- app/opentracing/opentracing_layer.go | 32 +++++-- app/post.go | 2 +- app/preference.go | 5 +- app/role.go | 20 +++-- app/slashcommands/command_expand_collapse.go | 5 +- app/syncables.go | 32 +++---- app/team.go | 88 +++++++++++++------- app/team_test.go | 2 +- app/user.go | 7 +- i18n/en.json | 4 + 16 files changed, 166 insertions(+), 104 deletions(-) diff --git a/app/app_iface.go b/app/app_iface.go index 384f3f95a3..f2466ad4c2 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -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. @@ -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) @@ -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 @@ -873,7 +873,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) @@ -954,7 +954,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 diff --git a/app/channel.go b/app/channel.go index 860b44f125..e5f0c7aefc 100644 --- a/app/channel.go +++ b/app/channel.go @@ -654,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) @@ -1012,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) @@ -1278,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) @@ -1307,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) @@ -1580,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() { @@ -2692,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)) @@ -3259,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)) @@ -3360,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 2d5a9b559d..459423ab29 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -2092,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/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/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/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 71ae068d0c..9b071c3ccc 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") @@ -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 { @@ -11731,7 +11745,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") @@ -13686,7 +13700,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") @@ -16153,7 +16167,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") diff --git a/app/post.go b/app/post.go index 716b821984..b537e43417 100644 --- a/app/post.go +++ b/app/post.go @@ -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/slashcommands/command_expand_collapse.go b/app/slashcommands/command_expand_collapse.go index ecfc832251..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 { @@ -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/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 1dfa276012..9d2a2eb3ac 100644 --- a/app/user.go +++ b/app/user.go @@ -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/i18n/en.json b/i18n/en.json index d5918e62f3..980c0ffe45 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6151,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."