Files
zeekay 8feb24ea1f One name for this fork, and it is ours
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.

That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.

Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
2026-08-28 23:35:36 -07:00

189 lines
7.4 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package localcachelayer
import (
"fmt"
"slices"
"github.com/hanzoteam/server/server/public/model"
"github.com/hanzoteam/server/server/public/shared/mlog"
"github.com/hanzoteam/server/server/public/shared/request"
"github.com/hanzoteam/server/server/v8/channels/store"
"github.com/hanzoteam/server/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 err := s.rootStore.readReceiptPostUnreadCountCache.Purge(); err != nil {
s.rootStore.logger.Error("failed to purge read receipt post unread count 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())
s.rootStore.metrics.IncrementMemCacheInvalidationCounter(s.rootStore.readReceiptPostUnreadCountCache.Name())
}
}
func (s LocalCacheReadReceiptStore) InvalidateReadReceiptForPostsCache(postID string) {
s.rootStore.doInvalidateCacheCluster(s.rootStore.readReceiptPostReadersCache, postID, nil)
s.rootStore.doInvalidateCacheCluster(s.rootStore.readReceiptPostUnreadCountCache, postID, nil)
if s.rootStore.metrics != nil {
s.rootStore.metrics.IncrementMemCacheInvalidationCounter(s.rootStore.readReceiptPostReadersCache.Name())
s.rootStore.metrics.IncrementMemCacheInvalidationCounter(s.rootStore.readReceiptPostUnreadCountCache.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)
s.rootStore.doInvalidateCacheCluster(s.rootStore.readReceiptPostUnreadCountCache, 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
}
func (s LocalCacheReadReceiptStore) GetUnreadCountForPost(rctx request.CTX, post *model.Post) (int64, error) {
var count int64
if err := s.rootStore.doStandardReadCache(s.rootStore.readReceiptPostUnreadCountCache, post.Id, &count); err == nil {
return count, nil
}
count, err := s.ReadReceiptStore.GetUnreadCountForPost(rctx, post)
if err != nil {
return count, err
}
s.rootStore.doStandardAddToCache(s.rootStore.readReceiptPostUnreadCountCache, post.Id, count)
return count, nil
}