MM-30558 - Add unreadReplies and unreadMentions to thread membership (#16304)

Этот коммит содержится в:
Eli Yukelzon
2020-12-06 10:02:53 +02:00
коммит произвёл GitHub
родитель cd9185fa23
Коммит 86e228b6c6
18 изменённых файлов: 653 добавлений и 187 удалений

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

@@ -21,8 +21,6 @@ type Routes struct {
Users *mux.Router // 'api/v4/users' Users *mux.Router // 'api/v4/users'
User *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}' 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\\_\\-\\.]+}' UserByUsername *mux.Router // 'api/v4/users/username/{username:[A-Za-z0-9\\_\\-\\.]+}'
UserByEmail *mux.Router // 'api/v4/users/email/{email:.+}' 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' 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]+}' 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]+}' 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_-]+}' 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' 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]+}' 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.Users = api.BaseRoutes.ApiRoot.PathPrefix("/users").Subrouter()
api.BaseRoutes.User = api.BaseRoutes.ApiRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}").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.UserByUsername = api.BaseRoutes.Users.PathPrefix("/username/{username:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
api.BaseRoutes.UserByEmail = api.BaseRoutes.Users.PathPrefix("/email/{email:.+}").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.TeamsForUser = api.BaseRoutes.User.PathPrefix("/teams").Subrouter()
api.BaseRoutes.Team = api.BaseRoutes.Teams.PathPrefix("/{team_id:[A-Za-z0-9]+}").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.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.TeamByName = api.BaseRoutes.Teams.PathPrefix("/name/{team_name:[A-Za-z0-9_-]+}").Subrouter()
api.BaseRoutes.TeamMembers = api.BaseRoutes.Team.PathPrefix("/members").Subrouter() api.BaseRoutes.TeamMembers = api.BaseRoutes.Team.PathPrefix("/members").Subrouter()
api.BaseRoutes.TeamMember = api.BaseRoutes.TeamMembers.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter() api.BaseRoutes.TeamMember = api.BaseRoutes.TeamMembers.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter()

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

@@ -93,7 +93,7 @@ func (api *API) InitUser() {
api.BaseRoutes.User.Handle("/uploads", api.ApiSessionRequired(getUploadsForUser)).Methods("GET") api.BaseRoutes.User.Handle("/uploads", api.ApiSessionRequired(getUploadsForUser)).Methods("GET")
api.BaseRoutes.UserThreads.Handle("", api.ApiSessionRequired(getThreadsForUser)).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(followThreadByUser)).Methods("PUT")
api.BaseRoutes.UserThread.Handle("/following", api.ApiSessionRequired(unfollowThreadByUser)).Methods("DELETE") 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) { func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId() c.RequireUserId().RequireTeamId()
if c.Err != nil { if c.Err != nil {
return return
} }
@@ -2869,7 +2869,7 @@ func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
options.Deleted, _ = strconv.ParseBool(deletedStr) options.Deleted, _ = strconv.ParseBool(deletedStr)
options.Extended, _ = strconv.ParseBool(extendedStr) 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 { if err != nil {
c.Err = err c.Err = err
return 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) { func updateReadStateThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId().RequireThreadId().RequireTimestamp() c.RequireUserId().RequireThreadId().RequireTimestamp().RequireTeamId()
if c.Err != nil { if c.Err != nil {
return return
} }
@@ -2888,13 +2888,14 @@ func updateReadStateThreadByUser(c *Context, w http.ResponseWriter, r *http.Requ
defer c.LogAuditRec(auditRec) defer c.LogAuditRec(auditRec)
auditRec.AddMeta("user_id", c.Params.UserId) auditRec.AddMeta("user_id", c.Params.UserId)
auditRec.AddMeta("thread_id", c.Params.ThreadId) auditRec.AddMeta("thread_id", c.Params.ThreadId)
auditRec.AddMeta("team_id", c.Params.TeamId)
auditRec.AddMeta("timestamp", c.Params.Timestamp) auditRec.AddMeta("timestamp", c.Params.Timestamp)
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return 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 { if err != nil {
c.Err = err c.Err = err
return 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) { func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId().RequireThreadId() c.RequireUserId().RequireThreadId().RequireTeamId()
if c.Err != nil { if c.Err != nil {
return return
} }
@@ -2915,6 +2916,7 @@ func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) {
defer c.LogAuditRec(auditRec) defer c.LogAuditRec(auditRec)
auditRec.AddMeta("user_id", c.Params.UserId) auditRec.AddMeta("user_id", c.Params.UserId)
auditRec.AddMeta("thread_id", c.Params.ThreadId) auditRec.AddMeta("thread_id", c.Params.ThreadId)
auditRec.AddMeta("team_id", c.Params.TeamId)
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) 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) { func followThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId().RequireThreadId() c.RequireUserId().RequireThreadId().RequireTeamId()
if c.Err != nil { if c.Err != nil {
return return
} }
@@ -2942,6 +2944,7 @@ func followThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) {
defer c.LogAuditRec(auditRec) defer c.LogAuditRec(auditRec)
auditRec.AddMeta("user_id", c.Params.UserId) auditRec.AddMeta("user_id", c.Params.UserId)
auditRec.AddMeta("thread_id", c.Params.ThreadId) auditRec.AddMeta("thread_id", c.Params.ThreadId)
auditRec.AddMeta("team_id", c.Params.TeamId)
if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) 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) { func updateReadStateAllThreadsByUser(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId().RequireTimestamp() c.RequireUserId().RequireTeamId()
if c.Err != nil { if c.Err != nil {
return return
} }
@@ -2967,14 +2970,14 @@ func updateReadStateAllThreadsByUser(c *Context, w http.ResponseWriter, r *http.
auditRec := c.MakeAuditRecord("updateReadStateAllThreadsByUser", audit.Fail) auditRec := c.MakeAuditRecord("updateReadStateAllThreadsByUser", audit.Fail)
defer c.LogAuditRec(auditRec) defer c.LogAuditRec(auditRec)
auditRec.AddMeta("user_id", c.Params.UserId) 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) { if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return return
} }
err := c.App.UpdateThreadsReadForUser(c.Params.UserId, c.Params.Timestamp) err := c.App.UpdateThreadsReadForUser(c.Params.UserId, c.Params.TeamId)
if err != nil { if err != nil {
c.Err = err c.Err = err
return return

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

@@ -5269,7 +5269,7 @@ func TestGetThreadsForUser(t *testing.T) {
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) 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, Page: 0,
PageSize: 30, PageSize: 30,
}) })
@@ -5289,7 +5289,7 @@ func TestGetThreadsForUser(t *testing.T) {
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) 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, Page: 0,
PageSize: 30, PageSize: 30,
}) })
@@ -5311,7 +5311,7 @@ func TestGetThreadsForUser(t *testing.T) {
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) 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, Page: 0,
PageSize: 30, PageSize: 30,
Extended: true, Extended: true,
@@ -5335,7 +5335,7 @@ func TestGetThreadsForUser(t *testing.T) {
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) 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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5350,7 +5350,7 @@ func TestGetThreadsForUser(t *testing.T) {
require.True(t, res) require.True(t, res)
require.Nil(t, resp2.Error) 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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5358,7 +5358,7 @@ func TestGetThreadsForUser(t *testing.T) {
require.Nil(t, resp.Error) require.Nil(t, resp.Error)
require.Len(t, uss.Threads, 0) 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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: true, Deleted: true,
@@ -5387,7 +5387,7 @@ func TestGetThreadsForUser(t *testing.T) {
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) 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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, 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) 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) CheckNoError(t, resp)
CheckOKStatus(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) 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) CheckNoError(t, resp)
CheckOKStatus(t, resp) CheckOKStatus(t, resp)
@@ -5509,7 +5509,7 @@ func TestFollowThreads(t *testing.T) {
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id)
var uss *model.Threads 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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5517,11 +5517,11 @@ func TestFollowThreads(t *testing.T) {
CheckNoError(t, resp) CheckNoError(t, resp)
require.Len(t, uss.Threads, 1) 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) CheckNoError(t, resp)
CheckOKStatus(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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5529,11 +5529,11 @@ func TestFollowThreads(t *testing.T) {
CheckNoError(t, resp) CheckNoError(t, resp)
require.Len(t, uss.Threads, 0) 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) CheckNoError(t, resp)
CheckOKStatus(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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, 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) { func postAndCheck(t *testing.T, client *model.Client4, post *model.Post) (*model.Post, *model.Response) {
p, resp := client.CreatePost(post) p, resp := client.CreatePost(post)
CheckNoError(t, resp) CheckNoError(t, resp)
CheckCreatedStatus(t, resp) CheckCreatedStatus(t, resp)
return p, resp return p, resp
} }
func TestMaintainUnreadMentionsInThread(t *testing.T) { func TestMaintainUnreadMentionsInThread(t *testing.T) {
th := Setup(t).InitBasic() th := Setup(t).InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -5559,24 +5622,19 @@ func TestMaintainUnreadMentionsInThread(t *testing.T) {
*cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON *cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON
}) })
checkThreadList := func(client *model.Client4, userId string, expectedMentions, expectedThreads int) (*model.Threads, *model.Response) { 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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
}) })
CheckNoError(t, resp) CheckNoError(t, resp)
require.Len(t, uss.Threads, expectedThreads) 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) sum := int64(0)
for _, membership := range memberships { for _, thr := range uss.Threads {
sum += membership.UnreadMentions sum += thr.UnreadMentions
} }
require.EqualValues(t, expectedMentions, sum) require.Equal(t, sum, uss.TotalUnreadMentions)
return uss, resp return uss, resp
} }
@@ -5628,13 +5686,13 @@ func TestReadThreads(t *testing.T) {
rpost, resp := Client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testMsg"}) rpost, resp := Client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testMsg"})
CheckNoError(t, resp) CheckNoError(t, resp)
CheckCreatedStatus(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) CheckNoError(t, resp2)
CheckCreatedStatus(t, resp2) CheckCreatedStatus(t, resp2)
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id) defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id)
var uss, uss2, uss3 *model.Threads var uss, uss2 *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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5643,11 +5701,11 @@ func TestReadThreads(t *testing.T) {
require.Len(t, uss.Threads, 1) require.Len(t, uss.Threads, 1)
time.Sleep(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) CheckNoError(t, resp)
CheckOKStatus(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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5655,19 +5713,6 @@ func TestReadThreads(t *testing.T) {
CheckNoError(t, resp) CheckNoError(t, resp)
require.Len(t, uss2.Threads, 1) require.Len(t, uss2.Threads, 1)
require.Greater(t, uss2.Threads[0].LastViewedAt, uss.Threads[0].LastViewedAt) 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) { 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) defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id)
var uss, uss2, uss3 *model.Threads 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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5698,11 +5743,11 @@ func TestReadThreads(t *testing.T) {
CheckNoError(t, resp) CheckNoError(t, resp)
require.Len(t, uss.Threads, 2) 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) CheckNoError(t, resp)
CheckOKStatus(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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,
@@ -5712,11 +5757,11 @@ func TestReadThreads(t *testing.T) {
require.Greater(t, uss2.Threads[1].LastViewedAt, uss.Threads[1].LastViewedAt) require.Greater(t, uss2.Threads[1].LastViewedAt, uss.Threads[1].LastViewedAt)
timestamp := model.GetMillis() 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) CheckNoError(t, resp)
CheckOKStatus(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, Page: 0,
PageSize: 30, PageSize: 30,
Deleted: false, Deleted: false,

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

@@ -691,8 +691,8 @@ type AppIface interface {
GetTeamsForUser(userId string) ([]*model.Team, *model.AppError) GetTeamsForUser(userId string) ([]*model.Team, *model.AppError)
GetTeamsUnreadForUser(excludeTeamId string, userId string) ([]*model.TeamUnread, *model.AppError) GetTeamsUnreadForUser(excludeTeamId string, userId string) ([]*model.TeamUnread, *model.AppError)
GetTermsOfService(id string) (*model.TermsOfService, *model.AppError) GetTermsOfService(id string) (*model.TermsOfService, *model.AppError)
GetThreadMembershipsForUser(userId string) ([]*model.ThreadMembership, error) GetThreadMembershipsForUser(userId, teamId string) ([]*model.ThreadMembership, error)
GetThreadsForUser(userId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) GetThreadsForUser(userId, teamId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError)
GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError) GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError)
GetUploadSessionsForUser(userId string) ([]*model.UploadSession, *model.AppError) GetUploadSessionsForUser(userId string) ([]*model.UploadSession, *model.AppError)
GetUser(userId string) (*model.User, *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 UpdateTeamPrivacy(teamId string, teamType string, allowOpenInvite bool) *model.AppError
UpdateTeamScheme(team *model.Team) (*model.Team, *model.AppError) UpdateTeamScheme(team *model.Team) (*model.Team, *model.AppError)
UpdateThreadFollowForUser(userId, threadId string, state bool) *model.AppError UpdateThreadFollowForUser(userId, threadId string, state bool) *model.AppError
UpdateThreadReadForUser(userId, threadId string, timestamp int64) *model.AppError UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) *model.AppError
UpdateThreadsReadForUser(userId string, timestamp int64) *model.AppError UpdateThreadsReadForUser(userId, teamId string) *model.AppError
UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError)
UpdateUserActive(userId string, active bool) *model.AppError UpdateUserActive(userId string, active bool) *model.AppError
UpdateUserAsUser(user *model.User, asAdmin bool) (*model.User, *model.AppError) UpdateUserAsUser(user *model.User, asAdmin bool) (*model.User, *model.AppError)

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

@@ -8485,7 +8485,7 @@ func (a *OpenTracingAppLayer) GetTermsOfService(id string) (*model.TermsOfServic
return resultVar0, resultVar1 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 origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetThreadMembershipsForUser") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetThreadMembershipsForUser")
@@ -8497,7 +8497,7 @@ func (a *OpenTracingAppLayer) GetThreadMembershipsForUser(userId string) ([]*mod
}() }()
defer span.Finish() defer span.Finish()
resultVar0, resultVar1 := a.app.GetThreadMembershipsForUser(userId) resultVar0, resultVar1 := a.app.GetThreadMembershipsForUser(userId, teamId)
if resultVar1 != nil { if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1)) span.LogFields(spanlog.Error(resultVar1))
@@ -8507,7 +8507,7 @@ func (a *OpenTracingAppLayer) GetThreadMembershipsForUser(userId string) ([]*mod
return resultVar0, resultVar1 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 origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetThreadsForUser") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetThreadsForUser")
@@ -8519,7 +8519,7 @@ func (a *OpenTracingAppLayer) GetThreadsForUser(userId string, options model.Get
}() }()
defer span.Finish() defer span.Finish()
resultVar0, resultVar1 := a.app.GetThreadsForUser(userId, options) resultVar0, resultVar1 := a.app.GetThreadsForUser(userId, teamId, options)
if resultVar1 != nil { if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1)) span.LogFields(spanlog.Error(resultVar1))
@@ -15319,7 +15319,7 @@ func (a *OpenTracingAppLayer) UpdateThreadFollowForUser(userId string, threadId
return resultVar0 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 origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadReadForUser") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadReadForUser")
@@ -15331,7 +15331,7 @@ func (a *OpenTracingAppLayer) UpdateThreadReadForUser(userId string, threadId st
}() }()
defer span.Finish() defer span.Finish()
resultVar0 := a.app.UpdateThreadReadForUser(userId, threadId, timestamp) resultVar0 := a.app.UpdateThreadReadForUser(userId, teamId, threadId, timestamp)
if resultVar0 != nil { if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0)) span.LogFields(spanlog.Error(resultVar0))
@@ -15341,7 +15341,7 @@ func (a *OpenTracingAppLayer) UpdateThreadReadForUser(userId string, threadId st
return resultVar0 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 origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadsReadForUser") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadsReadForUser")
@@ -15353,7 +15353,7 @@ func (a *OpenTracingAppLayer) UpdateThreadsReadForUser(userId string, timestamp
}() }()
defer span.Finish() defer span.Finish()
resultVar0 := a.app.UpdateThreadsReadForUser(userId, timestamp) resultVar0 := a.app.UpdateThreadsReadForUser(userId, teamId)
if resultVar0 != nil { if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0)) span.LogFields(spanlog.Error(resultVar0))

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

