mirror of
https://github.com/hanzoai/team.git
synced 2026-11-01 07:55:31 +00:00
* Add read receipt store for burn on read message types * update mocks * fix invalidation target * have consistent case on index creation * Add temporary posts table * add mock * add transaction support * reflect review comments * wip: Add reveal endpoint * user check error id instead * wip: Add ws events and cleanup for burn on read posts * add burn endpoint for explicitly burning messages * add translations * Added logic to associate files of BoR post with the post * Added test * fixes * disable pinning posts and review comments * MM-66594 - Burn on read UI integration (#34647) * MM-66244 - add BoR visual components to message editor * MM-66246 - BoR visual indicator for sender and receiver * MM-66607 - bor - add timer countdown and autodeletion * add the system console max time to live config * use the max expire at and create global scheduler to register bor messages * use seconds for BoR config values in BE * implement the read by text shown in the tooltip logic * unestack the posts from same receiver and BoR and fix styling * avoid opening reply RHS * remove unused dispatchers * persis the BoR label in the drafts * move expiration value to metadata * adjust unit tests to metadata insted of props * code clean up and some performance improvements; add period grace for deletion too * adjust migration serie number * hide bor messages when config is off * performance improvements on post component and code clean up * keep bor existing post functionality if config is disabled * Add read receipt store for burn on read message types * Add temporary posts table * add transaction support * reflect review comments * wip: Add reveal endpoint * user check error id instead * wip: Add ws events and cleanup for burn on read posts * avoid reacting to unrevealed bor messages * adjust migration number * Add read receipt store for burn on read message types * have consistent case on index creation * Add temporary posts table * add mock * add transaction support * reflect review comments * wip: Add reveal endpoint * user check error id instead * wip: Add ws events and cleanup for burn on read posts * add burn endpoint for explicitly burning messages * adjust post reveal and type with backend changes * use real config values, adjust icon usage and style * adjust the delete from from sender and receiver * improve self deleting logic by placing in badge, use burn endpoint * adjust websocket events handling for the read by sender label information * adjust styling for concealed and error state * update burn-on-read post event handling for improved recipient tracking and multi-device sync * replace burn_on_read with type in database migrations and model * remove burn_on_read metadata from PostMetadata and related structures * Added logic to associate files of BoR post with the post * Added test * adjust migration name and fix linter * Add read receipt store for burn on read message types * update mocks * have consistent case on index creation * Add temporary posts table * add mock * add transaction support * reflect review comments * wip: Add reveal endpoint * user check error id instead * wip: Add ws events and cleanup for burn on read posts * add burn endpoint for explicitly burning messages * Added logic to associate files of BoR post with the post * Added test * disable pinning posts and review comments * show attachment on bor reveal * remove unused translation * Enhance burn-on-read post handling and refine previous post ID retrieval logic * adjust the returning chunk to work with bor messages * read temp post from master db * read from master * show the copy link button to the sender * revert unnecessary check * restore correct json tag * remove unused error handling and clarify burn-on-read comment * improve type safety and use proper selectors * eliminate code duplication in deletion handler * optimize performance and add documentation * delete bor message for sender once all receivers reveal it * add burn on read to scheduled posts * add feature enable check * use master to avoid all read recipients race condition --------- Co-authored-by: Mattermost Build <[email protected]> Co-authored-by: Ibrahim Serdar Acikgoz <[email protected]> Co-authored-by: Harshil Sharma <[email protected]> * squash migrations into single file * add configuration for the scheduler * don't run messagehasbeenposted hook * remove parallel tests on burn on read * add clean up for closing opened modals from previous tests * simplify delete menu item rendering * add cleanup step to close open modals after each test to prevent pollution * streamline delete button visibility logic for Burn on Read posts * improve reliability of closing post menu and modals by using body ESC key --------- Co-authored-by: Harshil Sharma <[email protected]> Co-authored-by: Pablo Vélez <[email protected]> Co-authored-by: Mattermost Build <[email protected]>
166 lines
6.3 KiB
Go
166 lines
6.3 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package localcachelayer
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
"github.com/mattermost/mattermost/server/v8/platform/services/cache"
|
|
)
|
|
|
|
type LocalCacheReadReceiptStore struct {
|
|
store.ReadReceiptStore
|
|
rootStore *LocalCacheStore
|
|
}
|
|
|
|
func (s *LocalCacheReadReceiptStore) handleClusterInvalidateReadReceipts(msg *model.ClusterMessage) {
|
|
if err := s.rootStore.readReceiptCache.Purge(); err != nil {
|
|
s.rootStore.logger.Error("failed to purge read receipt cache", mlog.Err(err))
|
|
}
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) ClearCaches() {
|
|
if err := s.rootStore.readReceiptCache.Purge(); err != nil {
|
|
s.rootStore.logger.Error("failed to purge read receipt cache", mlog.Err(err))
|
|
}
|
|
if err := s.rootStore.readReceiptPostReadersCache.Purge(); err != nil {
|
|
s.rootStore.logger.Error("failed to purge read receipt post readers cache", mlog.Err(err))
|
|
}
|
|
|
|
if s.rootStore.metrics != nil {
|
|
s.rootStore.metrics.IncrementMemCacheInvalidationCounter(s.rootStore.readReceiptCache.Name())
|
|
s.rootStore.metrics.IncrementMemCacheInvalidationCounter(s.rootStore.readReceiptPostReadersCache.Name())
|
|
}
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) InvalidateReadReceiptForPostsCache(postID string) {
|
|
s.rootStore.doInvalidateCacheCluster(s.rootStore.readReceiptPostReadersCache, postID, nil)
|
|
if s.rootStore.metrics != nil {
|
|
s.rootStore.metrics.IncrementMemCacheInvalidationCounter(s.rootStore.readReceiptPostReadersCache.Name())
|
|
}
|
|
if externalCache, ok := s.rootStore.readReceiptCache.(cache.ExternalCache); ok {
|
|
// For redis, invalidate all keys with pattern "postID:*"
|
|
s.rootStore.doInvalidateCacheCluster(externalCache, fmt.Sprintf("%s:*", postID), nil)
|
|
} else {
|
|
if err := s.rootStore.readReceiptCache.Purge(); err != nil {
|
|
s.rootStore.logger.Error("failed to purge read receipt cache", mlog.Err(err))
|
|
return
|
|
}
|
|
}
|
|
if s.rootStore.metrics != nil {
|
|
s.rootStore.metrics.IncrementMemCacheInvalidationCounter(s.rootStore.readReceiptCache.Name())
|
|
}
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) Delete(rctx request.CTX, postID, userID string) error {
|
|
defer func() {
|
|
s.InvalidateReadReceiptForPostsCache(postID)
|
|
}()
|
|
return s.ReadReceiptStore.Delete(rctx, postID, userID)
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) DeleteByPost(rctx request.CTX, postID string) error {
|
|
defer func(postID string) {
|
|
s.InvalidateReadReceiptForPostsCache(postID)
|
|
}(postID)
|
|
|
|
return s.ReadReceiptStore.DeleteByPost(rctx, postID)
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) Save(rctx request.CTX, receipt *model.ReadReceipt) (*model.ReadReceipt, error) {
|
|
defer func() {
|
|
s.rootStore.doInvalidateCacheCluster(s.rootStore.readReceiptCache, fmt.Sprintf("%s:%s", receipt.PostID, receipt.UserID), nil)
|
|
s.rootStore.doInvalidateCacheCluster(s.rootStore.readReceiptPostReadersCache, receipt.PostID, nil)
|
|
}()
|
|
return s.ReadReceiptStore.Save(rctx, receipt)
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) Update(rctx request.CTX, receipt *model.ReadReceipt) (*model.ReadReceipt, error) {
|
|
defer func() {
|
|
s.rootStore.doInvalidateCacheCluster(s.rootStore.readReceiptCache, fmt.Sprintf("%s:%s", receipt.PostID, receipt.UserID), nil)
|
|
}()
|
|
return s.ReadReceiptStore.Update(rctx, receipt)
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) Get(rctx request.CTX, postID, userID string) (*model.ReadReceipt, error) {
|
|
// no need to store the entire struct in cache, just the expireAt would be sufficient
|
|
// as other two fields are part of the cache key
|
|
var expireAt int64
|
|
if err := s.rootStore.doStandardReadCache(s.rootStore.readReceiptCache, fmt.Sprintf("%s:%s", postID, userID), &expireAt); err == nil {
|
|
return &model.ReadReceipt{
|
|
PostID: postID,
|
|
UserID: userID,
|
|
ExpireAt: expireAt,
|
|
}, nil
|
|
}
|
|
|
|
rr, err := s.ReadReceiptStore.Get(rctx, postID, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.rootStore.doStandardAddToCache(s.rootStore.readReceiptCache, fmt.Sprintf("%s:%s", postID, userID), rr.ExpireAt)
|
|
|
|
// Update post readers cache: add this userID to the list if not already present
|
|
var existingUserIDs []string
|
|
if err := s.rootStore.doStandardReadCache(s.rootStore.readReceiptPostReadersCache, postID, &existingUserIDs); err == nil {
|
|
// Cache exists: check if userID is already in the list
|
|
if !slices.Contains(existingUserIDs, userID) {
|
|
// Add userID to the existing list
|
|
existingUserIDs = append(existingUserIDs, userID)
|
|
s.rootStore.doStandardAddToCache(s.rootStore.readReceiptPostReadersCache, postID, existingUserIDs)
|
|
}
|
|
} else {
|
|
// Cache doesn't exist: create new entry with just this userID
|
|
s.rootStore.doStandardAddToCache(s.rootStore.readReceiptPostReadersCache, postID, []string{userID})
|
|
}
|
|
|
|
return rr, nil
|
|
}
|
|
|
|
func (s LocalCacheReadReceiptStore) GetByPost(rctx request.CTX, postID string) ([]*model.ReadReceipt, error) {
|
|
// Try to get cached user IDs for this post
|
|
var cachedUserIDs []string
|
|
if err := s.rootStore.doStandardReadCache(s.rootStore.readReceiptPostReadersCache, postID, &cachedUserIDs); err == nil {
|
|
// Cache hit: reconstruct receipts from cached user IDs and individual receipt caches
|
|
receipts := make([]*model.ReadReceipt, 0, len(cachedUserIDs))
|
|
for _, userID := range cachedUserIDs {
|
|
var expireAt int64
|
|
if err := s.rootStore.doStandardReadCache(s.rootStore.readReceiptCache, fmt.Sprintf("%s:%s", postID, userID), &expireAt); err == nil {
|
|
receipts = append(receipts, &model.ReadReceipt{
|
|
PostID: postID,
|
|
UserID: userID,
|
|
ExpireAt: expireAt,
|
|
})
|
|
}
|
|
}
|
|
// If we got all receipts from cache, return them
|
|
if len(receipts) == len(cachedUserIDs) {
|
|
return receipts, nil
|
|
}
|
|
}
|
|
|
|
// Cache miss or partial cache: fetch from underlying store
|
|
receipts, err := s.ReadReceiptStore.GetByPost(rctx, postID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Cache the user IDs for this post
|
|
userIDs := make([]string, len(receipts))
|
|
for i, receipt := range receipts {
|
|
userIDs[i] = receipt.UserID
|
|
// Also ensure individual receipts are cached
|
|
s.rootStore.doStandardAddToCache(s.rootStore.readReceiptCache, fmt.Sprintf("%s:%s", postID, receipt.UserID), receipt.ExpireAt)
|
|
}
|
|
s.rootStore.doStandardAddToCache(s.rootStore.readReceiptPostReadersCache, postID, userIDs)
|
|
|
|
return receipts, nil
|
|
}
|