From 86e228b6c63cae8d3b8a1d32c29f8fd0a5dd1d55 Mon Sep 17 00:00:00 2001 From: Eli Yukelzon Date: Sun, 6 Dec 2020 10:02:53 +0200 Subject: [PATCH] MM-30558 - Add unreadReplies and unreadMentions to thread membership (#16304) --- api4/api.go | 8 +- api4/user.go | 23 +- api4/user_test.go | 139 ++++++++---- app/app_iface.go | 8 +- app/opentracing/opentracing_layer.go | 16 +- app/post.go | 4 +- app/post_test.go | 6 +- app/user.go | 36 +++- einterfaces/mocks/CloudInterface.go | 238 +++++++++++++++++++++ einterfaces/mocks/CloudJobInterface.go | 47 ++++ model/client4.go | 26 +-- model/thread.go | 20 +- store/opentracinglayer/opentracinglayer.go | 12 +- store/retrylayer/retrylayer.go | 12 +- store/sqlstore/thread_store.go | 189 ++++++++++++---- store/store.go | 6 +- store/storetest/mocks/ThreadStore.go | 38 ++-- store/timerlayer/timerlayer.go | 12 +- 18 files changed, 653 insertions(+), 187 deletions(-) create mode 100644 einterfaces/mocks/CloudInterface.go create mode 100644 einterfaces/mocks/CloudJobInterface.go diff --git a/api4/api.go b/api4/api.go index ad761ec14a..926b619007 100644 --- a/api4/api.go +++ b/api4/api.go @@ -21,8 +21,6 @@ type Routes struct { Users *mux.Router // 'api/v4/users' User *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}' - UserThreads *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/threads' - UserThread *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/threads/{thread_id:[A-Za-z0-9]+}' UserByUsername *mux.Router // 'api/v4/users/username/{username:[A-Za-z0-9\\_\\-\\.]+}' UserByEmail *mux.Router // 'api/v4/users/email/{email:.+}' @@ -33,6 +31,8 @@ type Routes struct { TeamsForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams' Team *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}' TeamForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}' + UserThreads *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/threads' + UserThread *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/threads/{thread_id:[A-Za-z0-9]+}' TeamByName *mux.Router // 'api/v4/teams/name/{team_name:[A-Za-z0-9_-]+}' TeamMembers *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/members' TeamMember *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/members/{user_id:[A-Za-z0-9]+}' @@ -145,8 +145,6 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp api.BaseRoutes.Users = api.BaseRoutes.ApiRoot.PathPrefix("/users").Subrouter() api.BaseRoutes.User = api.BaseRoutes.ApiRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}").Subrouter() - api.BaseRoutes.UserThreads = api.BaseRoutes.ApiRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}/threads").Subrouter() - api.BaseRoutes.UserThread = api.BaseRoutes.ApiRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}/threads/{thread_id:[A-Za-z0-9]+}").Subrouter() api.BaseRoutes.UserByUsername = api.BaseRoutes.Users.PathPrefix("/username/{username:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() api.BaseRoutes.UserByEmail = api.BaseRoutes.Users.PathPrefix("/email/{email:.+}").Subrouter() @@ -157,6 +155,8 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp api.BaseRoutes.TeamsForUser = api.BaseRoutes.User.PathPrefix("/teams").Subrouter() api.BaseRoutes.Team = api.BaseRoutes.Teams.PathPrefix("/{team_id:[A-Za-z0-9]+}").Subrouter() api.BaseRoutes.TeamForUser = api.BaseRoutes.TeamsForUser.PathPrefix("/{team_id:[A-Za-z0-9]+}").Subrouter() + api.BaseRoutes.UserThreads = api.BaseRoutes.TeamForUser.PathPrefix("/threads").Subrouter() + api.BaseRoutes.UserThread = api.BaseRoutes.TeamForUser.PathPrefix("/threads/{thread_id:[A-Za-z0-9]+}").Subrouter() api.BaseRoutes.TeamByName = api.BaseRoutes.Teams.PathPrefix("/name/{team_name:[A-Za-z0-9_-]+}").Subrouter() api.BaseRoutes.TeamMembers = api.BaseRoutes.Team.PathPrefix("/members").Subrouter() api.BaseRoutes.TeamMember = api.BaseRoutes.TeamMembers.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter() diff --git a/api4/user.go b/api4/user.go index 7c4eb190ff..e7a9760e03 100644 --- a/api4/user.go +++ b/api4/user.go @@ -93,7 +93,7 @@ func (api *API) InitUser() { api.BaseRoutes.User.Handle("/uploads", api.ApiSessionRequired(getUploadsForUser)).Methods("GET") api.BaseRoutes.UserThreads.Handle("", api.ApiSessionRequired(getThreadsForUser)).Methods("GET") - api.BaseRoutes.UserThreads.Handle("/read/{timestamp:[0-9]+}", api.ApiSessionRequired(updateReadStateAllThreadsByUser)).Methods("PUT") + api.BaseRoutes.UserThreads.Handle("/read", api.ApiSessionRequired(updateReadStateAllThreadsByUser)).Methods("PUT") api.BaseRoutes.UserThread.Handle("/following", api.ApiSessionRequired(followThreadByUser)).Methods("PUT") api.BaseRoutes.UserThread.Handle("/following", api.ApiSessionRequired(unfollowThreadByUser)).Methods("DELETE") @@ -2815,7 +2815,7 @@ func migrateAuthToSaml(c *Context, w http.ResponseWriter, r *http.Request) { } func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) { - c.RequireUserId() + c.RequireUserId().RequireTeamId() if c.Err != nil { return } @@ -2869,7 +2869,7 @@ func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) { options.Deleted, _ = strconv.ParseBool(deletedStr) options.Extended, _ = strconv.ParseBool(extendedStr) - threads, err := c.App.GetThreadsForUser(c.Params.UserId, options) + threads, err := c.App.GetThreadsForUser(c.Params.UserId, c.Params.TeamId, options) if err != nil { c.Err = err return @@ -2879,7 +2879,7 @@ func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) { } func updateReadStateThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { - c.RequireUserId().RequireThreadId().RequireTimestamp() + c.RequireUserId().RequireThreadId().RequireTimestamp().RequireTeamId() if c.Err != nil { return } @@ -2888,13 +2888,14 @@ func updateReadStateThreadByUser(c *Context, w http.ResponseWriter, r *http.Requ defer c.LogAuditRec(auditRec) auditRec.AddMeta("user_id", c.Params.UserId) auditRec.AddMeta("thread_id", c.Params.ThreadId) + auditRec.AddMeta("team_id", c.Params.TeamId) auditRec.AddMeta("timestamp", c.Params.Timestamp) if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return } - err := c.App.UpdateThreadReadForUser(c.Params.UserId, c.Params.ThreadId, c.Params.Timestamp) + err := c.App.UpdateThreadReadForUser(c.Params.UserId, c.Params.TeamId, c.Params.ThreadId, c.Params.Timestamp) if err != nil { c.Err = err return @@ -2906,7 +2907,7 @@ func updateReadStateThreadByUser(c *Context, w http.ResponseWriter, r *http.Requ } func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { - c.RequireUserId().RequireThreadId() + c.RequireUserId().RequireThreadId().RequireTeamId() if c.Err != nil { return } @@ -2915,6 +2916,7 @@ func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { defer c.LogAuditRec(auditRec) auditRec.AddMeta("user_id", c.Params.UserId) auditRec.AddMeta("thread_id", c.Params.ThreadId) + auditRec.AddMeta("team_id", c.Params.TeamId) if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -2933,7 +2935,7 @@ func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { } func followThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { - c.RequireUserId().RequireThreadId() + c.RequireUserId().RequireThreadId().RequireTeamId() if c.Err != nil { return } @@ -2942,6 +2944,7 @@ func followThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { defer c.LogAuditRec(auditRec) auditRec.AddMeta("user_id", c.Params.UserId) auditRec.AddMeta("thread_id", c.Params.ThreadId) + auditRec.AddMeta("team_id", c.Params.TeamId) if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -2959,7 +2962,7 @@ func followThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { } func updateReadStateAllThreadsByUser(c *Context, w http.ResponseWriter, r *http.Request) { - c.RequireUserId().RequireTimestamp() + c.RequireUserId().RequireTeamId() if c.Err != nil { return } @@ -2967,14 +2970,14 @@ func updateReadStateAllThreadsByUser(c *Context, w http.ResponseWriter, r *http. auditRec := c.MakeAuditRecord("updateReadStateAllThreadsByUser", audit.Fail) defer c.LogAuditRec(auditRec) auditRec.AddMeta("user_id", c.Params.UserId) - auditRec.AddMeta("timestamp", c.Params.Timestamp) + auditRec.AddMeta("team_id", c.Params.TeamId) if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return } - err := c.App.UpdateThreadsReadForUser(c.Params.UserId, c.Params.Timestamp) + err := c.App.UpdateThreadsReadForUser(c.Params.UserId, c.Params.TeamId) if err != nil { c.Err = err return diff --git a/api4/user_test.go b/api4/user_test.go index 465f600a5b..1884764943 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -5269,7 +5269,7 @@ func TestGetThreadsForUser(t *testing.T) { defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) - uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, }) @@ -5289,7 +5289,7 @@ func TestGetThreadsForUser(t *testing.T) { defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) - uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, }) @@ -5311,7 +5311,7 @@ func TestGetThreadsForUser(t *testing.T) { defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) - uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Extended: true, @@ -5335,7 +5335,7 @@ func TestGetThreadsForUser(t *testing.T) { defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) - uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5350,7 +5350,7 @@ func TestGetThreadsForUser(t *testing.T) { require.True(t, res) require.Nil(t, resp2.Error) - uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5358,7 +5358,7 @@ func TestGetThreadsForUser(t *testing.T) { require.Nil(t, resp.Error) require.Len(t, uss.Threads, 0) - uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: true, @@ -5387,7 +5387,7 @@ func TestGetThreadsForUser(t *testing.T) { defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) - uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5446,7 +5446,7 @@ func TestThreadSocketEvents(t *testing.T) { require.Truef(t, caught, "User should have received %s event", model.WEBSOCKET_EVENT_THREAD_UPDATED) }) - resp = th.Client.UpdateThreadFollowForUser(th.BasicUser.Id, rpost.Id, false) + resp = th.Client.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, false) CheckNoError(t, resp) CheckOKStatus(t, resp) @@ -5468,7 +5468,7 @@ func TestThreadSocketEvents(t *testing.T) { require.Truef(t, caught, "User should have received %s event", model.WEBSOCKET_EVENT_THREAD_FOLLOW_CHANGED) }) - resp = th.Client.UpdateThreadReadForUser(th.BasicUser.Id, rpost.Id, 123) + resp = th.Client.UpdateThreadReadForUser(th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, 123) CheckNoError(t, resp) CheckOKStatus(t, resp) @@ -5509,7 +5509,7 @@ func TestFollowThreads(t *testing.T) { defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) var uss *model.Threads - uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5517,11 +5517,11 @@ func TestFollowThreads(t *testing.T) { CheckNoError(t, resp) require.Len(t, uss.Threads, 1) - resp = th.Client.UpdateThreadFollowForUser(th.BasicUser.Id, rpost.Id, false) + resp = th.Client.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, false) CheckNoError(t, resp) CheckOKStatus(t, resp) - uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5529,11 +5529,11 @@ func TestFollowThreads(t *testing.T) { CheckNoError(t, resp) require.Len(t, uss.Threads, 0) - resp = th.Client.UpdateThreadFollowForUser(th.BasicUser.Id, rpost.Id, true) + resp = th.Client.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, true) CheckNoError(t, resp) CheckOKStatus(t, resp) - uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5544,13 +5544,76 @@ func TestFollowThreads(t *testing.T) { }) } +func TestMaintainUnreadRepliesInThread(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.ThreadAutoFollow = true + *cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON + }) + + Client := th.Client + defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) + defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.SystemAdminUser.Id) + + // create a post by regular user + rpost, resp := Client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testMsg"}) + CheckNoError(t, resp) + CheckCreatedStatus(t, resp) + // reply with another + _, resp2 := th.SystemAdminClient.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id}) + CheckNoError(t, resp2) + CheckCreatedStatus(t, resp2) + + checkThreadList := func(client *model.Client4, userId string, expectedReplies, expectedThreads int) (*model.Threads, *model.Response) { + u, r := client.GetUserThreads(userId, th.BasicTeam.Id, model.GetUserThreadsOpts{ + Page: 0, + PageSize: 30, + Deleted: false, + }) + CheckNoError(t, r) + require.Len(t, u.Threads, expectedThreads) + require.EqualValues(t, expectedReplies, u.Threads[0].UnreadReplies) + + sum := int64(0) + for _, thr := range u.Threads { + sum += thr.UnreadReplies + } + require.Equal(t, sum, u.TotalUnreadReplies) + + return u, r + } + // regular user should have one thread with one reply + checkThreadList(th.Client, th.BasicUser.Id, 1, 1) + + // add another reply by regular user + _, resp3 := Client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply2", RootId: rpost.Id}) + CheckNoError(t, resp3) + CheckCreatedStatus(t, resp3) + + // replying to the thread clears reply count, so it should be 0 + checkThreadList(th.Client, th.BasicUser.Id, 0, 1) + + // the other user should have 2 replies + checkThreadList(th.SystemAdminClient, th.SystemAdminUser.Id, 2, 1) + + // mark all as read for user + resp = th.Client.UpdateThreadsReadForUser(th.BasicUser.Id, th.BasicTeam.Id) + CheckNoError(t, resp) + CheckOKStatus(t, resp) + + // reply count should be 0 + checkThreadList(th.Client, th.BasicUser.Id, 0, 1) + + // the other user should also have 2 + checkThreadList(th.SystemAdminClient, th.SystemAdminUser.Id, 2, 1) +} func postAndCheck(t *testing.T, client *model.Client4, post *model.Post) (*model.Post, *model.Response) { p, resp := client.CreatePost(post) CheckNoError(t, resp) CheckCreatedStatus(t, resp) return p, resp } - func TestMaintainUnreadMentionsInThread(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -5559,24 +5622,19 @@ func TestMaintainUnreadMentionsInThread(t *testing.T) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON }) - checkThreadList := func(client *model.Client4, userId string, expectedMentions, expectedThreads int) (*model.Threads, *model.Response) { - uss, resp := client.GetUserThreads(userId, model.GetUserThreadsOpts{ + uss, resp := client.GetUserThreads(userId, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, }) CheckNoError(t, resp) require.Len(t, uss.Threads, expectedThreads) - - // validate amount of mentions via store. once GetUserThreads starts returning mentions - update - memberships, err := th.App.Srv().Store.Thread().GetMembershipsForUser(userId) - require.NoError(t, err) sum := int64(0) - for _, membership := range memberships { - sum += membership.UnreadMentions + for _, thr := range uss.Threads { + sum += thr.UnreadMentions } - require.EqualValues(t, expectedMentions, sum) + require.Equal(t, sum, uss.TotalUnreadMentions) return uss, resp } @@ -5628,13 +5686,13 @@ func TestReadThreads(t *testing.T) { rpost, resp := Client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testMsg"}) CheckNoError(t, resp) CheckCreatedStatus(t, resp) - rpost2, resp2 := Client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id}) + _, resp2 := Client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id}) CheckNoError(t, resp2) CheckCreatedStatus(t, resp2) defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) - var uss, uss2, uss3 *model.Threads - uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + var uss, uss2 *model.Threads + uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5643,11 +5701,11 @@ func TestReadThreads(t *testing.T) { require.Len(t, uss.Threads, 1) time.Sleep(1) - resp = th.Client.UpdateThreadsReadForUser(th.BasicUser.Id, model.GetMillis()) + resp = th.Client.UpdateThreadsReadForUser(th.BasicUser.Id, th.BasicTeam.Id) CheckNoError(t, resp) CheckOKStatus(t, resp) - uss2, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss2, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5655,19 +5713,6 @@ func TestReadThreads(t *testing.T) { CheckNoError(t, resp) require.Len(t, uss2.Threads, 1) require.Greater(t, uss2.Threads[0].LastViewedAt, uss.Threads[0].LastViewedAt) - - resp = th.Client.UpdateThreadsReadForUser(th.BasicUser.Id, rpost2.UpdateAt) - CheckNoError(t, resp) - CheckOKStatus(t, resp) - - uss3, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ - Page: 0, - PageSize: 30, - Deleted: false, - }) - CheckNoError(t, resp) - require.Len(t, uss3.Threads, 1) - require.Equal(t, uss3.Threads[0].LastViewedAt, rpost2.UpdateAt) }) t.Run("1 thread", func(t *testing.T) { @@ -5690,7 +5735,7 @@ func TestReadThreads(t *testing.T) { defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) var uss, uss2, uss3 *model.Threads - uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5698,11 +5743,11 @@ func TestReadThreads(t *testing.T) { CheckNoError(t, resp) require.Len(t, uss.Threads, 2) - resp = th.Client.UpdateThreadReadForUser(th.BasicUser.Id, rrpost.Id, model.GetMillis()) + resp = th.Client.UpdateThreadReadForUser(th.BasicUser.Id, th.BasicTeam.Id, rrpost.Id, model.GetMillis()) CheckNoError(t, resp) CheckOKStatus(t, resp) - uss2, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss2, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, @@ -5712,11 +5757,11 @@ func TestReadThreads(t *testing.T) { require.Greater(t, uss2.Threads[1].LastViewedAt, uss.Threads[1].LastViewedAt) timestamp := model.GetMillis() - resp = th.Client.UpdateThreadReadForUser(th.BasicUser.Id, rrpost.Id, timestamp) + resp = th.Client.UpdateThreadReadForUser(th.BasicUser.Id, th.BasicTeam.Id, rrpost.Id, timestamp) CheckNoError(t, resp) CheckOKStatus(t, resp) - uss3, resp = th.Client.GetUserThreads(th.BasicUser.Id, model.GetUserThreadsOpts{ + uss3, resp = th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{ Page: 0, PageSize: 30, Deleted: false, diff --git a/app/app_iface.go b/app/app_iface.go index a83f7f0acc..60f8652b67 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -691,8 +691,8 @@ type AppIface interface { GetTeamsForUser(userId string) ([]*model.Team, *model.AppError) GetTeamsUnreadForUser(excludeTeamId string, userId string) ([]*model.TeamUnread, *model.AppError) GetTermsOfService(id string) (*model.TermsOfService, *model.AppError) - GetThreadMembershipsForUser(userId string) ([]*model.ThreadMembership, error) - GetThreadsForUser(userId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) + GetThreadMembershipsForUser(userId, teamId string) ([]*model.ThreadMembership, error) + GetThreadsForUser(userId, teamId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError) GetUploadSessionsForUser(userId string) ([]*model.UploadSession, *model.AppError) GetUser(userId string) (*model.User, *model.AppError) @@ -1002,8 +1002,8 @@ type AppIface interface { UpdateTeamPrivacy(teamId string, teamType string, allowOpenInvite bool) *model.AppError UpdateTeamScheme(team *model.Team) (*model.Team, *model.AppError) UpdateThreadFollowForUser(userId, threadId string, state bool) *model.AppError - UpdateThreadReadForUser(userId, threadId string, timestamp int64) *model.AppError - UpdateThreadsReadForUser(userId string, timestamp int64) *model.AppError + UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) *model.AppError + UpdateThreadsReadForUser(userId, teamId string) *model.AppError UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) UpdateUserActive(userId string, active bool) *model.AppError UpdateUserAsUser(user *model.User, asAdmin bool) (*model.User, *model.AppError) diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 1fb2f8747f..a2f6d4a26d 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -8485,7 +8485,7 @@ func (a *OpenTracingAppLayer) GetTermsOfService(id string) (*model.TermsOfServic return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetThreadMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { +func (a *OpenTracingAppLayer) GetThreadMembershipsForUser(userId string, teamId string) ([]*model.ThreadMembership, error) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetThreadMembershipsForUser") @@ -8497,7 +8497,7 @@ func (a *OpenTracingAppLayer) GetThreadMembershipsForUser(userId string) ([]*mod }() defer span.Finish() - resultVar0, resultVar1 := a.app.GetThreadMembershipsForUser(userId) + resultVar0, resultVar1 := a.app.GetThreadMembershipsForUser(userId, teamId) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) @@ -8507,7 +8507,7 @@ func (a *OpenTracingAppLayer) GetThreadMembershipsForUser(userId string) ([]*mod return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetThreadsForUser(userId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) { +func (a *OpenTracingAppLayer) GetThreadsForUser(userId string, teamId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetThreadsForUser") @@ -8519,7 +8519,7 @@ func (a *OpenTracingAppLayer) GetThreadsForUser(userId string, options model.Get }() defer span.Finish() - resultVar0, resultVar1 := a.app.GetThreadsForUser(userId, options) + resultVar0, resultVar1 := a.app.GetThreadsForUser(userId, teamId, options) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) @@ -15319,7 +15319,7 @@ func (a *OpenTracingAppLayer) UpdateThreadFollowForUser(userId string, threadId return resultVar0 } -func (a *OpenTracingAppLayer) UpdateThreadReadForUser(userId string, threadId string, timestamp int64) *model.AppError { +func (a *OpenTracingAppLayer) UpdateThreadReadForUser(userId string, teamId string, threadId string, timestamp int64) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadReadForUser") @@ -15331,7 +15331,7 @@ func (a *OpenTracingAppLayer) UpdateThreadReadForUser(userId string, threadId st }() defer span.Finish() - resultVar0 := a.app.UpdateThreadReadForUser(userId, threadId, timestamp) + resultVar0 := a.app.UpdateThreadReadForUser(userId, teamId, threadId, timestamp) if resultVar0 != nil { span.LogFields(spanlog.Error(resultVar0)) @@ -15341,7 +15341,7 @@ func (a *OpenTracingAppLayer) UpdateThreadReadForUser(userId string, threadId st return resultVar0 } -func (a *OpenTracingAppLayer) UpdateThreadsReadForUser(userId string, timestamp int64) *model.AppError { +func (a *OpenTracingAppLayer) UpdateThreadsReadForUser(userId string, teamId string) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadsReadForUser") @@ -15353,7 +15353,7 @@ func (a *OpenTracingAppLayer) UpdateThreadsReadForUser(userId string, timestamp }() defer span.Finish() - resultVar0 := a.app.UpdateThreadsReadForUser(userId, timestamp) + resultVar0 := a.app.UpdateThreadsReadForUser(userId, teamId) if resultVar0 != nil { span.LogFields(spanlog.Error(resultVar0)) diff --git a/app/post.go b/app/post.go index e8cddc31f9..4ab5c0f188 100644 --- a/app/post.go +++ b/app/post.go @@ -1538,6 +1538,6 @@ func isPostMention(user *model.User, post *model.Post, keywords map[string][]str return false } -func (a *App) GetThreadMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { - return a.Srv().Store.Thread().GetMembershipsForUser(userId) +func (a *App) GetThreadMembershipsForUser(userId, teamId string) ([]*model.ThreadMembership, error) { + return a.Srv().Store.Thread().GetMembershipsForUser(userId, teamId) } diff --git a/app/post_test.go b/app/post_test.go index 196ed71256..0d51b1bdd3 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -1892,11 +1892,11 @@ func TestThreadMembership(t *testing.T) { require.Nil(t, err) // first user should now be part of the thread since they replied to a post - memberships, err2 := th.App.GetThreadMembershipsForUser(user1.Id) + memberships, err2 := th.App.GetThreadMembershipsForUser(user1.Id, th.BasicTeam.Id) require.Nil(t, err2) require.Len(t, memberships, 1) // second user should also be part of a thread since they were mentioned - memberships, err2 = th.App.GetThreadMembershipsForUser(user2.Id) + memberships, err2 = th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id) require.Nil(t, err2) require.Len(t, memberships, 1) @@ -1916,7 +1916,7 @@ func TestThreadMembership(t *testing.T) { require.Nil(t, err) // first user should now be part of two threads - memberships, err2 = th.App.GetThreadMembershipsForUser(user1.Id) + memberships, err2 = th.App.GetThreadMembershipsForUser(user1.Id, th.BasicTeam.Id) require.Nil(t, err2) require.Len(t, memberships, 2) }) diff --git a/app/user.go b/app/user.go index dda7bed393..e1cf755963 100644 --- a/app/user.go +++ b/app/user.go @@ -2371,8 +2371,8 @@ func (a *App) ConvertBotToUser(bot *model.Bot, userPatch *model.UserPatch, sysad return user, nil } -func (a *App) GetThreadsForUser(userId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) { - threads, err := a.Srv().Store.Thread().GetThreadsForUser(userId, options) +func (a *App) GetThreadsForUser(userId, teamId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) { + threads, err := a.Srv().Store.Thread().GetThreadsForUser(userId, teamId, options) if err != nil { return nil, model.NewAppError("GetThreadsForUser", "app.user.get_threads_for_user.app_error", nil, err.Error(), http.StatusInternalServerError) } @@ -2383,13 +2383,12 @@ func (a *App) GetThreadsForUser(userId string, options model.GetUserThreadsOpts) return threads, nil } -func (a *App) UpdateThreadsReadForUser(userId string, timestamp int64) *model.AppError { - nErr := a.Srv().Store.Thread().MarkAllAsRead(userId, timestamp) +func (a *App) UpdateThreadsReadForUser(userId, teamId string) *model.AppError { + nErr := a.Srv().Store.Thread().MarkAllAsRead(userId, teamId) if nErr != nil { return model.NewAppError("UpdateThreadsReadForUser", "app.user.update_threads_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError) } message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_THREAD_READ_CHANGED, "", "", userId, nil) - message.Add("timestamp", timestamp) a.Publish(message) return nil } @@ -2406,8 +2405,31 @@ func (a *App) UpdateThreadFollowForUser(userId, threadId string, state bool) *mo return nil } -func (a *App) UpdateThreadReadForUser(userId, threadId string, timestamp int64) *model.AppError { - nErr := a.Srv().Store.Thread().MarkAsRead(userId, threadId, timestamp) +func (a *App) UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) *model.AppError { + user, err := a.GetUser(userId) + if err != nil { + return err + } + membership, nErr := a.Srv().Store.Thread().GetMembershipForUser(userId, threadId) + if nErr != nil { + return model.NewAppError("UpdateThreadsReadForUser", "app.user.update_threads_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError) + } + post, err := a.GetSinglePost(threadId) + if err != nil { + return err + } + + membership.UnreadMentions, err = a.countThreadMentions(user, post, teamId, timestamp) + if err != nil { + return err + } + membership.Following = true + _, nErr = a.Srv().Store.Thread().UpdateMembership(membership) + if nErr != nil { + return model.NewAppError("UpdateThreadsReadForUser", "app.user.update_threads_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError) + } + + nErr = a.Srv().Store.Thread().MarkAsRead(userId, threadId, timestamp) if nErr != nil { return model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError) } diff --git a/einterfaces/mocks/CloudInterface.go b/einterfaces/mocks/CloudInterface.go new file mode 100644 index 0000000000..9a78417e08 --- /dev/null +++ b/einterfaces/mocks/CloudInterface.go @@ -0,0 +1,238 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +// Regenerate this file using `make einterfaces-mocks`. + +package mocks + +import ( + model "github.com/mattermost/mattermost-server/v5/model" + mock "github.com/stretchr/testify/mock" +) + +// CloudInterface is an autogenerated mock type for the CloudInterface type +type CloudInterface struct { + mock.Mock +} + +// ConfirmCustomerPayment provides a mock function with given fields: _a0 +func (_m *CloudInterface) ConfirmCustomerPayment(_a0 *model.ConfirmPaymentMethodRequest) *model.AppError { + ret := _m.Called(_a0) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(*model.ConfirmPaymentMethodRequest) *model.AppError); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + +// CreateCustomerPayment provides a mock function with given fields: +func (_m *CloudInterface) CreateCustomerPayment() (*model.StripeSetupIntent, *model.AppError) { + ret := _m.Called() + + var r0 *model.StripeSetupIntent + if rf, ok := ret.Get(0).(func() *model.StripeSetupIntent); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.StripeSetupIntent) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetCloudCustomer provides a mock function with given fields: +func (_m *CloudInterface) GetCloudCustomer() (*model.CloudCustomer, *model.AppError) { + ret := _m.Called() + + var r0 *model.CloudCustomer + if rf, ok := ret.Get(0).(func() *model.CloudCustomer); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.CloudCustomer) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetCloudProducts provides a mock function with given fields: +func (_m *CloudInterface) GetCloudProducts() ([]*model.Product, *model.AppError) { + ret := _m.Called() + + var r0 []*model.Product + if rf, ok := ret.Get(0).(func() []*model.Product); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Product) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetInvoicePDF provides a mock function with given fields: invoiceID +func (_m *CloudInterface) GetInvoicePDF(invoiceID string) ([]byte, string, *model.AppError) { + ret := _m.Called(invoiceID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(string) []byte); ok { + r0 = rf(invoiceID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 string + if rf, ok := ret.Get(1).(func(string) string); ok { + r1 = rf(invoiceID) + } else { + r1 = ret.Get(1).(string) + } + + var r2 *model.AppError + if rf, ok := ret.Get(2).(func(string) *model.AppError); ok { + r2 = rf(invoiceID) + } else { + if ret.Get(2) != nil { + r2 = ret.Get(2).(*model.AppError) + } + } + + return r0, r1, r2 +} + +// GetInvoicesForSubscription provides a mock function with given fields: +func (_m *CloudInterface) GetInvoicesForSubscription() ([]*model.Invoice, *model.AppError) { + ret := _m.Called() + + var r0 []*model.Invoice + if rf, ok := ret.Get(0).(func() []*model.Invoice); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Invoice) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetSubscription provides a mock function with given fields: +func (_m *CloudInterface) GetSubscription() (*model.Subscription, *model.AppError) { + ret := _m.Called() + + var r0 *model.Subscription + if rf, ok := ret.Get(0).(func() *model.Subscription); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Subscription) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// UpdateCloudCustomer provides a mock function with given fields: customerInfo +func (_m *CloudInterface) UpdateCloudCustomer(customerInfo *model.CloudCustomerInfo) (*model.CloudCustomer, *model.AppError) { + ret := _m.Called(customerInfo) + + var r0 *model.CloudCustomer + if rf, ok := ret.Get(0).(func(*model.CloudCustomerInfo) *model.CloudCustomer); ok { + r0 = rf(customerInfo) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.CloudCustomer) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(*model.CloudCustomerInfo) *model.AppError); ok { + r1 = rf(customerInfo) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// UpdateCloudCustomerAddress provides a mock function with given fields: address +func (_m *CloudInterface) UpdateCloudCustomerAddress(address *model.Address) (*model.CloudCustomer, *model.AppError) { + ret := _m.Called(address) + + var r0 *model.CloudCustomer + if rf, ok := ret.Get(0).(func(*model.Address) *model.CloudCustomer); ok { + r0 = rf(address) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.CloudCustomer) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(*model.Address) *model.AppError); ok { + r1 = rf(address) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} diff --git a/einterfaces/mocks/CloudJobInterface.go b/einterfaces/mocks/CloudJobInterface.go new file mode 100644 index 0000000000..8808c98ae8 --- /dev/null +++ b/einterfaces/mocks/CloudJobInterface.go @@ -0,0 +1,47 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +// Regenerate this file using `make einterfaces-mocks`. + +package mocks + +import ( + model "github.com/mattermost/mattermost-server/v5/model" + mock "github.com/stretchr/testify/mock" +) + +// CloudJobInterface is an autogenerated mock type for the CloudJobInterface type +type CloudJobInterface struct { + mock.Mock +} + +// MakeScheduler provides a mock function with given fields: +func (_m *CloudJobInterface) MakeScheduler() model.Scheduler { + ret := _m.Called() + + var r0 model.Scheduler + if rf, ok := ret.Get(0).(func() model.Scheduler); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(model.Scheduler) + } + } + + return r0 +} + +// MakeWorker provides a mock function with given fields: +func (_m *CloudJobInterface) MakeWorker() model.Worker { + ret := _m.Called() + + var r0 model.Worker + if rf, ok := ret.Get(0).(func() model.Worker); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(model.Worker) + } + } + + return r0 +} diff --git a/model/client4.go b/model/client4.go index a6a76a959b..5c93a0276c 100644 --- a/model/client4.go +++ b/model/client4.go @@ -190,12 +190,12 @@ func (c *Client4) GetUserRoute(userId string) string { return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId) } -func (c *Client4) GetUserThreadsRoute(userId string) string { - return fmt.Sprintf(c.GetUsersRoute()+"/%v/threads", userId) +func (c *Client4) GetUserThreadsRoute(userID, teamID string) string { + return c.GetUserRoute(userID) + c.GetTeamRoute(teamID) + "/threads" } -func (c *Client4) GetUserThreadRoute(userId, threadId string) string { - return fmt.Sprintf(c.GetUserThreadsRoute(userId)+"/%v", threadId) +func (c *Client4) GetUserThreadRoute(userId, teamId, threadId string) string { + return c.GetUserThreadsRoute(userId, teamId) + "/" + threadId } func (c *Client4) GetUserCategoryRoute(userID, teamID string) string { @@ -5767,7 +5767,7 @@ func (c *Client4) ListImports() ([]string, *Response) { return ArrayFromJson(r.Body), BuildResponse(r) } -func (c *Client4) GetUserThreads(userId string, options GetUserThreadsOpts) (*Threads, *Response) { +func (c *Client4) GetUserThreads(userId, teamId string, options GetUserThreadsOpts) (*Threads, *Response) { v := url.Values{} if options.Since != 0 { v.Set("since", fmt.Sprintf("%d", options.Since)) @@ -5785,7 +5785,7 @@ func (c *Client4) GetUserThreads(userId string, options GetUserThreadsOpts) (*Th v.Set("deleted", "true") } - url := c.GetUserThreadsRoute(userId) + url := c.GetUserThreadsRoute(userId, teamId) if len(v) > 0 { url += "?" + v.Encode() } @@ -5802,8 +5802,8 @@ func (c *Client4) GetUserThreads(userId string, options GetUserThreadsOpts) (*Th return &threads, BuildResponse(r) } -func (c *Client4) UpdateThreadsReadForUser(userId string, timestamp int64) *Response { - r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadsRoute(userId), timestamp), "") +func (c *Client4) UpdateThreadsReadForUser(userId, teamId string) *Response { + r, appErr := c.DoApiPut(fmt.Sprintf("%s/read", c.GetUserThreadsRoute(userId, teamId)), "") if appErr != nil { return BuildErrorResponse(r, appErr) } @@ -5812,8 +5812,8 @@ func (c *Client4) UpdateThreadsReadForUser(userId string, timestamp int64) *Resp return BuildResponse(r) } -func (c *Client4) UpdateThreadReadForUser(userId, threadId string, timestamp int64) *Response { - r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadRoute(userId, threadId), timestamp), "") +func (c *Client4) UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) *Response { + r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadRoute(userId, teamId, threadId), timestamp), "") if appErr != nil { return BuildErrorResponse(r, appErr) } @@ -5822,13 +5822,13 @@ func (c *Client4) UpdateThreadReadForUser(userId, threadId string, timestamp int return BuildResponse(r) } -func (c *Client4) UpdateThreadFollowForUser(userId, threadId string, state bool) *Response { +func (c *Client4) UpdateThreadFollowForUser(userId, teamId, threadId string, state bool) *Response { var appErr *AppError var r *http.Response if state { - r, appErr = c.DoApiPut(c.GetUserThreadRoute(userId, threadId)+"/following", "") + r, appErr = c.DoApiPut(c.GetUserThreadRoute(userId, teamId, threadId)+"/following", "") } else { - r, appErr = c.DoApiDelete(c.GetUserThreadRoute(userId, threadId) + "/following") + r, appErr = c.DoApiDelete(c.GetUserThreadRoute(userId, teamId, threadId) + "/following") } if appErr != nil { return BuildErrorResponse(r, appErr) diff --git a/model/thread.go b/model/thread.go index eb4b7faf8e..794a86b7ef 100644 --- a/model/thread.go +++ b/model/thread.go @@ -16,17 +16,21 @@ type Thread struct { } type ThreadResponse struct { - PostId string `json:"id"` - ReplyCount int64 `json:"reply_count"` - LastReplyAt int64 `json:"last_reply_at"` - LastViewedAt int64 `json:"last_viewed_at"` - Participants []*User `json:"participants"` - Post *Post `json:"post"` + PostId string `json:"id"` + ReplyCount int64 `json:"reply_count"` + LastReplyAt int64 `json:"last_reply_at"` + LastViewedAt int64 `json:"last_viewed_at"` + Participants []*User `json:"participants"` + Post *Post `json:"post"` + UnreadReplies int64 `json:"unread_replies"` + UnreadMentions int64 `json:"unread_mentions"` } type Threads struct { - Total int64 `json:"total"` - Threads []*ThreadResponse `json:"threads"` + Total int64 `json:"total"` + TotalUnreadReplies int64 `json:"total_unread_replies"` + TotalUnreadMentions int64 `json:"total_unread_mentions"` + Threads []*ThreadResponse `json:"threads"` } type GetUserThreadsOpts struct { diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index f7e43383d7..b22596721a 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -7756,7 +7756,7 @@ func (s *OpenTracingLayerThreadStore) GetMembershipForUser(userId string, postId return result, err } -func (s *OpenTracingLayerThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { +func (s *OpenTracingLayerThreadStore) GetMembershipsForUser(userId string, teamId string) ([]*model.ThreadMembership, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetMembershipsForUser") s.Root.Store.SetContext(newCtx) @@ -7765,7 +7765,7 @@ func (s *OpenTracingLayerThreadStore) GetMembershipsForUser(userId string) ([]*m }() defer span.Finish() - result, err := s.ThreadStore.GetMembershipsForUser(userId) + result, err := s.ThreadStore.GetMembershipsForUser(userId, teamId) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -7792,7 +7792,7 @@ func (s *OpenTracingLayerThreadStore) GetPosts(threadId string, since int64) ([] return result, err } -func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { +func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadsForUser") s.Root.Store.SetContext(newCtx) @@ -7801,7 +7801,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, opts mode }() defer span.Finish() - result, err := s.ThreadStore.GetThreadsForUser(userId, opts) + result, err := s.ThreadStore.GetThreadsForUser(userId, teamId, opts) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -7810,7 +7810,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, opts mode return result, err } -func (s *OpenTracingLayerThreadStore) MarkAllAsRead(userId string, timestamp int64) error { +func (s *OpenTracingLayerThreadStore) MarkAllAsRead(userId string, teamId string) error { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.MarkAllAsRead") s.Root.Store.SetContext(newCtx) @@ -7819,7 +7819,7 @@ func (s *OpenTracingLayerThreadStore) MarkAllAsRead(userId string, timestamp int }() defer span.Finish() - err := s.ThreadStore.MarkAllAsRead(userId, timestamp) + err := s.ThreadStore.MarkAllAsRead(userId, teamId) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 2f54f15b79..2e22ad4d9e 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -8418,11 +8418,11 @@ func (s *RetryLayerThreadStore) GetMembershipForUser(userId string, postId strin } -func (s *RetryLayerThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { +func (s *RetryLayerThreadStore) GetMembershipsForUser(userId string, teamId string) ([]*model.ThreadMembership, error) { tries := 0 for { - result, err := s.ThreadStore.GetMembershipsForUser(userId) + result, err := s.ThreadStore.GetMembershipsForUser(userId, teamId) if err == nil { return result, nil } @@ -8458,11 +8458,11 @@ func (s *RetryLayerThreadStore) GetPosts(threadId string, since int64) ([]*model } -func (s *RetryLayerThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { +func (s *RetryLayerThreadStore) GetThreadsForUser(userId string, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { tries := 0 for { - result, err := s.ThreadStore.GetThreadsForUser(userId, opts) + result, err := s.ThreadStore.GetThreadsForUser(userId, teamId, opts) if err == nil { return result, nil } @@ -8478,11 +8478,11 @@ func (s *RetryLayerThreadStore) GetThreadsForUser(userId string, opts model.GetU } -func (s *RetryLayerThreadStore) MarkAllAsRead(userId string, timestamp int64) error { +func (s *RetryLayerThreadStore) MarkAllAsRead(userId string, teamId string) error { tries := 0 for { - err := s.ThreadStore.MarkAllAsRead(userId, timestamp) + err := s.ThreadStore.MarkAllAsRead(userId, teamId) if err == nil { return nil } diff --git a/store/sqlstore/thread_store.go b/store/sqlstore/thread_store.go index 76b3d14587..0ba20b16e3 100644 --- a/store/sqlstore/thread_store.go +++ b/store/sqlstore/thread_store.go @@ -108,44 +108,119 @@ func (s *SqlThreadStore) Get(id string) (*model.Thread, error) { return &thread, nil } -func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { +func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { type JoinedThread struct { - PostId string - ReplyCount int64 - LastReplyAt int64 - LastViewedAt int64 - Participants model.StringArray + PostId string + ReplyCount int64 + LastReplyAt int64 + LastViewedAt int64 + UnreadReplies int64 + UnreadMentions int64 + Participants model.StringArray model.Post } - var threads []*JoinedThread + unreadRepliesQuery := "SELECT COUNT(Posts.Id) From Posts Where Posts.RootId=ThreadMemberships.PostId AND Posts.UpdateAt >= ThreadMemberships.LastViewed AND Posts.DeleteAt=0" fetchConditions := sq.And{ + sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}}, sq.Eq{"ThreadMemberships.UserId": userId}, sq.Eq{"ThreadMemberships.Following": true}, } - if !opts.Deleted { - fetchConditions = sq.And{fetchConditions, sq.Eq{"Posts.DeleteAt": 0}} - } - if opts.Since > 0 { - fetchConditions = sq.And{fetchConditions, sq.GtOrEq{"Threads.LastReplyAt": opts.Since}} - } + pageSize := uint64(30) if opts.PageSize == 0 { pageSize = opts.PageSize } - query, args, _ := s.getQueryBuilder(). - Select("Threads.*, Posts.*, ThreadMemberships.LastViewed as LastViewedAt"). - From("Threads"). - LeftJoin("Posts ON Posts.Id = Threads.PostId"). - LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Threads.PostId"). - OrderBy("Threads.LastReplyAt DESC"). - Offset(pageSize * opts.Page). - Limit(pageSize). - Where(fetchConditions).ToSql() - _, err := s.GetReplica().Select(&threads, query, args...) - if err != nil { - return nil, errors.Wrapf(err, "failed to get threads for user id=%s", userId) + + totalUnreadRepliesChan := make(chan store.StoreResult, 1) + totalCountChan := make(chan store.StoreResult, 1) + totalUnreadMentionsChan := make(chan store.StoreResult, 1) + threadsChan := make(chan store.StoreResult, 1) + go func() { + repliesQuery, repliesQueryArgs, _ := s.getQueryBuilder(). + Select("COUNT(Posts.Id)"). + From("Posts"). + LeftJoin("ThreadMemberships ON Posts.RootId = ThreadMemberships.PostId"). + LeftJoin("Channels ON Posts.ChannelId = Channels.Id"). + Where(fetchConditions). + Where("Posts.UpdateAt >= ThreadMemberships.LastViewed").ToSql() + + totalUnreadReplies, err := s.GetMaster().SelectInt(repliesQuery, repliesQueryArgs...) + totalUnreadRepliesChan <- store.StoreResult{Data: totalUnreadReplies, NErr: errors.Wrapf(err, "failed to get count replies on threads for user id=%s", userId)} + close(totalUnreadRepliesChan) + }() + go func() { + threadsQuery, threadsQueryArgs, _ := s.getQueryBuilder(). + Select("COUNT(ThreadMemberships.PostId)"). + LeftJoin("Threads ON Threads.PostId = ThreadMemberships.PostId"). + LeftJoin("Channels ON Threads.ChannelId = Channels.Id"). + From("ThreadMemberships"). + Where(fetchConditions).ToSql() + + totalCount, err := s.GetMaster().SelectInt(threadsQuery, threadsQueryArgs...) + totalCountChan <- store.StoreResult{Data: totalCount, NErr: err} + close(totalCountChan) + }() + go func() { + mentionsQuery, mentionsQueryArgs, _ := s.getQueryBuilder(). + Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0)"). + From("ThreadMemberships"). + LeftJoin("Threads ON Threads.PostId = ThreadMemberships.PostId"). + LeftJoin("Channels ON Threads.ChannelId = Channels.Id"). + Where(fetchConditions).ToSql() + totalUnreadMentions, err := s.GetMaster().SelectInt(mentionsQuery, mentionsQueryArgs...) + totalUnreadMentionsChan <- store.StoreResult{Data: totalUnreadMentions, NErr: err} + close(totalUnreadMentionsChan) + }() + go func() { + newFetchConditions := fetchConditions + if !opts.Deleted { + newFetchConditions = sq.And{fetchConditions, sq.Eq{"Posts.DeleteAt": 0}} + } + if opts.Since > 0 { + newFetchConditions = sq.And{newFetchConditions, sq.GtOrEq{"Threads.LastReplyAt": opts.Since}} + } + + var threads []*JoinedThread + query, args, _ := s.getQueryBuilder(). + Select("Threads.*, Posts.*, ThreadMemberships.LastViewed as LastViewedAt, ThreadMemberships.UnreadMentions as UnreadMentions"). + From("Threads"). + Column(sq.Alias(sq.Expr(unreadRepliesQuery), "UnreadReplies")). + LeftJoin("Posts ON Posts.Id = Threads.PostId"). + LeftJoin("Channels ON Posts.ChannelId = Channels.Id"). + LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Threads.PostId"). + Where(newFetchConditions). + OrderBy("Threads.LastReplyAt DESC"). + Offset(pageSize * opts.Page). + Limit(pageSize).ToSql() + _, err := s.GetReplica().Select(&threads, query, args...) + threadsChan <- store.StoreResult{Data: threads, NErr: err} + close(threadsChan) + }() + + threadsResult := <-threadsChan + if threadsResult.NErr != nil { + return nil, threadsResult.NErr } + threads := threadsResult.Data.([]*JoinedThread) + + totalUnreadMentionsResult := <-totalUnreadMentionsChan + if totalUnreadMentionsResult.NErr != nil { + return nil, totalUnreadMentionsResult.NErr + } + totalUnreadMentions := totalUnreadMentionsResult.Data.(int64) + + totalCountResult := <-totalCountChan + if totalCountResult.NErr != nil { + return nil, totalCountResult.NErr + } + totalCount := totalCountResult.Data.(int64) + + totalUnreadRepliesResult := <-totalUnreadRepliesChan + if totalUnreadRepliesResult.NErr != nil { + return nil, totalUnreadRepliesResult.NErr + } + totalUnreadReplies := totalUnreadRepliesResult.Data.(int64) var userIds []string userIdMap := map[string]bool{} @@ -159,9 +234,8 @@ func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThre } var users []*model.User if opts.Extended { - query, args, _ = s.getQueryBuilder().Select("*").From("Users").Where(sq.Eq{"Id": userIds}).ToSql() - _, err = s.GetReplica().Select(&users, query, args...) - if err != nil { + query, args, _ := s.getQueryBuilder().Select("*").From("Users").Where(sq.Eq{"Id": userIds}).ToSql() + if _, err := s.GetReplica().Select(&users, query, args...); err != nil { return nil, errors.Wrapf(err, "failed to get threads for user id=%s", userId) } } else { @@ -171,8 +245,10 @@ func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThre } result := &model.Threads{ - Total: 0, - Threads: nil, + Total: totalCount, + Threads: nil, + TotalUnreadMentions: totalUnreadMentions, + TotalUnreadReplies: totalUnreadReplies, } for _, thread := range threads { @@ -191,20 +267,37 @@ func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThre participants = append(participants, participant) } result.Threads = append(result.Threads, &model.ThreadResponse{ - PostId: thread.PostId, - ReplyCount: thread.ReplyCount, - LastReplyAt: thread.LastReplyAt, - LastViewedAt: thread.LastViewedAt, - Participants: participants, - Post: &thread.Post, + PostId: thread.PostId, + ReplyCount: thread.ReplyCount, + LastReplyAt: thread.LastReplyAt, + LastViewedAt: thread.LastViewedAt, + UnreadReplies: thread.UnreadReplies, + UnreadMentions: thread.UnreadMentions, + Participants: participants, + Post: &thread.Post, }) } return result, nil } -func (s *SqlThreadStore) MarkAllAsRead(userId string, timestamp int64) error { - query, args, _ := s.getQueryBuilder().Update("ThreadMemberships").Where(sq.Eq{"UserId": userId}).Set("LastViewed", timestamp).ToSql() +func (s *SqlThreadStore) MarkAllAsRead(userId, teamId string) error { + memberships, err := s.GetMembershipsForUser(userId, teamId) + if err != nil { + return err + } + var membershipIds []string + for _, m := range memberships { + membershipIds = append(membershipIds, m.PostId) + } + timestamp := model.GetMillis() + query, args, _ := s.getQueryBuilder(). + Update("ThreadMemberships"). + Where(sq.Eq{"PostId": membershipIds}). + Where(sq.Eq{"UserId": userId}). + Set("LastViewed", timestamp). + Set("UnreadMentions", 0). + ToSql() if _, err := s.GetMaster().Exec(query, args...); err != nil { return errors.Wrapf(err, "failed to update thread read state for user id=%s", userId) } @@ -212,7 +305,11 @@ func (s *SqlThreadStore) MarkAllAsRead(userId string, timestamp int64) error { } func (s *SqlThreadStore) MarkAsRead(userId, threadId string, timestamp int64) error { - query, args, _ := s.getQueryBuilder().Update("ThreadMemberships").Where(sq.Eq{"UserId": userId}, sq.Eq{"PostId": threadId}).Set("LastViewed", timestamp).ToSql() + query, args, _ := s.getQueryBuilder(). + Update("ThreadMemberships"). + Where(sq.Eq{"UserId": userId}, sq.Eq{"PostId": threadId}). + Set("LastViewed", timestamp). + ToSql() if _, err := s.GetMaster().Exec(query, args...); err != nil { return errors.Wrapf(err, "failed to update thread read state for user id=%s thread_id=%v", userId, threadId) } @@ -244,9 +341,19 @@ func (s *SqlThreadStore) UpdateMembership(membership *model.ThreadMembership) (* return membership, nil } -func (s *SqlThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { +func (s *SqlThreadStore) GetMembershipsForUser(userId, teamId string) ([]*model.ThreadMembership, error) { var memberships []*model.ThreadMembership - _, err := s.GetReplica().Select(&memberships, "SELECT * from ThreadMemberships WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}) + + query, args, _ := s.getQueryBuilder(). + Select("ThreadMemberships.*"). + Join("Threads ON Threads.PostId = ThreadMemberships.PostId"). + Join("Channels ON Threads.ChannelId = Channels.Id"). + From("ThreadMemberships"). + Where(sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}}). + Where(sq.Eq{"ThreadMemberships.UserId": userId}). + ToSql() + + _, err := s.GetReplica().Select(&memberships, query, args...) if err != nil { return nil, errors.Wrapf(err, "failed to get thread membership with userid=%s", userId) } diff --git a/store/store.go b/store/store.go index 912936027e..b67d13c4c3 100644 --- a/store/store.go +++ b/store/store.go @@ -251,16 +251,16 @@ type ThreadStore interface { Save(thread *model.Thread) (*model.Thread, error) Update(thread *model.Thread) (*model.Thread, error) Get(id string) (*model.Thread, error) - GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) + GetThreadsForUser(userId, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) Delete(postId string) error GetPosts(threadId string, since int64) ([]*model.Post, error) - MarkAllAsRead(userId string, timestamp int64) error + MarkAllAsRead(userId, teamId string) error MarkAsRead(userId, threadId string, timestamp int64) error SaveMembership(membership *model.ThreadMembership) (*model.ThreadMembership, error) UpdateMembership(membership *model.ThreadMembership) (*model.ThreadMembership, error) - GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error) + GetMembershipsForUser(userId, teamId string) ([]*model.ThreadMembership, error) GetMembershipForUser(userId, postId string) (*model.ThreadMembership, error) DeleteMembershipForUser(userId, postId string) error CreateMembershipIfNeeded(userId, postId string, following, incrementMentions, updateFollowing bool) error diff --git a/store/storetest/mocks/ThreadStore.go b/store/storetest/mocks/ThreadStore.go index 60d6674dc4..3d0fd2592b 100644 --- a/store/storetest/mocks/ThreadStore.go +++ b/store/storetest/mocks/ThreadStore.go @@ -125,13 +125,13 @@ func (_m *ThreadStore) GetMembershipForUser(userId string, postId string) (*mode return r0, r1 } -// GetMembershipsForUser provides a mock function with given fields: userId -func (_m *ThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { - ret := _m.Called(userId) +// GetMembershipsForUser provides a mock function with given fields: userId, teamId +func (_m *ThreadStore) GetMembershipsForUser(userId string, teamId string) ([]*model.ThreadMembership, error) { + ret := _m.Called(userId, teamId) var r0 []*model.ThreadMembership - if rf, ok := ret.Get(0).(func(string) []*model.ThreadMembership); ok { - r0 = rf(userId) + if rf, ok := ret.Get(0).(func(string, string) []*model.ThreadMembership); ok { + r0 = rf(userId, teamId) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.ThreadMembership) @@ -139,8 +139,8 @@ func (_m *ThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMemb } var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(userId) + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(userId, teamId) } else { r1 = ret.Error(1) } @@ -171,13 +171,13 @@ func (_m *ThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, er return r0, r1 } -// GetThreadsForUser provides a mock function with given fields: userId, opts -func (_m *ThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { - ret := _m.Called(userId, opts) +// GetThreadsForUser provides a mock function with given fields: userId, teamId, opts +func (_m *ThreadStore) GetThreadsForUser(userId string, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { + ret := _m.Called(userId, teamId, opts) var r0 *model.Threads - if rf, ok := ret.Get(0).(func(string, model.GetUserThreadsOpts) *model.Threads); ok { - r0 = rf(userId, opts) + if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) *model.Threads); ok { + r0 = rf(userId, teamId, opts) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*model.Threads) @@ -185,8 +185,8 @@ func (_m *ThreadStore) GetThreadsForUser(userId string, opts model.GetUserThread } var r1 error - if rf, ok := ret.Get(1).(func(string, model.GetUserThreadsOpts) error); ok { - r1 = rf(userId, opts) + if rf, ok := ret.Get(1).(func(string, string, model.GetUserThreadsOpts) error); ok { + r1 = rf(userId, teamId, opts) } else { r1 = ret.Error(1) } @@ -194,13 +194,13 @@ func (_m *ThreadStore) GetThreadsForUser(userId string, opts model.GetUserThread return r0, r1 } -// MarkAllAsRead provides a mock function with given fields: userId, timestamp -func (_m *ThreadStore) MarkAllAsRead(userId string, timestamp int64) error { - ret := _m.Called(userId, timestamp) +// MarkAllAsRead provides a mock function with given fields: userId, teamId +func (_m *ThreadStore) MarkAllAsRead(userId string, teamId string) error { + ret := _m.Called(userId, teamId) var r0 error - if rf, ok := ret.Get(0).(func(string, int64) error); ok { - r0 = rf(userId, timestamp) + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(userId, teamId) } else { r0 = ret.Error(0) } diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index 4a48c6a0a1..1e95f4e441 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -7000,10 +7000,10 @@ func (s *TimerLayerThreadStore) GetMembershipForUser(userId string, postId strin return result, err } -func (s *TimerLayerThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { +func (s *TimerLayerThreadStore) GetMembershipsForUser(userId string, teamId string) ([]*model.ThreadMembership, error) { start := timemodule.Now() - result, err := s.ThreadStore.GetMembershipsForUser(userId) + result, err := s.ThreadStore.GetMembershipsForUser(userId, teamId) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -7032,10 +7032,10 @@ func (s *TimerLayerThreadStore) GetPosts(threadId string, since int64) ([]*model return result, err } -func (s *TimerLayerThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { +func (s *TimerLayerThreadStore) GetThreadsForUser(userId string, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { start := timemodule.Now() - result, err := s.ThreadStore.GetThreadsForUser(userId, opts) + result, err := s.ThreadStore.GetThreadsForUser(userId, teamId, opts) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -7048,10 +7048,10 @@ func (s *TimerLayerThreadStore) GetThreadsForUser(userId string, opts model.GetU return result, err } -func (s *TimerLayerThreadStore) MarkAllAsRead(userId string, timestamp int64) error { +func (s *TimerLayerThreadStore) MarkAllAsRead(userId string, teamId string) error { start := timemodule.Now() - err := s.ThreadStore.MarkAllAsRead(userId, timestamp) + err := s.ThreadStore.MarkAllAsRead(userId, teamId) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil {