github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/store/sqlstore/thread_store.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "context" 8 "database/sql" 9 "time" 10 11 sq "github.com/Masterminds/squirrel" 12 "github.com/pkg/errors" 13 14 "github.com/mattermost/mattermost-server/v5/model" 15 "github.com/mattermost/mattermost-server/v5/store" 16 "github.com/mattermost/mattermost-server/v5/utils" 17 ) 18 19 type SqlThreadStore struct { 20 *SqlStore 21 } 22 23 func (s *SqlThreadStore) ClearCaches() { 24 } 25 26 func newSqlThreadStore(sqlStore *SqlStore) store.ThreadStore { 27 s := &SqlThreadStore{ 28 SqlStore: sqlStore, 29 } 30 31 for _, db := range sqlStore.GetAllConns() { 32 tableThreads := db.AddTableWithName(model.Thread{}, "Threads").SetKeys(false, "PostId") 33 tableThreads.ColMap("PostId").SetMaxSize(26) 34 tableThreads.ColMap("ChannelId").SetMaxSize(26) 35 tableThreads.ColMap("Participants").SetMaxSize(0) 36 tableThreadMemberships := db.AddTableWithName(model.ThreadMembership{}, "ThreadMemberships").SetKeys(false, "PostId", "UserId") 37 tableThreadMemberships.ColMap("PostId").SetMaxSize(26) 38 tableThreadMemberships.ColMap("UserId").SetMaxSize(26) 39 } 40 41 return s 42 } 43 44 func threadSliceColumns() []string { 45 return []string{"PostId", "ChannelId", "LastReplyAt", "ReplyCount", "Participants"} 46 } 47 48 func threadToSlice(thread *model.Thread) []interface{} { 49 return []interface{}{ 50 thread.PostId, 51 thread.ChannelId, 52 thread.LastReplyAt, 53 thread.ReplyCount, 54 thread.Participants, 55 } 56 } 57 58 func (s *SqlThreadStore) createIndexesIfNotExists() { 59 s.CreateIndexIfNotExists("idx_thread_memberships_last_update_at", "ThreadMemberships", "LastUpdated") 60 s.CreateIndexIfNotExists("idx_thread_memberships_last_view_at", "ThreadMemberships", "LastViewed") 61 s.CreateIndexIfNotExists("idx_thread_memberships_user_id", "ThreadMemberships", "UserId") 62 s.CreateIndexIfNotExists("idx_threads_channel_id", "Threads", "ChannelId") 63 } 64 65 func (s *SqlThreadStore) SaveMultiple(threads []*model.Thread) ([]*model.Thread, int, error) { 66 builder := s.getQueryBuilder().Insert("Threads").Columns(threadSliceColumns()...) 67 for _, thread := range threads { 68 builder = builder.Values(threadToSlice(thread)...) 69 } 70 query, args, err := builder.ToSql() 71 if err != nil { 72 return nil, -1, errors.Wrap(err, "thread_tosql") 73 } 74 75 if _, err := s.GetMaster().Exec(query, args...); err != nil { 76 return nil, -1, errors.Wrap(err, "failed to save Post") 77 } 78 79 return threads, -1, nil 80 } 81 82 func (s *SqlThreadStore) Save(thread *model.Thread) (*model.Thread, error) { 83 threads, _, err := s.SaveMultiple([]*model.Thread{thread}) 84 if err != nil { 85 return nil, err 86 } 87 return threads[0], nil 88 } 89 90 func (s *SqlThreadStore) Update(thread *model.Thread) (*model.Thread, error) { 91 if _, err := s.GetMaster().Update(thread); err != nil { 92 return nil, errors.Wrapf(err, "failed to update thread with id=%s", thread.PostId) 93 } 94 95 return thread, nil 96 } 97 98 func (s *SqlThreadStore) Get(id string) (*model.Thread, error) { 99 var thread model.Thread 100 query, args, _ := s.getQueryBuilder().Select("*").From("Threads").Where(sq.Eq{"PostId": id}).ToSql() 101 err := s.GetReplica().SelectOne(&thread, query, args...) 102 if err != nil { 103 if err == sql.ErrNoRows { 104 return nil, store.NewErrNotFound("Thread", id) 105 } 106 107 return nil, errors.Wrapf(err, "failed to get thread with id=%s", id) 108 } 109 return &thread, nil 110 } 111 112 func (s *SqlThreadStore) GetThreadMentionsForUserPerChannel(userId, teamId string) (map[string]int64, error) { 113 type Count struct { 114 UnreadMentions int64 115 ChannelId string 116 } 117 var counts []Count 118 119 sql, args, _ := s.getQueryBuilder(). 120 Select("SUM(UnreadMentions) as UnreadMentions", "ChannelId"). 121 From("ThreadMemberships"). 122 LeftJoin("Threads ON Threads.PostId = ThreadMemberships.PostId"). 123 LeftJoin("Channels ON Threads.ChannelId = Channels.Id"). 124 Where(sq.And{ 125 sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}}, 126 sq.Eq{"ThreadMemberships.UserId": userId}, 127 sq.Eq{"ThreadMemberships.Following": true}, 128 }). 129 GroupBy("Threads.ChannelId").ToSql() 130 131 if _, err := s.GetMaster().Select(&counts, sql, args...); err != nil { 132 return nil, err 133 } 134 result := map[string]int64{} 135 for _, count := range counts { 136 result[count.ChannelId] = count.UnreadMentions 137 } 138 return result, nil 139 } 140 141 func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.GetUserThreadsOpts) (*model.Threads, error) { 142 type JoinedThread struct { 143 PostId string 144 ReplyCount int64 145 LastReplyAt int64 146 LastViewedAt int64 147 UnreadReplies int64 148 UnreadMentions int64 149 Participants model.StringArray 150 model.Post 151 } 152 153 unreadRepliesQuery := "SELECT COUNT(Posts.Id) From Posts Where Posts.RootId=ThreadMemberships.PostId AND Posts.UpdateAt >= ThreadMemberships.LastViewed" 154 fetchConditions := sq.And{ 155 sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}}, 156 sq.Eq{"ThreadMemberships.UserId": userId}, 157 sq.Eq{"ThreadMemberships.Following": true}, 158 } 159 if !opts.Deleted { 160 fetchConditions = sq.And{fetchConditions, sq.Eq{"Posts.DeleteAt": 0}} 161 } 162 163 pageSize := uint64(30) 164 if opts.PageSize != 0 { 165 pageSize = opts.PageSize 166 } 167 168 totalUnreadThreadsChan := make(chan store.StoreResult, 1) 169 totalCountChan := make(chan store.StoreResult, 1) 170 totalUnreadMentionsChan := make(chan store.StoreResult, 1) 171 threadsChan := make(chan store.StoreResult, 1) 172 go func() { 173 repliesQuery, repliesQueryArgs, _ := s.getQueryBuilder(). 174 Select("COUNT(Posts.Id)"). 175 From("Posts"). 176 LeftJoin("ThreadMemberships ON Posts.Id = ThreadMemberships.PostId"). 177 LeftJoin("Channels ON Posts.ChannelId = Channels.Id"). 178 Where(fetchConditions). 179 Where("Posts.UpdateAt >= ThreadMemberships.LastViewed").ToSql() 180 181 totalUnreadThreads, err := s.GetMaster().SelectInt(repliesQuery, repliesQueryArgs...) 182 totalUnreadThreadsChan <- store.StoreResult{Data: totalUnreadThreads, NErr: errors.Wrapf(err, "failed to get count unread on threads for user id=%s", userId)} 183 close(totalUnreadThreadsChan) 184 }() 185 go func() { 186 newFetchConditions := fetchConditions 187 188 if opts.Unread { 189 newFetchConditions = sq.And{newFetchConditions, sq.Expr("ThreadMemberships.LastViewed < Threads.LastReplyAt")} 190 } 191 192 threadsQuery, threadsQueryArgs, _ := s.getQueryBuilder(). 193 Select("COUNT(ThreadMemberships.PostId)"). 194 LeftJoin("Threads ON Threads.PostId = ThreadMemberships.PostId"). 195 LeftJoin("Channels ON Threads.ChannelId = Channels.Id"). 196 LeftJoin("Posts ON Posts.Id = ThreadMemberships.PostId"). 197 From("ThreadMemberships"). 198 Where(newFetchConditions).ToSql() 199 200 totalCount, err := s.GetMaster().SelectInt(threadsQuery, threadsQueryArgs...) 201 totalCountChan <- store.StoreResult{Data: totalCount, NErr: err} 202 close(totalCountChan) 203 }() 204 go func() { 205 mentionsQuery, mentionsQueryArgs, _ := s.getQueryBuilder(). 206 Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0)"). 207 From("ThreadMemberships"). 208 LeftJoin("Threads ON Threads.PostId = ThreadMemberships.PostId"). 209 LeftJoin("Posts ON Posts.Id = ThreadMemberships.PostId"). 210 LeftJoin("Channels ON Threads.ChannelId = Channels.Id"). 211 Where(fetchConditions).ToSql() 212 totalUnreadMentions, err := s.GetMaster().SelectInt(mentionsQuery, mentionsQueryArgs...) 213 totalUnreadMentionsChan <- store.StoreResult{Data: totalUnreadMentions, NErr: err} 214 close(totalUnreadMentionsChan) 215 }() 216 go func() { 217 newFetchConditions := fetchConditions 218 if opts.Since > 0 { 219 newFetchConditions = sq.And{newFetchConditions, sq.GtOrEq{"ThreadMemberships.LastUpdated": opts.Since}} 220 } 221 order := "DESC" 222 if opts.Before != "" { 223 newFetchConditions = sq.And{ 224 newFetchConditions, 225 sq.Expr(`LastReplyAt < (SELECT LastReplyAt FROM Threads WHERE PostId = ?)`, opts.Before), 226 } 227 } 228 if opts.After != "" { 229 order = "ASC" 230 newFetchConditions = sq.And{ 231 newFetchConditions, 232 sq.Expr(`LastReplyAt > (SELECT LastReplyAt FROM Threads WHERE PostId = ?)`, opts.After), 233 } 234 } 235 if opts.Unread { 236 newFetchConditions = sq.And{newFetchConditions, sq.Expr("ThreadMemberships.LastViewed < Threads.LastReplyAt")} 237 } 238 var threads []*JoinedThread 239 query, args, _ := s.getQueryBuilder(). 240 Select("Threads.*, Posts.*, ThreadMemberships.LastViewed as LastViewedAt, ThreadMemberships.UnreadMentions as UnreadMentions"). 241 From("Threads"). 242 Column(sq.Alias(sq.Expr(unreadRepliesQuery), "UnreadReplies")). 243 LeftJoin("Posts ON Posts.Id = Threads.PostId"). 244 LeftJoin("Channels ON Posts.ChannelId = Channels.Id"). 245 LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Threads.PostId"). 246 Where(newFetchConditions). 247 OrderBy("Threads.LastReplyAt " + order). 248 Limit(pageSize).ToSql() 249 250 _, err := s.GetReplica().Select(&threads, query, args...) 251 threadsChan <- store.StoreResult{Data: threads, NErr: err} 252 close(threadsChan) 253 }() 254 255 threadsResult := <-threadsChan 256 if threadsResult.NErr != nil { 257 return nil, threadsResult.NErr 258 } 259 threads := threadsResult.Data.([]*JoinedThread) 260 261 totalUnreadMentionsResult := <-totalUnreadMentionsChan 262 if totalUnreadMentionsResult.NErr != nil { 263 return nil, totalUnreadMentionsResult.NErr 264 } 265 totalUnreadMentions := totalUnreadMentionsResult.Data.(int64) 266 267 totalCountResult := <-totalCountChan 268 if totalCountResult.NErr != nil { 269 return nil, totalCountResult.NErr 270 } 271 totalCount := totalCountResult.Data.(int64) 272 273 totalUnreadThreadsResult := <-totalUnreadThreadsChan 274 if totalUnreadThreadsResult.NErr != nil { 275 return nil, totalUnreadThreadsResult.NErr 276 } 277 totalUnreadThreads := totalUnreadThreadsResult.Data.(int64) 278 279 var userIds []string 280 userIdMap := map[string]bool{} 281 for _, thread := range threads { 282 for _, participantId := range thread.Participants { 283 if _, ok := userIdMap[participantId]; !ok { 284 userIdMap[participantId] = true 285 userIds = append(userIds, participantId) 286 } 287 } 288 } 289 var users []*model.User 290 if opts.Extended { 291 var err error 292 users, err = s.User().GetProfileByIds(context.Background(), userIds, &store.UserGetByIdsOpts{}, true) 293 if err != nil { 294 return nil, errors.Wrapf(err, "failed to get threads for user id=%s", userId) 295 } 296 } else { 297 for _, userId := range userIds { 298 users = append(users, &model.User{Id: userId}) 299 } 300 } 301 302 result := &model.Threads{ 303 Total: totalCount, 304 Threads: []*model.ThreadResponse{}, 305 TotalUnreadMentions: totalUnreadMentions, 306 TotalUnreadThreads: totalUnreadThreads, 307 } 308 309 for _, thread := range threads { 310 var participants []*model.User 311 for _, participantId := range thread.Participants { 312 var participant *model.User 313 for _, u := range users { 314 if u.Id == participantId { 315 participant = u 316 break 317 } 318 } 319 if participant == nil { 320 return nil, errors.New("cannot find thread participant with id=" + participantId) 321 } 322 participants = append(participants, participant) 323 } 324 result.Threads = append(result.Threads, &model.ThreadResponse{ 325 PostId: thread.PostId, 326 ReplyCount: thread.ReplyCount, 327 LastReplyAt: thread.LastReplyAt, 328 LastViewedAt: thread.LastViewedAt, 329 UnreadReplies: thread.UnreadReplies, 330 UnreadMentions: thread.UnreadMentions, 331 Participants: participants, 332 Post: &thread.Post, 333 }) 334 } 335 336 return result, nil 337 } 338 func (s *SqlThreadStore) GetThreadForUser(userId, teamId, threadId string, extended bool) (*model.ThreadResponse, error) { 339 type JoinedThread struct { 340 PostId string 341 Following bool 342 ReplyCount int64 343 LastReplyAt int64 344 LastViewedAt int64 345 UnreadReplies int64 346 UnreadMentions int64 347 Participants model.StringArray 348 model.Post 349 } 350 351 unreadRepliesQuery := "SELECT COUNT(Posts.Id) From Posts Where Posts.RootId=ThreadMemberships.PostId AND Posts.UpdateAt >= ThreadMemberships.LastViewed AND Posts.DeleteAt=0" 352 fetchConditions := sq.And{ 353 sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}}, 354 sq.Eq{"ThreadMemberships.UserId": userId}, 355 sq.Eq{"Threads.PostId": threadId}, 356 } 357 358 var thread JoinedThread 359 query, args, _ := s.getQueryBuilder(). 360 Select("Threads.*, Posts.*, ThreadMemberships.LastViewed as LastViewedAt, ThreadMemberships.UnreadMentions as UnreadMentions, ThreadMemberships.Following"). 361 From("Threads"). 362 Column(sq.Alias(sq.Expr(unreadRepliesQuery), "UnreadReplies")). 363 LeftJoin("Posts ON Posts.Id = Threads.PostId"). 364 LeftJoin("Channels ON Posts.ChannelId = Channels.Id"). 365 LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = Threads.PostId"). 366 Where(fetchConditions).ToSql() 367 err := s.GetReplica().SelectOne(&thread, query, args...) 368 369 if err != nil { 370 return nil, err 371 } 372 373 if !thread.Following { 374 return nil, nil // in case the thread is not followed anymore - return nil error to be interpreted as 404 375 } 376 377 var users []*model.User 378 if extended { 379 var err error 380 users, err = s.User().GetProfileByIds(context.Background(), thread.Participants, &store.UserGetByIdsOpts{}, true) 381 if err != nil { 382 return nil, errors.Wrapf(err, "failed to get threads for user id=%s", userId) 383 } 384 } else { 385 for _, userId := range thread.Participants { 386 users = append(users, &model.User{Id: userId}) 387 } 388 } 389 390 result := &model.ThreadResponse{ 391 PostId: thread.PostId, 392 ReplyCount: thread.ReplyCount, 393 LastReplyAt: thread.LastReplyAt, 394 LastViewedAt: thread.LastViewedAt, 395 UnreadReplies: thread.UnreadReplies, 396 UnreadMentions: thread.UnreadMentions, 397 Participants: users, 398 Post: &thread.Post, 399 } 400 401 return result, nil 402 } 403 404 func (s *SqlThreadStore) MarkAllAsRead(userId, teamId string) error { 405 memberships, err := s.GetMembershipsForUser(userId, teamId) 406 if err != nil { 407 return err 408 } 409 var membershipIds []string 410 for _, m := range memberships { 411 membershipIds = append(membershipIds, m.PostId) 412 } 413 timestamp := model.GetMillis() 414 query, args, _ := s.getQueryBuilder(). 415 Update("ThreadMemberships"). 416 Where(sq.Eq{"PostId": membershipIds}). 417 Where(sq.Eq{"UserId": userId}). 418 Set("LastViewed", timestamp). 419 Set("UnreadMentions", 0). 420 ToSql() 421 if _, err := s.GetMaster().Exec(query, args...); err != nil { 422 return errors.Wrapf(err, "failed to update thread read state for user id=%s", userId) 423 } 424 return nil 425 } 426 427 func (s *SqlThreadStore) MarkAsRead(userId, threadId string, timestamp int64) error { 428 query, args, _ := s.getQueryBuilder(). 429 Update("ThreadMemberships"). 430 Where(sq.Eq{"UserId": userId}). 431 Where(sq.Eq{"PostId": threadId}). 432 Set("LastViewed", timestamp). 433 ToSql() 434 if _, err := s.GetMaster().Exec(query, args...); err != nil { 435 return errors.Wrapf(err, "failed to update thread read state for user id=%s thread_id=%v", userId, threadId) 436 } 437 return nil 438 } 439 440 func (s *SqlThreadStore) Delete(threadId string) error { 441 query, args, _ := s.getQueryBuilder().Delete("Threads").Where(sq.Eq{"PostId": threadId}).ToSql() 442 if _, err := s.GetMaster().Exec(query, args...); err != nil { 443 return errors.Wrap(err, "failed to update threads") 444 } 445 446 return nil 447 } 448 449 func (s *SqlThreadStore) SaveMembership(membership *model.ThreadMembership) (*model.ThreadMembership, error) { 450 if err := s.GetMaster().Insert(membership); err != nil { 451 return nil, errors.Wrapf(err, "failed to save thread membership with postid=%s userid=%s", membership.PostId, membership.UserId) 452 } 453 454 return membership, nil 455 } 456 457 func (s *SqlThreadStore) UpdateMembership(membership *model.ThreadMembership) (*model.ThreadMembership, error) { 458 if _, err := s.GetMaster().Update(membership); err != nil { 459 return nil, errors.Wrapf(err, "failed to update thread membership with postid=%s userid=%s", membership.PostId, membership.UserId) 460 } 461 462 return membership, nil 463 } 464 465 func (s *SqlThreadStore) GetMembershipsForUser(userId, teamId string) ([]*model.ThreadMembership, error) { 466 var memberships []*model.ThreadMembership 467 468 query, args, _ := s.getQueryBuilder(). 469 Select("ThreadMemberships.*"). 470 Join("Threads ON Threads.PostId = ThreadMemberships.PostId"). 471 Join("Channels ON Threads.ChannelId = Channels.Id"). 472 From("ThreadMemberships"). 473 Where(sq.Or{sq.Eq{"Channels.TeamId": teamId}, sq.Eq{"Channels.TeamId": ""}}). 474 Where(sq.Eq{"ThreadMemberships.UserId": userId}). 475 ToSql() 476 477 _, err := s.GetReplica().Select(&memberships, query, args...) 478 if err != nil { 479 return nil, errors.Wrapf(err, "failed to get thread membership with userid=%s", userId) 480 } 481 return memberships, nil 482 } 483 484 func (s *SqlThreadStore) GetMembershipForUser(userId, postId string) (*model.ThreadMembership, error) { 485 var membership model.ThreadMembership 486 err := s.GetReplica().SelectOne(&membership, "SELECT * from ThreadMemberships WHERE UserId = :UserId AND PostId = :PostId", map[string]interface{}{"UserId": userId, "PostId": postId}) 487 if err != nil { 488 if err == sql.ErrNoRows { 489 return nil, store.NewErrNotFound("Thread", postId) 490 } 491 return nil, errors.Wrapf(err, "failed to get thread membership with userid=%s postid=%s", userId, postId) 492 } 493 return &membership, nil 494 } 495 496 func (s *SqlThreadStore) DeleteMembershipForUser(userId string, postId string) error { 497 if _, err := s.GetMaster().Exec("DELETE FROM ThreadMemberships Where PostId = :PostId AND UserId = :UserId", map[string]interface{}{"PostId": postId, "UserId": userId}); err != nil { 498 return errors.Wrap(err, "failed to update thread membership") 499 } 500 501 return nil 502 } 503 504 func (s *SqlThreadStore) CreateMembershipIfNeeded(userId, postId string, following, incrementMentions, updateFollowing bool) error { 505 membership, err := s.GetMembershipForUser(userId, postId) 506 now := utils.MillisFromTime(time.Now()) 507 if err == nil { 508 if (updateFollowing && !membership.Following || membership.Following != following) || incrementMentions { 509 if updateFollowing { 510 membership.Following = following 511 } 512 membership.LastUpdated = now 513 if incrementMentions { 514 membership.UnreadMentions += 1 515 } 516 _, err = s.UpdateMembership(membership) 517 } 518 return err 519 } 520 521 var nfErr *store.ErrNotFound 522 523 if !errors.As(err, &nfErr) { 524 return errors.Wrap(err, "failed to get thread membership") 525 } 526 mentions := 0 527 if incrementMentions { 528 mentions = 1 529 } 530 _, err = s.SaveMembership(&model.ThreadMembership{ 531 PostId: postId, 532 UserId: userId, 533 Following: following, 534 LastViewed: 0, 535 LastUpdated: now, 536 UnreadMentions: int64(mentions), 537 }) 538 if err != nil { 539 return err 540 } 541 542 thread, err := s.Get(postId) 543 if err != nil { 544 return err 545 } 546 if !thread.Participants.Contains(userId) { 547 thread.Participants = append(thread.Participants, userId) 548 _, err = s.Update(thread) 549 } 550 return err 551 } 552 553 func (s *SqlThreadStore) CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error) { 554 var changedThreads []string 555 query, args, _ := s.getQueryBuilder(). 556 Select("Threads.PostId"). 557 From("Threads"). 558 LeftJoin("ChannelMembers ON ChannelMembers.ChannelId=Threads.ChannelId"). 559 Where(sq.And{ 560 sq.Eq{"Threads.ChannelId": channelIds}, 561 sq.Eq{"ChannelMembers.UserId": userId}, 562 sq.Or{ 563 sq.Expr("Threads.LastReplyAt >= ChannelMembers.LastViewedAt"), 564 sq.GtOrEq{"Threads.LastReplyAt": timestamp}, 565 }, 566 }). 567 ToSql() 568 if _, err := s.GetReplica().Select(&changedThreads, query, args...); err != nil { 569 return nil, errors.Wrap(err, "failed to fetch threads") 570 } 571 return changedThreads, nil 572 } 573 574 func (s *SqlThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error { 575 if len(changedThreads) == 0 { 576 return nil 577 } 578 579 qb := s.getQueryBuilder(). 580 Update("ThreadMemberships"). 581 Where(sq.Eq{"UserId": userId, "PostId": changedThreads}). 582 Set("LastUpdated", timestamp) 583 584 if updateViewedTimestamp { 585 qb = qb.Set("LastViewed", timestamp) 586 } 587 updateQuery, updateArgs, _ := qb.ToSql() 588 589 if _, err := s.GetMaster().Exec(updateQuery, updateArgs...); err != nil { 590 return errors.Wrap(err, "failed to update thread membership") 591 } 592 593 return nil 594 } 595 596 func (s *SqlThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, error) { 597 query, args, _ := s.getQueryBuilder(). 598 Select("*"). 599 From("Posts"). 600 Where(sq.Eq{"RootId": threadId}). 601 Where(sq.Eq{"DeleteAt": 0}). 602 Where(sq.GtOrEq{"UpdateAt": since}).ToSql() 603 var result []*model.Post 604 if _, err := s.GetReplica().Select(&result, query, args...); err != nil { 605 return nil, errors.Wrap(err, "failed to fetch thread posts") 606 } 607 return result, nil 608 }