@@ -1538,6 +1538,6 @@ func isPostMention(user *model.User, post *model.Post, keywords map[string][]str
return false return false
} }
func (a *App) GetThreadMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { func (a *App) GetThreadMembershipsForUser(userId, teamId string) ([]*model.ThreadMembership, error) {
return a.Srv().Store.Thread().GetMembershipsForUser(userId) return a.Srv().Store.Thread().GetMembershipsForUser(userId, teamId)
} }

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

@@ -1892,11 +1892,11 @@ func TestThreadMembership(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
// first user should now be part of the thread since they replied to a post // 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.Nil(t, err2)
require.Len(t, memberships, 1) require.Len(t, memberships, 1)
// second user should also be part of a thread since they were mentioned // 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.Nil(t, err2)
require.Len(t, memberships, 1) require.Len(t, memberships, 1)
@@ -1916,7 +1916,7 @@ func TestThreadMembership(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
// first user should now be part of two threads // 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.Nil(t, err2)
require.Len(t, memberships, 2) require.Len(t, memberships, 2)
}) })

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

@@ -2371,8 +2371,8 @@ func (a *App) ConvertBotToUser(bot *model.Bot, userPatch *model.UserPatch, sysad
return user, nil return user, nil
} }
func (a *App) GetThreadsForUser(userId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) { func (a *App) GetThreadsForUser(userId, teamId string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) {
threads, err := a.Srv().Store.Thread().GetThreadsForUser(userId, options) threads, err := a.Srv().Store.Thread().GetThreadsForUser(userId, teamId, options)
if err != nil { if err != nil {
return nil, model.NewAppError("GetThreadsForUser", "app.user.get_threads_for_user.app_error", nil, err.Error(), http.StatusInternalServerError) 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 return threads, nil
} }
func (a *App) UpdateThreadsReadForUser(userId string, timestamp int64) *model.AppError { func (a *App) UpdateThreadsReadForUser(userId, teamId string) *model.AppError {
nErr := a.Srv().Store.Thread().MarkAllAsRead(userId, timestamp) nErr := a.Srv().Store.Thread().MarkAllAsRead(userId, teamId)
if nErr != nil { if nErr != nil {
return model.NewAppError("UpdateThreadsReadForUser", "app.user.update_threads_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError) 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 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_THREAD_READ_CHANGED, "", "", userId, nil)
message.Add("timestamp", timestamp)
a.Publish(message) a.Publish(message)
return nil return nil
} }
@@ -2406,8 +2405,31 @@ func (a *App) UpdateThreadFollowForUser(userId, threadId string, state bool) *mo
return nil return nil
} }
func (a *App) UpdateThreadReadForUser(userId, threadId string, timestamp int64) *model.AppError { func (a *App) UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) *model.AppError {
nErr := a.Srv().Store.Thread().MarkAsRead(userId, threadId, timestamp) 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 { if nErr != nil {
return model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError) return model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError)
} }

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

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

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

@@ -190,12 +190,12 @@ func (c *Client4) GetUserRoute(userId string) string {
return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId) return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId)
} }
func (c *Client4) GetUserThreadsRoute(userId string) string { func (c *Client4) GetUserThreadsRoute(userID, teamID string) string {
return fmt.Sprintf(c.GetUsersRoute()+"/%v/threads", userId) return c.GetUserRoute(userID) + c.GetTeamRoute(teamID) + "/threads"
} }
func (c *Client4) GetUserThreadRoute(userId, threadId string) string { func (c *Client4) GetUserThreadRoute(userId, teamId, threadId string) string {
return fmt.Sprintf(c.GetUserThreadsRoute(userId)+"/%v", threadId) return c.GetUserThreadsRoute(userId, teamId) + "/" + threadId
} }
func (c *Client4) GetUserCategoryRoute(userID, teamID string) string { func (c *Client4) GetUserCategoryRoute(userID, teamID string) string {
@@ -5767,7 +5767,7 @@ func (c *Client4) ListImports() ([]string, *Response) {
return ArrayFromJson(r.Body), BuildResponse(r) 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{} v := url.Values{}
if options.Since != 0 { if options.Since != 0 {
v.Set("since", fmt.Sprintf("%d", options.Since)) 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") v.Set("deleted", "true")
} }
url := c.GetUserThreadsRoute(userId) url := c.GetUserThreadsRoute(userId, teamId)
if len(v) > 0 { if len(v) > 0 {
url += "?" + v.Encode() url += "?" + v.Encode()
} }
@@ -5802,8 +5802,8 @@ func (c *Client4) GetUserThreads(userId string, options GetUserThreadsOpts) (*Th
return &threads, BuildResponse(r) return &threads, BuildResponse(r)
} }
func (c *Client4) UpdateThreadsReadForUser(userId string, timestamp int64) *Response { func (c *Client4) UpdateThreadsReadForUser(userId, teamId string) *Response {
r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadsRoute(userId), timestamp), "") r, appErr := c.DoApiPut(fmt.Sprintf("%s/read", c.GetUserThreadsRoute(userId, teamId)), "")
if appErr != nil { if appErr != nil {
return BuildErrorResponse(r, appErr) return BuildErrorResponse(r, appErr)
} }
@@ -5812,8 +5812,8 @@ func (c *Client4) UpdateThreadsReadForUser(userId string, timestamp int64) *Resp
return BuildResponse(r) return BuildResponse(r)
} }
func (c *Client4) UpdateThreadReadForUser(userId, threadId string, timestamp int64) *Response { func (c *Client4) UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) *Response {
r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadRoute(userId, threadId), timestamp), "") r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadRoute(userId, teamId, threadId), timestamp), "")
if appErr != nil { if appErr != nil {
return BuildErrorResponse(r, appErr) return BuildErrorResponse(r, appErr)
} }
@@ -5822,13 +5822,13 @@ func (c *Client4) UpdateThreadReadForUser(userId, threadId string, timestamp int
return BuildResponse(r) 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 appErr *AppError
var r *http.Response var r *http.Response
if state { if state {
r, appErr = c.DoApiPut(c.GetUserThreadRoute(userId, threadId)+"/following", "") r, appErr = c.DoApiPut(c.GetUserThreadRoute(userId, teamId, threadId)+"/following", "")
} else { } else {
r, appErr = c.DoApiDelete(c.GetUserThreadRoute(userId, threadId) + "/following") r, appErr = c.DoApiDelete(c.GetUserThreadRoute(userId, teamId, threadId) + "/following")
} }
if appErr != nil { if appErr != nil {
return BuildErrorResponse(r, appErr) return BuildErrorResponse(r, appErr)

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

@@ -16,17 +16,21 @@ type Thread struct {
} }
type ThreadResponse struct { type ThreadResponse struct {
PostId string `json:"id"` PostId string `json:"id"`
ReplyCount int64 `json:"reply_count"` ReplyCount int64 `json:"reply_count"`
LastReplyAt int64 `json:"last_reply_at"` LastReplyAt int64 `json:"last_reply_at"`
LastViewedAt int64 `json:"last_viewed_at"` LastViewedAt int64 `json:"last_viewed_at"`
Participants []*User `json:"participants"` Participants []*User `json:"participants"`
Post *Post `json:"post"` Post *Post `json:"post"`
UnreadReplies int64 `json:"unread_replies"`
UnreadMentions int64 `json:"unread_mentions"`
} }
type Threads struct { type Threads struct {
Total int64 `json:"total"` Total int64 `json:"total"`
Threads []*ThreadResponse `json:"threads"` TotalUnreadReplies int64 `json:"total_unread_replies"`
TotalUnreadMentions int64 `json:"total_unread_mentions"`
Threads []*ThreadResponse `json:"threads"`
} }
type GetUserThreadsOpts struct { type GetUserThreadsOpts struct {

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

@@ -7756,7 +7756,7 @@ func (s *OpenTracingLayerThreadStore) GetMembershipForUser(userId string, postId
return result, err 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() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetMembershipsForUser") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetMembershipsForUser")
s.Root.Store.SetContext(newCtx) s.Root.Store.SetContext(newCtx)
@@ -7765,7 +7765,7 @@ func (s *OpenTracingLayerThreadStore) GetMembershipsForUser(userId string) ([]*m
}() }()
defer span.Finish() defer span.Finish()
result, err := s.ThreadStore.GetMembershipsForUser(userId) result, err := s.ThreadStore.GetMembershipsForUser(userId, teamId)
if err != nil { if err != nil {
span.LogFields(spanlog.Error(err)) span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true) ext.Error.Set(span, true)
@@ -7792,7 +7792,7 @@ func (s *OpenTracingLayerThreadStore) GetPosts(threadId string, since int64) ([]
return result, err 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() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadsForUser") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadsForUser")
s.Root.Store.SetContext(newCtx) s.Root.Store.SetContext(newCtx)
@@ -7801,7 +7801,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, opts mode
}() }()
defer span.Finish() defer span.Finish()
result, err := s.ThreadStore.GetThreadsForUser(userId, opts) result, err := s.ThreadStore.GetThreadsForUser(userId, teamId, opts)
if err != nil { if err != nil {
span.LogFields(spanlog.Error(err)) span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true) ext.Error.Set(span, true)
@@ -7810,7 +7810,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, opts mode
return result, err 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() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.MarkAllAsRead") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.MarkAllAsRead")
s.Root.Store.SetContext(newCtx) s.Root.Store.SetContext(newCtx)
@@ -7819,7 +7819,7 @@ func (s *OpenTracingLayerThreadStore) MarkAllAsRead(userId string, timestamp int
}() }()
defer span.Finish() defer span.Finish()
err := s.ThreadStore.MarkAllAsRead(userId, timestamp) err := s.ThreadStore.MarkAllAsRead(userId, teamId)
if err != nil { if err != nil {
span.LogFields(spanlog.Error(err)) span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true) ext.Error.Set(span, true)

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

@@ -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 tries := 0
for { for {
result, err := s.ThreadStore.GetMembershipsForUser(userId) result, err := s.ThreadStore.GetMembershipsForUser(userId, teamId)
if err == nil { if err == nil {
return result, 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 tries := 0
for { for {
result, err := s.ThreadStore.GetThreadsForUser(userId, opts) result, err := s.ThreadStore.GetThreadsForUser(userId, teamId, opts)
if err == nil { if err == nil {
return result, 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 tries := 0
for { for {
err := s.ThreadStore.MarkAllAsRead(userId, timestamp) err := s.ThreadStore.MarkAllAsRead(userId, teamId)
if err == nil { if err == nil {
return nil return nil
} }

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

@@ -108,44 +108,119 @@ func (s *SqlThreadStore) Get(id string) (*model.Thread, error) {
return &thread, nil 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 { type JoinedThread struct {
PostId string PostId string
ReplyCount int64 ReplyCount int64
LastReplyAt int64 LastReplyAt int64
LastViewedAt int64 LastViewedAt int64
Participants model.StringArray UnreadReplies int64
UnreadMentions int64
Participants model.StringArray
model.Post 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{ fetchConditions := sq.And{
sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}},
sq.Eq{"ThreadMemberships.UserId": userId}, sq.Eq{"ThreadMemberships.UserId": userId},
sq.Eq{"ThreadMemberships.Following": true}, 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) pageSize := uint64(30)
if opts.PageSize == 0 { if opts.PageSize == 0 {
pageSize = opts.PageSize pageSize = opts.PageSize
} }
query, args, _ := s.getQueryBuilder().
Select("Threads.*, Posts.*, ThreadMemberships.LastViewed as LastViewedAt"). totalUnreadRepliesChan := make(chan store.StoreResult, 1)
From("Threads"). totalCountChan := make(chan store.StoreResult, 1)
LeftJoin("Posts ON Posts.Id = Threads.PostId"). totalUnreadMentionsChan := make(chan store.StoreResult, 1)
LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Threads.PostId"). threadsChan := make(chan store.StoreResult, 1)
OrderBy("Threads.LastReplyAt DESC"). go func() {
Offset(pageSize * opts.Page). repliesQuery, repliesQueryArgs, _ := s.getQueryBuilder().
Limit(pageSize). Select("COUNT(Posts.Id)").
Where(fetchConditions).ToSql() From("Posts").
_, err := s.GetReplica().Select(&threads, query, args...) LeftJoin("ThreadMemberships ON Posts.RootId = ThreadMemberships.PostId").
if err != nil { LeftJoin("Channels ON Posts.ChannelId = Channels.Id").
return nil, errors.Wrapf(err, "failed to get threads for user id=%s", userId) 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 var userIds []string
userIdMap := map[string]bool{} userIdMap := map[string]bool{}
@@ -159,9 +234,8 @@ func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThre
} }
var users []*model.User var users []*model.User
if opts.Extended { if opts.Extended {
query, args, _ = s.getQueryBuilder().Select("*").From("Users").Where(sq.Eq{"Id": userIds}).ToSql() query, args, _ := s.getQueryBuilder().Select("*").From("Users").Where(sq.Eq{"Id": userIds}).ToSql()
_, err = s.GetReplica().Select(&users, query, args...) if _, err := s.GetReplica().Select(&users, query, args...); err != nil {
if err != nil {
return nil, errors.Wrapf(err, "failed to get threads for user id=%s", userId) return nil, errors.Wrapf(err, "failed to get threads for user id=%s", userId)
} }
} else { } else {
@@ -171,8 +245,10 @@ func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThre
} }
result := &model.Threads{ result := &model.Threads{
Total: 0, Total: totalCount,
Threads: nil, Threads: nil,
TotalUnreadMentions: totalUnreadMentions,
TotalUnreadReplies: totalUnreadReplies,
} }
for _, thread := range threads { for _, thread := range threads {
@@ -191,20 +267,37 @@ func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThre
participants = append(participants, participant) participants = append(participants, participant)
} }
result.Threads = append(result.Threads, &model.ThreadResponse{ result.Threads = append(result.Threads, &model.ThreadResponse{
PostId: thread.PostId, PostId: thread.PostId,
ReplyCount: thread.ReplyCount, ReplyCount: thread.ReplyCount,
LastReplyAt: thread.LastReplyAt, LastReplyAt: thread.LastReplyAt,
LastViewedAt: thread.LastViewedAt, LastViewedAt: thread.LastViewedAt,
Participants: participants, UnreadReplies: thread.UnreadReplies,
Post: &thread.Post, UnreadMentions: thread.UnreadMentions,
Participants: participants,
Post: &thread.Post,
}) })
} }
return result, nil return result, nil
} }
func (s *SqlThreadStore) MarkAllAsRead(userId string, timestamp int64) error { func (s *SqlThreadStore) MarkAllAsRead(userId, teamId string) error {
query, args, _ := s.getQueryBuilder().Update("ThreadMemberships").Where(sq.Eq{"UserId": userId}).Set("LastViewed", timestamp).ToSql() 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 { if _, err := s.GetMaster().Exec(query, args...); err != nil {
return errors.Wrapf(err, "failed to update thread read state for user id=%s", userId) 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 { 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 { 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) 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 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 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 { if err != nil {
return nil, errors.Wrapf(err, "failed to get thread membership with userid=%s", userId) return nil, errors.Wrapf(err, "failed to get thread membership with userid=%s", userId)
} }

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

@@ -251,16 +251,16 @@ type ThreadStore interface {
Save(thread *model.Thread) (*model.Thread, error) Save(thread *model.Thread) (*model.Thread, error)
Update(thread *model.Thread) (*model.Thread, error) Update(thread *model.Thread) (*model.Thread, error)
Get(id string) (*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 Delete(postId string) error
GetPosts(threadId string, since int64) ([]*model.Post, 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 MarkAsRead(userId, threadId string, timestamp int64) error
SaveMembership(membership *model.ThreadMembership) (*model.ThreadMembership, error) SaveMembership(membership *model.ThreadMembership) (*model.ThreadMembership, error)
UpdateMembership(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) GetMembershipForUser(userId, postId string) (*model.ThreadMembership, error)
DeleteMembershipForUser(userId, postId string) error DeleteMembershipForUser(userId, postId string) error
CreateMembershipIfNeeded(userId, postId string, following, incrementMentions, updateFollowing bool) error CreateMembershipIfNeeded(userId, postId string, following, incrementMentions, updateFollowing bool) error

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

@@ -125,13 +125,13 @@ func (_m *ThreadStore) GetMembershipForUser(userId string, postId string) (*mode
return r0, r1 return r0, r1
} }
// GetMembershipsForUser provides a mock function with given fields: userId // GetMembershipsForUser provides a mock function with given fields: userId, teamId
func (_m *ThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error) { func (_m *ThreadStore) GetMembershipsForUser(userId string, teamId string) ([]*model.ThreadMembership, error) {
ret := _m.Called(userId) ret := _m.Called(userId, teamId)
var r0 []*model.ThreadMembership var r0 []*model.ThreadMembership
if rf, ok := ret.Get(0).(func(string) []*model.ThreadMembership); ok { if rf, ok := ret.Get(0).(func(string, string) []*model.ThreadMembership); ok {
r0 = rf(userId) r0 = rf(userId, teamId)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.ThreadMembership) r0 = ret.Get(0).([]*model.ThreadMembership)
@@ -139,8 +139,8 @@ func (_m *ThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMemb
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok { if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(userId) r1 = rf(userId, teamId)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }
@@ -171,13 +171,13 @@ func (_m *ThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, er
return r0, r1 return r0, r1
} }
// GetThreadsForUser provides a mock function with given fields: userId, opts // GetThreadsForUser provides a mock function with given fields: userId, teamId, opts
func (_m *ThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { func (_m *ThreadStore) GetThreadsForUser(userId string, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) {
ret := _m.Called(userId, opts) ret := _m.Called(userId, teamId, opts)
var r0 *model.Threads var r0 *model.Threads
if rf, ok := ret.Get(0).(func(string, model.GetUserThreadsOpts) *model.Threads); ok { if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) *model.Threads); ok {
r0 = rf(userId, opts) r0 = rf(userId, teamId, opts)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Threads) r0 = ret.Get(0).(*model.Threads)
@@ -185,8 +185,8 @@ func (_m *ThreadStore) GetThreadsForUser(userId string, opts model.GetUserThread
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(string, model.GetUserThreadsOpts) error); ok { if rf, ok := ret.Get(1).(func(string, string, model.GetUserThreadsOpts) error); ok {
r1 = rf(userId, opts) r1 = rf(userId, teamId, opts)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }
@@ -194,13 +194,13 @@ func (_m *ThreadStore) GetThreadsForUser(userId string, opts model.GetUserThread
return r0, r1 return r0, r1
} }
// MarkAllAsRead provides a mock function with given fields: userId, timestamp // MarkAllAsRead provides a mock function with given fields: userId, teamId
func (_m *ThreadStore) MarkAllAsRead(userId string, timestamp int64) error { func (_m *ThreadStore) MarkAllAsRead(userId string, teamId string) error {
ret := _m.Called(userId, timestamp) ret := _m.Called(userId, teamId)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok { if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(userId, timestamp) r0 = rf(userId, teamId)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
} }

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

@@ -7000,10 +7000,10 @@ func (s *TimerLayerThreadStore) GetMembershipForUser(userId string, postId strin
return result, err 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() start := timemodule.Now()
result, err := s.ThreadStore.GetMembershipsForUser(userId) result, err := s.ThreadStore.GetMembershipsForUser(userId, teamId)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil { if s.Root.Metrics != nil {
@@ -7032,10 +7032,10 @@ func (s *TimerLayerThreadStore) GetPosts(threadId string, since int64) ([]*model
return result, err 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() 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) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil { if s.Root.Metrics != nil {
@@ -7048,10 +7048,10 @@ func (s *TimerLayerThreadStore) GetThreadsForUser(userId string, opts model.GetU
return result, err return result, err
} }
func (s *TimerLayerThreadStore) MarkAllAsRead(userId string, timestamp int64) error { func (s *TimerLayerThreadStore) MarkAllAsRead(userId string, teamId string) error {
start := timemodule.Now() start := timemodule.Now()
err := s.ThreadStore.MarkAllAsRead(userId, timestamp) err := s.ThreadStore.MarkAllAsRead(userId, teamId)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil { if s.Root.Metrics != nil {