mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-08 05:47:07 +09:00
GO-5297 Merge branch 'main' of github.com:anyproto/anytype-heart into go-5297-push-service-1st-iteration
This commit is contained in:
commit
49680f375e
44 changed files with 5150 additions and 4701 deletions
|
@ -116,6 +116,10 @@ func validateDetails(s *pb.SnapshotWithType, info *useCaseInfo) (err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if k == bundle.RelationKeyAutoWidgetTargets.String() && val == "bin" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, found := info.objects[val]
|
||||
if !found {
|
||||
if isBrokenTemplate(k, val) {
|
||||
|
|
|
@ -32,15 +32,15 @@ const CName = "core.block.chats"
|
|||
var log = logging.Logger(CName).Desugar()
|
||||
|
||||
type Service interface {
|
||||
AddMessage(ctx context.Context, sessionCtx session.Context, chatObjectId string, message *model.ChatMessage) (string, error)
|
||||
EditMessage(ctx context.Context, chatObjectId string, messageId string, newMessage *model.ChatMessage) error
|
||||
AddMessage(ctx context.Context, sessionCtx session.Context, chatObjectId string, message *chatobject.Message) (string, error)
|
||||
EditMessage(ctx context.Context, chatObjectId string, messageId string, newMessage *chatobject.Message) error
|
||||
ToggleMessageReaction(ctx context.Context, chatObjectId string, messageId string, emoji string) error
|
||||
DeleteMessage(ctx context.Context, chatObjectId string, messageId string) error
|
||||
GetMessages(ctx context.Context, chatObjectId string, req chatobject.GetMessagesRequest) (*chatobject.GetMessagesResponse, error)
|
||||
GetMessagesByIds(ctx context.Context, chatObjectId string, messageIds []string) ([]*model.ChatMessage, error)
|
||||
GetMessagesByIds(ctx context.Context, chatObjectId string, messageIds []string) ([]*chatobject.Message, error)
|
||||
SubscribeLastMessages(ctx context.Context, chatObjectId string, limit int, subId string) (*chatobject.SubscribeLastMessagesResponse, error)
|
||||
ReadMessages(ctx context.Context, chatObjectId string, afterOrderId string, beforeOrderId string, lastDbState int64) error
|
||||
UnreadMessages(ctx context.Context, chatObjectId string, afterOrderId string) error
|
||||
ReadMessages(ctx context.Context, req ReadMessagesRequest) error
|
||||
UnreadMessages(ctx context.Context, chatObjectId string, afterOrderId string, counterType chatobject.CounterType) error
|
||||
Unsubscribe(chatObjectId string, subId string) error
|
||||
|
||||
SubscribeToMessagePreviews(ctx context.Context) (string, error)
|
||||
|
@ -231,13 +231,14 @@ func (s *service) Close(ctx context.Context) error {
|
|||
|
||||
s.componentCtxCancel()
|
||||
|
||||
err = errors.Join(err,
|
||||
s.crossSpaceSubService.Unsubscribe(allChatsSubscriptionId),
|
||||
)
|
||||
unsubErr := s.crossSpaceSubService.Unsubscribe(allChatsSubscriptionId)
|
||||
if !errors.Is(err, crossspacesub.ErrSubscriptionNotFound) {
|
||||
err = errors.Join(err, unsubErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *service) AddMessage(ctx context.Context, sessionCtx session.Context, chatObjectId string, message *model.ChatMessage) (string, error) {
|
||||
func (s *service) AddMessage(ctx context.Context, sessionCtx session.Context, chatObjectId string, message *chatobject.Message) (string, error) {
|
||||
var messageId, spaceId string
|
||||
err := cache.Do(s.objectGetter, chatObjectId, func(sb chatobject.StoreObject) error {
|
||||
var err error
|
||||
|
@ -261,7 +262,7 @@ func (s *service) sendPushNotification(spaceId, chatObjectId string, message *mo
|
|||
}
|
||||
}
|
||||
|
||||
func (s *service) EditMessage(ctx context.Context, chatObjectId string, messageId string, newMessage *model.ChatMessage) error {
|
||||
func (s *service) EditMessage(ctx context.Context, chatObjectId string, messageId string, newMessage *chatobject.Message) error {
|
||||
return cache.Do(s.objectGetter, chatObjectId, func(sb chatobject.StoreObject) error {
|
||||
return sb.EditMessage(ctx, messageId, newMessage)
|
||||
})
|
||||
|
@ -292,8 +293,8 @@ func (s *service) GetMessages(ctx context.Context, chatObjectId string, req chat
|
|||
return resp, err
|
||||
}
|
||||
|
||||
func (s *service) GetMessagesByIds(ctx context.Context, chatObjectId string, messageIds []string) ([]*model.ChatMessage, error) {
|
||||
var res []*model.ChatMessage
|
||||
func (s *service) GetMessagesByIds(ctx context.Context, chatObjectId string, messageIds []string) ([]*chatobject.Message, error) {
|
||||
var res []*chatobject.Message
|
||||
err := cache.Do(s.objectGetter, chatObjectId, func(sb chatobject.StoreObject) error {
|
||||
msg, err := sb.GetMessagesByIds(ctx, messageIds)
|
||||
if err != nil {
|
||||
|
@ -324,14 +325,22 @@ func (s *service) Unsubscribe(chatObjectId string, subId string) error {
|
|||
})
|
||||
}
|
||||
|
||||
func (s *service) ReadMessages(ctx context.Context, chatObjectId string, afterOrderId string, beforeOrderId string, lastAddedMessageTimestamp int64) error {
|
||||
return cache.Do(s.objectGetter, chatObjectId, func(sb chatobject.StoreObject) error {
|
||||
return sb.MarkReadMessages(ctx, afterOrderId, beforeOrderId, lastAddedMessageTimestamp)
|
||||
type ReadMessagesRequest struct {
|
||||
ChatObjectId string
|
||||
AfterOrderId string
|
||||
BeforeOrderId string
|
||||
LastStateId string
|
||||
CounterType chatobject.CounterType
|
||||
}
|
||||
|
||||
func (s *service) ReadMessages(ctx context.Context, req ReadMessagesRequest) error {
|
||||
return cache.Do(s.objectGetter, req.ChatObjectId, func(sb chatobject.StoreObject) error {
|
||||
return sb.MarkReadMessages(ctx, req.AfterOrderId, req.BeforeOrderId, req.LastStateId, req.CounterType)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) UnreadMessages(ctx context.Context, chatObjectId string, afterOrderId string) error {
|
||||
func (s *service) UnreadMessages(ctx context.Context, chatObjectId string, afterOrderId string, counterType chatobject.CounterType) error {
|
||||
return cache.Do(s.objectGetter, chatObjectId, func(sb chatobject.StoreObject) error {
|
||||
return sb.MarkMessagesAsUnread(ctx, afterOrderId)
|
||||
return sb.MarkMessagesAsUnread(ctx, afterOrderId, counterType)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ func (s *Service) CreateWorkspace(ctx context.Context, req *pb.RpcWorkspaceCreat
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("set details for space %s: %w", newSpace.Id(), err)
|
||||
}
|
||||
_, err = s.builtinObjectService.CreateObjectsForUseCase(nil, newSpace.Id(), req.UseCase)
|
||||
_, _, err = s.builtinObjectService.CreateObjectsForUseCase(nil, newSpace.Id(), req.UseCase)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("import use-case: %w", err)
|
||||
}
|
||||
|
|
|
@ -9,26 +9,27 @@ import (
|
|||
anystore "github.com/anyproto/any-store"
|
||||
"github.com/anyproto/any-store/anyenc"
|
||||
"github.com/anyproto/any-store/query"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/storestate"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/util/timeid"
|
||||
)
|
||||
|
||||
type ChatHandler struct {
|
||||
collection anystore.Collection
|
||||
repository *repository
|
||||
subscription *subscription
|
||||
currentIdentity string
|
||||
myParticipantId string
|
||||
// forceNotRead forces handler to mark all messages as not read. It's useful for unit testing
|
||||
forceNotRead bool
|
||||
}
|
||||
|
||||
func (d *ChatHandler) CollectionName() string {
|
||||
return collectionName
|
||||
return CollectionName
|
||||
}
|
||||
|
||||
func (d *ChatHandler) Init(ctx context.Context, s *storestate.StoreState) (err error) {
|
||||
coll, err := s.Collection(ctx, collectionName)
|
||||
coll, err := s.Collection(ctx, CollectionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -38,35 +39,48 @@ func (d *ChatHandler) Init(ctx context.Context, s *storestate.StoreState) (err e
|
|||
if iErr != nil && !errors.Is(iErr, anystore.ErrIndexExists) {
|
||||
return iErr
|
||||
}
|
||||
d.collection = coll
|
||||
return
|
||||
}
|
||||
|
||||
func (d *ChatHandler) BeforeCreate(ctx context.Context, ch storestate.ChangeOp) (err error) {
|
||||
msg := newMessageWrapper(ch.Arena, ch.Value)
|
||||
msg.setCreatedAt(ch.Change.Timestamp)
|
||||
msg.setCreator(ch.Change.Creator)
|
||||
func (d *ChatHandler) BeforeCreate(ctx context.Context, ch storestate.ChangeOp) error {
|
||||
msg, err := unmarshalMessage(ch.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal message: %w", err)
|
||||
}
|
||||
msg.CreatedAt = ch.Change.Timestamp
|
||||
msg.Creator = ch.Change.Creator
|
||||
if d.forceNotRead {
|
||||
msg.setRead(false)
|
||||
msg.Read = false
|
||||
msg.MentionRead = false
|
||||
} else {
|
||||
if ch.Change.Creator == d.currentIdentity {
|
||||
msg.setRead(true)
|
||||
msg.Read = true
|
||||
msg.MentionRead = true
|
||||
} else {
|
||||
msg.setRead(false)
|
||||
msg.Read = false
|
||||
msg.MentionRead = false
|
||||
}
|
||||
}
|
||||
|
||||
msg.setAddedAt(timeid.NewNano())
|
||||
model := msg.toModel()
|
||||
model.OrderId = ch.Change.Order
|
||||
msg.StateId = bson.NewObjectId().Hex()
|
||||
|
||||
prevOrderId, err := getPrevOrderId(ctx, d.collection, ch.Change.Order)
|
||||
isMentioned, err := msg.IsCurrentUserMentioned(ctx, d.myParticipantId, d.currentIdentity, d.repository)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check if current user is mentioned: %w", err)
|
||||
}
|
||||
msg.CurrentUserMentioned = isMentioned
|
||||
msg.OrderId = ch.Change.Order
|
||||
|
||||
prevOrderId, err := d.repository.getPrevOrderId(ctx, ch.Change.Order)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get prev order id: %w", err)
|
||||
}
|
||||
d.subscription.add(prevOrderId, model)
|
||||
|
||||
return
|
||||
d.subscription.add(ctx, prevOrderId, msg)
|
||||
|
||||
msg.MarshalAnyenc(ch.Value, ch.Arena)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChatHandler) BeforeModify(ctx context.Context, ch storestate.ChangeOp) (mode storestate.ModifyMode, err error) {
|
||||
|
@ -74,7 +88,7 @@ func (d *ChatHandler) BeforeModify(ctx context.Context, ch storestate.ChangeOp)
|
|||
}
|
||||
|
||||
func (d *ChatHandler) BeforeDelete(ctx context.Context, ch storestate.ChangeOp) (mode storestate.DeleteMode, err error) {
|
||||
coll, err := ch.State.Collection(ctx, collectionName)
|
||||
coll, err := ch.State.Collection(ctx, CollectionName)
|
||||
if err != nil {
|
||||
return storestate.DeleteModeDelete, fmt.Errorf("get collection: %w", err)
|
||||
}
|
||||
|
@ -86,12 +100,16 @@ func (d *ChatHandler) BeforeDelete(ctx context.Context, ch storestate.ChangeOp)
|
|||
return storestate.DeleteModeDelete, fmt.Errorf("get message: %w", err)
|
||||
}
|
||||
|
||||
message := newMessageWrapper(ch.Arena, doc.Value())
|
||||
if message.getCreator() != ch.Change.Creator {
|
||||
message, err := unmarshalMessage(doc.Value())
|
||||
if err != nil {
|
||||
return storestate.DeleteModeDelete, fmt.Errorf("unmarshal message: %w", err)
|
||||
}
|
||||
if message.Creator != ch.Change.Creator {
|
||||
return storestate.DeleteModeDelete, errors.New("can't delete not own message")
|
||||
}
|
||||
|
||||
d.subscription.delete(messageId)
|
||||
|
||||
return storestate.DeleteModeDelete, nil
|
||||
}
|
||||
|
||||
|
@ -109,8 +127,10 @@ func (d *ChatHandler) UpgradeKeyModifier(ch storestate.ChangeOp, key *pb.KeyModi
|
|||
}
|
||||
|
||||
if modified {
|
||||
msg := newMessageWrapper(a, result)
|
||||
model := msg.toModel()
|
||||
msg, err := unmarshalMessage(result)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("unmarshal message: %w", err)
|
||||
}
|
||||
|
||||
switch path {
|
||||
case reactionsKey:
|
||||
|
@ -121,15 +141,15 @@ func (d *ChatHandler) UpgradeKeyModifier(ch storestate.ChangeOp, key *pb.KeyModi
|
|||
}
|
||||
// TODO Count validation
|
||||
|
||||
d.subscription.updateReactions(model)
|
||||
d.subscription.updateReactions(msg)
|
||||
case contentKey:
|
||||
creator := model.Creator
|
||||
creator := msg.Creator
|
||||
if creator != ch.Change.Creator {
|
||||
return v, false, errors.Join(storestate.ErrValidation, fmt.Errorf("can't modify someone else's message"))
|
||||
}
|
||||
result.Set(modifiedAtKey, a.NewNumberInt(int(ch.Change.Timestamp)))
|
||||
model.ModifiedAt = ch.Change.Timestamp
|
||||
d.subscription.updateFull(model)
|
||||
msg.ModifiedAt = ch.Change.Timestamp
|
||||
msg.MarshalAnyenc(result, a)
|
||||
d.subscription.updateFull(msg)
|
||||
default:
|
||||
return nil, false, fmt.Errorf("invalid key path %s", key.KeyPath)
|
||||
}
|
||||
|
|
|
@ -2,14 +2,11 @@ package chatobject
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
anystore "github.com/anyproto/any-store"
|
||||
"github.com/anyproto/any-store/anyenc"
|
||||
"github.com/anyproto/any-store/query"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
|
@ -19,6 +16,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/storestate"
|
||||
"github.com/anyproto/anytype-heart/core/block/source"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/core/event"
|
||||
"github.com/anyproto/anytype-heart/core/session"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
|
@ -28,10 +26,12 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
collectionName = "chats"
|
||||
descOrder = "-_o.id"
|
||||
ascOrder = "_o.id"
|
||||
descAdded = "-a"
|
||||
CollectionName = "chats"
|
||||
descOrder = "-_o.id"
|
||||
ascOrder = "_o.id"
|
||||
descStateId = "-stateId"
|
||||
diffManagerMessages = "messages"
|
||||
diffManagerMentions = "mentions"
|
||||
)
|
||||
|
||||
var log = logging.Logger("core.block.editor.chatobject").Desugar()
|
||||
|
@ -40,15 +40,15 @@ type StoreObject interface {
|
|||
smartblock.SmartBlock
|
||||
anystoredebug.AnystoreDebug
|
||||
|
||||
AddMessage(ctx context.Context, sessionCtx session.Context, message *model.ChatMessage) (string, error)
|
||||
AddMessage(ctx context.Context, sessionCtx session.Context, message *Message) (string, error)
|
||||
GetMessages(ctx context.Context, req GetMessagesRequest) (*GetMessagesResponse, error)
|
||||
GetMessagesByIds(ctx context.Context, messageIds []string) ([]*model.ChatMessage, error)
|
||||
EditMessage(ctx context.Context, messageId string, newMessage *model.ChatMessage) error
|
||||
GetMessagesByIds(ctx context.Context, messageIds []string) ([]*Message, error)
|
||||
EditMessage(ctx context.Context, messageId string, newMessage *Message) error
|
||||
ToggleMessageReaction(ctx context.Context, messageId string, emoji string) error
|
||||
DeleteMessage(ctx context.Context, messageId string) error
|
||||
SubscribeLastMessages(ctx context.Context, subId string, limit int, asyncInit bool) (*SubscribeLastMessagesResponse, error)
|
||||
MarkReadMessages(ctx context.Context, afterOrderId string, beforeOrderId string, lastAddedMessageTimestamp int64) error
|
||||
MarkMessagesAsUnread(ctx context.Context, afterOrderId string) error
|
||||
MarkReadMessages(ctx context.Context, afterOrderId string, beforeOrderId string, lastStateId string, counterType CounterType) error
|
||||
MarkMessagesAsUnread(ctx context.Context, afterOrderId string, counterType CounterType) error
|
||||
Unsubscribe(subId string) error
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,6 @@ type storeObject struct {
|
|||
locker smartblock.Locker
|
||||
|
||||
seenHeadsCollector seenHeadsCollector
|
||||
collection anystore.Collection
|
||||
accountService AccountService
|
||||
storeSource source.Store
|
||||
store *storestate.StoreState
|
||||
|
@ -82,6 +81,7 @@ type storeObject struct {
|
|||
crdtDb anystore.DB
|
||||
spaceIndex spaceindex.Store
|
||||
chatHandler *ChatHandler
|
||||
repository *repository
|
||||
|
||||
arenaPool *anyenc.ArenaPool
|
||||
componentCtx context.Context
|
||||
|
@ -108,18 +108,52 @@ func (s *storeObject) Init(ctx *smartblock.InitContext) error {
|
|||
if !ok {
|
||||
return fmt.Errorf("source is not a store")
|
||||
}
|
||||
storeSource.SetDiffManagerOnRemoveHook(s.markReadMessages)
|
||||
err := s.SmartBlock.Init(ctx)
|
||||
|
||||
collection, err := s.crdtDb.Collection(ctx.Ctx, storeSource.Id()+CollectionName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get collection: %w", err)
|
||||
}
|
||||
|
||||
s.repository = &repository{
|
||||
collection: collection,
|
||||
arenaPool: s.arenaPool,
|
||||
}
|
||||
// Use Object and Space IDs from source, because object is not initialized yet
|
||||
myParticipantId := domain.NewParticipantId(ctx.Source.SpaceID(), s.accountService.AccountID())
|
||||
s.subscription = s.newSubscription(
|
||||
domain.FullID{ObjectID: ctx.Source.Id(), SpaceID: ctx.Source.SpaceID()},
|
||||
s.accountService.AccountID(),
|
||||
myParticipantId,
|
||||
)
|
||||
|
||||
messagesOpts := newReadHandler(CounterTypeMessage, s.subscription)
|
||||
mentionsOpts := newReadHandler(CounterTypeMention, s.subscription)
|
||||
|
||||
// Diff managers should be added before SmartBlock.Init, because they have to be initialized in source.ReadStoreDoc
|
||||
storeSource.RegisterDiffManager(diffManagerMessages, func(removed []string) {
|
||||
markErr := s.markReadMessages(removed, messagesOpts)
|
||||
if markErr != nil {
|
||||
log.Error("mark read messages", zap.Error(markErr))
|
||||
}
|
||||
})
|
||||
storeSource.RegisterDiffManager(diffManagerMentions, func(removed []string) {
|
||||
markErr := s.markReadMessages(removed, mentionsOpts)
|
||||
if markErr != nil {
|
||||
log.Error("mark read mentions", zap.Error(markErr))
|
||||
}
|
||||
})
|
||||
|
||||
err = s.SmartBlock.Init(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.storeSource = storeSource
|
||||
|
||||
s.subscription = newSubscription(s.SpaceID(), s.Id(), s.eventSender, s.spaceIndex)
|
||||
|
||||
s.chatHandler = &ChatHandler{
|
||||
repository: s.repository,
|
||||
subscription: s.subscription,
|
||||
currentIdentity: s.accountService.AccountID(),
|
||||
myParticipantId: myParticipantId,
|
||||
}
|
||||
|
||||
stateStore, err := storestate.New(ctx.Ctx, s.Id(), s.crdtDb, s.chatHandler)
|
||||
|
@ -127,12 +161,8 @@ func (s *storeObject) Init(ctx *smartblock.InitContext) error {
|
|||
return fmt.Errorf("create state store: %w", err)
|
||||
}
|
||||
s.store = stateStore
|
||||
s.collection, err = s.store.Collection(s.componentCtx, collectionName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get s.collection.ction: %w", err)
|
||||
}
|
||||
|
||||
s.subscription.chatState, err = s.initialChatState()
|
||||
err = s.subscription.loadChatState(s.componentCtx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init chat state: %w", err)
|
||||
}
|
||||
|
@ -153,353 +183,50 @@ func (s *storeObject) onUpdate() {
|
|||
s.subscription.flush()
|
||||
}
|
||||
|
||||
// initialChatState returns the initial chat state for the chat object from the DB
|
||||
func (s *storeObject) initialChatState() (*model.ChatState, error) {
|
||||
txn, err := s.collection.ReadTx(s.componentCtx)
|
||||
func (s *storeObject) GetMessageById(ctx context.Context, id string) (*Message, error) {
|
||||
messages, err := s.GetMessagesByIds(ctx, []string{id})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("start read tx: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
defer txn.Commit()
|
||||
|
||||
oldestOrderId, err := s.getOldestOrderId(txn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get oldest order id: %w", err)
|
||||
if len(messages) == 0 {
|
||||
return nil, fmt.Errorf("message not found")
|
||||
}
|
||||
|
||||
count, err := s.countUnreadMessages(txn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("update messages: %w", err)
|
||||
}
|
||||
|
||||
lastAdded, err := s.getLastAddedDate(txn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get last added date: %w", err)
|
||||
}
|
||||
|
||||
return &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
OldestOrderId: oldestOrderId,
|
||||
Counter: int32(count),
|
||||
},
|
||||
// todo: add replies counter
|
||||
DbTimestamp: int64(lastAdded),
|
||||
}, nil
|
||||
return messages[0], nil
|
||||
}
|
||||
|
||||
func (s *storeObject) getOldestOrderId(txn anystore.ReadTx) (string, error) {
|
||||
unreadQuery := s.collection.Find(unreadFilter()).Sort(ascOrder)
|
||||
|
||||
iter, err := unreadQuery.Limit(1).Iter(txn.Context())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("init iter: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
return doc.Value().GetObject(orderKey).Get("id").GetString(), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *storeObject) countUnreadMessages(txn anystore.ReadTx) (int, error) {
|
||||
unreadQuery := s.collection.Find(unreadFilter())
|
||||
|
||||
return unreadQuery.Limit(1).Count(txn.Context())
|
||||
}
|
||||
|
||||
func unreadFilter() query.Filter {
|
||||
// Use Not because old messages don't have read key
|
||||
return query.Not{
|
||||
Filter: query.Key{Path: []string{readKey}, Filter: query.NewComp(query.CompOpEq, true)},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storeObject) getLastAddedDate(txn anystore.ReadTx) (int, error) {
|
||||
lastAddedDate := s.collection.Find(nil).Sort(descAdded).Limit(1)
|
||||
iter, err := lastAddedDate.Iter(txn.Context())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("find last added date: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
return doc.Value().GetInt(addedKey), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *storeObject) markReadMessages(changeIds []string) {
|
||||
if len(changeIds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
txn, err := s.collection.WriteTx(s.componentCtx)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Error("markReadMessages: start write tx")
|
||||
return
|
||||
}
|
||||
defer txn.Commit()
|
||||
|
||||
var idsModified []string
|
||||
for _, id := range changeIds {
|
||||
if id == s.Id() {
|
||||
// skip tree root
|
||||
continue
|
||||
}
|
||||
res, err := s.collection.UpdateId(txn.Context(), id, query.MustParseModifier(`{"$set":{"`+readKey+`":true}}`))
|
||||
// Not all changes are messages, skip them
|
||||
if errors.Is(err, anystore.ErrDocNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("markReadMessages: update message", zap.Error(err), zap.String("changeId", id), zap.String("chatObjectId", s.Id()))
|
||||
continue
|
||||
}
|
||||
if res.Modified > 0 {
|
||||
idsModified = append(idsModified, id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(idsModified) > 0 {
|
||||
newOldestOrderId, err := s.getOldestOrderId(txn)
|
||||
if err != nil {
|
||||
log.Error("markReadMessages: get oldest order id", zap.Error(err))
|
||||
err = txn.Rollback()
|
||||
if err != nil {
|
||||
log.Error("markReadMessages: rollback transaction", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
s.subscription.updateChatState(func(state *model.ChatState) {
|
||||
state.Messages.OldestOrderId = newOldestOrderId
|
||||
})
|
||||
s.subscription.updateReadStatus(idsModified, true)
|
||||
s.subscription.flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storeObject) MarkReadMessages(ctx context.Context, afterOrderId, beforeOrderId string, lastAddedMessageTimestamp int64) error {
|
||||
// 1. select all messages with orderId < beforeOrderId and addedTime < lastDbState
|
||||
// 2. use the last(by orderId) message id as lastHead
|
||||
// 3. update the MarkSeenHeads
|
||||
// 2. mark messages as read in the DB
|
||||
|
||||
msgs, err := s.getUnreadMessageIdsInRange(ctx, afterOrderId, beforeOrderId, lastAddedMessageTimestamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get message: %w", err)
|
||||
}
|
||||
|
||||
// mark the whole tree as seen from the current message
|
||||
return s.storeSource.MarkSeenHeads(ctx, msgs)
|
||||
}
|
||||
|
||||
func (s *storeObject) MarkMessagesAsUnread(ctx context.Context, afterOrderId string) error {
|
||||
txn, err := s.collection.WriteTx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create tx: %w", err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
msgs, err := s.getReadMessagesAfter(txn, afterOrderId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get read messages: %w", err)
|
||||
}
|
||||
|
||||
if len(msgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, msgId := range msgs {
|
||||
_, err := s.collection.UpdateId(txn.Context(), msgId, query.MustParseModifier(`{"$set":{"`+readKey+`":false}}`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("update message: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
newOldestOrderId, err := s.getOldestOrderId(txn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get oldest order id: %w", err)
|
||||
}
|
||||
|
||||
lastAdded, err := s.getLastAddedDate(txn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get last added date: %w", err)
|
||||
}
|
||||
|
||||
s.subscription.updateChatState(func(state *model.ChatState) {
|
||||
state.Messages.OldestOrderId = newOldestOrderId
|
||||
state.DbTimestamp = int64(lastAdded)
|
||||
})
|
||||
s.subscription.updateReadStatus(msgs, false)
|
||||
s.subscription.flush()
|
||||
|
||||
seenHeads, err := s.seenHeadsCollector.collectSeenHeads(ctx, afterOrderId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get seen heads: %w", err)
|
||||
}
|
||||
err = s.storeSource.InitDiffManager(ctx, seenHeads)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init diff manager: %w", err)
|
||||
}
|
||||
err = s.storeSource.StoreSeenHeads(txn.Context())
|
||||
if err != nil {
|
||||
return fmt.Errorf("store seen heads: %w", err)
|
||||
}
|
||||
|
||||
return txn.Commit()
|
||||
}
|
||||
|
||||
func (s *storeObject) getReadMessagesAfter(txn anystore.ReadTx, afterOrderId string) ([]string, error) {
|
||||
iter, err := s.collection.Find(query.And{
|
||||
query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpGte, afterOrderId)},
|
||||
query.Key{Path: []string{readKey}, Filter: query.NewComp(query.CompOpEq, true)},
|
||||
}).Iter(txn.Context())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init iterator: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var msgIds []string
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
msgIds = append(msgIds, doc.Value().GetString("id"))
|
||||
}
|
||||
return msgIds, iter.Err()
|
||||
}
|
||||
|
||||
func (s *storeObject) getUnreadMessageIdsInRange(ctx context.Context, afterOrderId, beforeOrderId string, lastAddedMessageTimestamp int64) ([]string, error) {
|
||||
iter, err := s.collection.Find(
|
||||
query.And{
|
||||
query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpGte, afterOrderId)},
|
||||
query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpLte, beforeOrderId)},
|
||||
query.Or{
|
||||
query.Not{query.Key{Path: []string{addedKey}, Filter: query.Exists{}}},
|
||||
query.Key{Path: []string{addedKey}, Filter: query.NewComp(query.CompOpLte, lastAddedMessageTimestamp)},
|
||||
},
|
||||
unreadFilter(),
|
||||
},
|
||||
).Iter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find id: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var msgIds []string
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
msgIds = append(msgIds, doc.Value().GetString("id"))
|
||||
}
|
||||
return msgIds, iter.Err()
|
||||
}
|
||||
|
||||
func (s *storeObject) GetMessagesByIds(ctx context.Context, messageIds []string) ([]*model.ChatMessage, error) {
|
||||
txn, err := s.collection.ReadTx(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("start read tx: %w", err)
|
||||
}
|
||||
messages := make([]*model.ChatMessage, 0, len(messageIds))
|
||||
for _, messageId := range messageIds {
|
||||
obj, err := s.collection.FindId(txn.Context(), messageId)
|
||||
if errors.Is(err, anystore.ErrDocNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Join(txn.Commit(), fmt.Errorf("find id: %w", err))
|
||||
}
|
||||
msg := newMessageWrapper(nil, obj.Value())
|
||||
messages = append(messages, msg.toModel())
|
||||
}
|
||||
return messages, txn.Commit()
|
||||
func (s *storeObject) GetMessagesByIds(ctx context.Context, messageIds []string) ([]*Message, error) {
|
||||
return s.repository.getMessagesByIds(ctx, messageIds)
|
||||
}
|
||||
|
||||
type GetMessagesResponse struct {
|
||||
Messages []*model.ChatMessage
|
||||
Messages []*Message
|
||||
ChatState *model.ChatState
|
||||
}
|
||||
|
||||
func (s *storeObject) GetMessages(ctx context.Context, req GetMessagesRequest) (*GetMessagesResponse, error) {
|
||||
var qry anystore.Query
|
||||
if req.AfterOrderId != "" {
|
||||
operator := query.CompOpGt
|
||||
if req.IncludeBoundary {
|
||||
operator = query.CompOpGte
|
||||
}
|
||||
qry = s.collection.Find(query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(operator, req.AfterOrderId)}).Sort(ascOrder).Limit(uint(req.Limit))
|
||||
} else if req.BeforeOrderId != "" {
|
||||
operator := query.CompOpLt
|
||||
if req.IncludeBoundary {
|
||||
operator = query.CompOpLte
|
||||
}
|
||||
qry = s.collection.Find(query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(operator, req.BeforeOrderId)}).Sort(descOrder).Limit(uint(req.Limit))
|
||||
} else {
|
||||
qry = s.collection.Find(nil).Sort(descOrder).Limit(uint(req.Limit))
|
||||
}
|
||||
|
||||
msgs, err := s.queryMessages(ctx, qry)
|
||||
msgs, err := s.repository.getMessages(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query messages: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(msgs, func(i, j int) bool {
|
||||
return msgs[i].OrderId < msgs[j].OrderId
|
||||
})
|
||||
|
||||
return &GetMessagesResponse{
|
||||
Messages: msgs,
|
||||
ChatState: s.subscription.getChatState(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *storeObject) queryMessages(ctx context.Context, query anystore.Query) ([]*model.ChatMessage, error) {
|
||||
arena := s.arenaPool.Get()
|
||||
defer func() {
|
||||
arena.Reset()
|
||||
s.arenaPool.Put(arena)
|
||||
}()
|
||||
|
||||
iter, err := query.Iter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find iter: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var res []*model.ChatMessage
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
|
||||
message := newMessageWrapper(arena, doc.Value()).toModel()
|
||||
res = append(res, message)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *storeObject) AddMessage(ctx context.Context, sessionCtx session.Context, message *model.ChatMessage) (string, error) {
|
||||
func (s *storeObject) AddMessage(ctx context.Context, sessionCtx session.Context, message *Message) (string, error) {
|
||||
arena := s.arenaPool.Get()
|
||||
defer func() {
|
||||
arena.Reset()
|
||||
s.arenaPool.Put(arena)
|
||||
}()
|
||||
message.Read = true
|
||||
obj := marshalModel(arena, message)
|
||||
|
||||
obj := arena.NewObject()
|
||||
message.MarshalAnyenc(obj, arena)
|
||||
|
||||
builder := storestate.Builder{}
|
||||
err := builder.Create(collectionName, storestate.IdFromChange, obj)
|
||||
err := builder.Create(CollectionName, storestate.IdFromChange, obj)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create chat: %w", err)
|
||||
}
|
||||
|
@ -518,7 +245,7 @@ func (s *storeObject) AddMessage(ctx context.Context, sessionCtx session.Context
|
|||
|
||||
func (s *storeObject) DeleteMessage(ctx context.Context, messageId string) error {
|
||||
builder := storestate.Builder{}
|
||||
builder.Delete(collectionName, messageId)
|
||||
builder.Delete(CollectionName, messageId)
|
||||
_, err := s.storeSource.PushStoreChange(ctx, source.PushStoreChangeParams{
|
||||
Changes: builder.ChangeSet,
|
||||
State: s.store,
|
||||
|
@ -530,16 +257,18 @@ func (s *storeObject) DeleteMessage(ctx context.Context, messageId string) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *storeObject) EditMessage(ctx context.Context, messageId string, newMessage *model.ChatMessage) error {
|
||||
func (s *storeObject) EditMessage(ctx context.Context, messageId string, newMessage *Message) error {
|
||||
arena := s.arenaPool.Get()
|
||||
defer func() {
|
||||
arena.Reset()
|
||||
s.arenaPool.Put(arena)
|
||||
}()
|
||||
obj := marshalModel(arena, newMessage)
|
||||
|
||||
obj := arena.NewObject()
|
||||
newMessage.MarshalAnyenc(obj, arena)
|
||||
|
||||
builder := storestate.Builder{}
|
||||
err := builder.Modify(collectionName, messageId, []string{contentKey}, pb.ModifyOp_Set, obj.Get(contentKey))
|
||||
err := builder.Modify(CollectionName, messageId, []string{contentKey}, pb.ModifyOp_Set, obj.Get(contentKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("modify content: %w", err)
|
||||
}
|
||||
|
@ -561,7 +290,7 @@ func (s *storeObject) ToggleMessageReaction(ctx context.Context, messageId strin
|
|||
s.arenaPool.Put(arena)
|
||||
}()
|
||||
|
||||
hasReaction, err := s.hasMyReaction(ctx, arena, messageId, emoji)
|
||||
hasReaction, err := s.repository.hasMyReaction(ctx, s.accountService.AccountID(), messageId, emoji)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check reaction: %w", err)
|
||||
}
|
||||
|
@ -569,12 +298,12 @@ func (s *storeObject) ToggleMessageReaction(ctx context.Context, messageId strin
|
|||
builder := storestate.Builder{}
|
||||
|
||||
if hasReaction {
|
||||
err = builder.Modify(collectionName, messageId, []string{reactionsKey, emoji}, pb.ModifyOp_Pull, arena.NewString(s.accountService.AccountID()))
|
||||
err = builder.Modify(CollectionName, messageId, []string{reactionsKey, emoji}, pb.ModifyOp_Pull, arena.NewString(s.accountService.AccountID()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("modify content: %w", err)
|
||||
}
|
||||
} else {
|
||||
err = builder.Modify(collectionName, messageId, []string{reactionsKey, emoji}, pb.ModifyOp_AddToSet, arena.NewString(s.accountService.AccountID()))
|
||||
err = builder.Modify(CollectionName, messageId, []string{reactionsKey, emoji}, pb.ModifyOp_AddToSet, arena.NewString(s.accountService.AccountID()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("modify content: %w", err)
|
||||
}
|
||||
|
@ -591,57 +320,35 @@ func (s *storeObject) ToggleMessageReaction(ctx context.Context, messageId strin
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *storeObject) hasMyReaction(ctx context.Context, arena *anyenc.Arena, messageId string, emoji string) (bool, error) {
|
||||
doc, err := s.collection.FindId(ctx, messageId)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("find message: %w", err)
|
||||
}
|
||||
|
||||
myIdentity := s.accountService.AccountID()
|
||||
msg := newMessageWrapper(arena, doc.Value())
|
||||
reactions := msg.reactionsToModel()
|
||||
if v, ok := reactions.GetReactions()[emoji]; ok {
|
||||
if slices.Contains(v.GetIds(), myIdentity) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type SubscribeLastMessagesResponse struct {
|
||||
Messages []*model.ChatMessage
|
||||
Messages []*Message
|
||||
ChatState *model.ChatState
|
||||
}
|
||||
|
||||
func (s *storeObject) SubscribeLastMessages(ctx context.Context, subId string, limit int, asyncInit bool) (*SubscribeLastMessagesResponse, error) {
|
||||
txn, err := s.store.NewTx(ctx)
|
||||
txn, err := s.repository.readTx(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init read transaction: %w", err)
|
||||
}
|
||||
defer txn.Commit()
|
||||
|
||||
query := s.collection.Find(nil).Sort(descOrder).Limit(uint(limit))
|
||||
messages, err := s.queryMessages(txn.Context(), query)
|
||||
messages, err := s.repository.getLastMessages(txn.Context(), uint(limit))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query messages: %w", err)
|
||||
}
|
||||
// reverse
|
||||
sort.Slice(messages, func(i, j int) bool {
|
||||
return messages[i].OrderId < messages[j].OrderId
|
||||
})
|
||||
|
||||
s.subscription.subscribe(subId)
|
||||
|
||||
if asyncInit {
|
||||
var previousOrderId string
|
||||
if len(messages) > 0 {
|
||||
previousOrderId, err = getPrevOrderId(txn.Context(), s.collection, messages[0].OrderId)
|
||||
previousOrderId, err = s.repository.getPrevOrderId(txn.Context(), messages[0].OrderId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get previous order id: %w", err)
|
||||
}
|
||||
}
|
||||
for _, message := range messages {
|
||||
s.subscription.add(previousOrderId, message)
|
||||
s.subscription.add(ctx, previousOrderId, message)
|
||||
previousOrderId = message.OrderId
|
||||
}
|
||||
|
||||
|
@ -657,28 +364,6 @@ func (s *storeObject) SubscribeLastMessages(ctx context.Context, subId string, l
|
|||
}
|
||||
}
|
||||
|
||||
func getPrevOrderId(ctx context.Context, coll anystore.Collection, orderId string) (string, error) {
|
||||
iter, err := coll.Find(query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpLt, orderId)}).
|
||||
Sort(descOrder).
|
||||
Limit(1).
|
||||
Iter(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("init iterator: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
if iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read doc: %w", err)
|
||||
}
|
||||
prevOrderId := doc.Value().GetString(orderKey, "id")
|
||||
return prevOrderId, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *storeObject) Unsubscribe(subId string) error {
|
||||
s.subscription.unsubscribe(subId)
|
||||
return nil
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/editor/storestate"
|
||||
"github.com/anyproto/anytype-heart/core/block/source"
|
||||
"github.com/anyproto/anytype-heart/core/block/source/mock_source"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/core/event/mock_event"
|
||||
"github.com/anyproto/anytype-heart/core/session"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
|
@ -25,6 +26,10 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
const (
|
||||
testSpaceId = "spaceId1"
|
||||
)
|
||||
|
||||
type accountServiceStub struct {
|
||||
accountId string
|
||||
}
|
||||
|
@ -48,6 +53,8 @@ type fixture struct {
|
|||
sourceCreator string
|
||||
eventSender *mock_event.MockSender
|
||||
events []*pb.EventMessage
|
||||
|
||||
generateOrderIdFunc func(tx *storestate.StoreStateTx) string
|
||||
}
|
||||
|
||||
const testCreator = "accountId1"
|
||||
|
@ -85,16 +92,21 @@ func newFixture(t *testing.T) *fixture {
|
|||
}).Return().Maybe()
|
||||
|
||||
source := mock_source.NewMockStore(t)
|
||||
source.EXPECT().Id().Return("chatId1")
|
||||
source.EXPECT().SpaceID().Return(testSpaceId)
|
||||
source.EXPECT().ReadStoreDoc(ctx, mock.Anything, mock.Anything).Return(nil)
|
||||
source.EXPECT().PushStoreChange(mock.Anything, mock.Anything).RunAndReturn(fx.applyToStore).Maybe()
|
||||
|
||||
var onSeenHook func([]string)
|
||||
source.EXPECT().SetDiffManagerOnRemoveHook(mock.Anything).Run(func(hook func([]string)) {
|
||||
onSeenHook = hook
|
||||
onSeenHooks := map[string]func([]string){}
|
||||
source.EXPECT().RegisterDiffManager(mock.Anything, mock.Anything).Run(func(name string, hook func([]string)) {
|
||||
onSeenHooks[name] = hook
|
||||
}).Return()
|
||||
|
||||
source.EXPECT().InitDiffManager(mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
|
||||
source.EXPECT().StoreSeenHeads(mock.Anything, mock.Anything).Return(nil).Maybe()
|
||||
|
||||
// Imitate diff manager
|
||||
source.EXPECT().MarkSeenHeads(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, seenHeads []string) error {
|
||||
source.EXPECT().MarkSeenHeads(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, name string, seenHeads []string) error {
|
||||
allMessagesResp, err := fx.GetMessages(ctx, GetMessagesRequest{
|
||||
AfterOrderId: "",
|
||||
IncludeBoundary: true,
|
||||
|
@ -114,7 +126,7 @@ func newFixture(t *testing.T) *fixture {
|
|||
}
|
||||
}
|
||||
|
||||
onSeenHook(collectedHeads)
|
||||
onSeenHooks[name](collectedHeads)
|
||||
|
||||
return nil
|
||||
}).Maybe()
|
||||
|
@ -138,7 +150,7 @@ func TestAddMessage(t *testing.T) {
|
|||
sessionCtx := session.NewContext()
|
||||
|
||||
fx := newFixture(t)
|
||||
fx.eventSender.EXPECT().BroadcastToOtherSessions(mock.Anything, mock.Anything).Return()
|
||||
fx.eventSender.EXPECT().BroadcastToOtherSessions(mock.Anything, mock.Anything).Return().Maybe()
|
||||
|
||||
inputMessage := givenComplexMessage()
|
||||
messageId, err := fx.AddMessage(ctx, sessionCtx, inputMessage)
|
||||
|
@ -153,7 +165,6 @@ func TestAddMessage(t *testing.T) {
|
|||
want := givenComplexMessage()
|
||||
want.Id = messageId
|
||||
want.Creator = testCreator
|
||||
want.Read = true
|
||||
|
||||
got := messagesResp.Messages[0]
|
||||
assertMessagesEqual(t, want, got)
|
||||
|
@ -178,12 +189,13 @@ func TestAddMessage(t *testing.T) {
|
|||
messagesResp, err := fx.GetMessages(ctx, GetMessagesRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, messagesResp.Messages, 1)
|
||||
assert.Equal(t, messagesResp.ChatState.DbTimestamp, messagesResp.Messages[0].AddedAt)
|
||||
assert.Equal(t, messagesResp.ChatState.LastStateId, messagesResp.Messages[0].StateId)
|
||||
|
||||
want := givenComplexMessage()
|
||||
want.Id = messageId
|
||||
want.Creator = testCreator
|
||||
want.Read = false
|
||||
want.MentionRead = false
|
||||
|
||||
got := messagesResp.Messages[0]
|
||||
assertMessagesEqual(t, want, got)
|
||||
|
@ -206,7 +218,7 @@ func TestGetMessages(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
lastMessage := messagesResp.Messages[4]
|
||||
assert.Equal(t, messagesResp.ChatState.DbTimestamp, lastMessage.AddedAt)
|
||||
assert.Equal(t, messagesResp.ChatState.LastStateId, lastMessage.StateId)
|
||||
|
||||
wantTexts := []string{"text 6", "text 7", "text 8", "text 9", "text 10"}
|
||||
for i, msg := range messagesResp.Messages {
|
||||
|
@ -252,7 +264,6 @@ func TestGetMessagesByIds(t *testing.T) {
|
|||
want := givenComplexMessage()
|
||||
want.Id = messageId
|
||||
want.Creator = testCreator
|
||||
want.Read = true
|
||||
got := messages[0]
|
||||
assertMessagesEqual(t, want, got)
|
||||
}
|
||||
|
@ -283,7 +294,6 @@ func TestEditMessage(t *testing.T) {
|
|||
want := editedMessage
|
||||
want.Id = messageId
|
||||
want.Creator = testCreator
|
||||
want.Read = true
|
||||
|
||||
got := messagesResp.Messages[0]
|
||||
assert.True(t, got.ModifiedAt > 0)
|
||||
|
@ -387,46 +397,7 @@ func TestToggleReaction(t *testing.T) {
|
|||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestReadMessages(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
fx.chatHandler.forceNotRead = true
|
||||
const n = 10
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := fx.AddMessage(ctx, nil, givenSimpleMessage(fmt.Sprintf("message %d", i+1)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// All messages forced as not read
|
||||
messagesResp := fx.assertReadStatus(t, ctx, "", "", false)
|
||||
|
||||
err := fx.MarkReadMessages(ctx, "", messagesResp.Messages[2].OrderId, messagesResp.ChatState.DbTimestamp)
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.assertReadStatus(t, ctx, "", messagesResp.Messages[2].OrderId, true)
|
||||
fx.assertReadStatus(t, ctx, messagesResp.Messages[3].OrderId, "", false)
|
||||
}
|
||||
|
||||
func TestMarkMessagesAsNotRead(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
const n = 10
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := fx.AddMessage(ctx, nil, givenSimpleMessage(fmt.Sprintf("message %d", i+1)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// All messages added by myself are read
|
||||
fx.assertReadStatus(t, ctx, "", "", true)
|
||||
|
||||
fx.source.EXPECT().InitDiffManager(mock.Anything, mock.Anything).Return(nil)
|
||||
fx.source.EXPECT().StoreSeenHeads(mock.Anything).Return(nil)
|
||||
err := fx.MarkMessagesAsUnread(ctx, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.assertReadStatus(t, ctx, "", "", false)
|
||||
}
|
||||
|
||||
func (fx *fixture) assertReadStatus(t *testing.T, ctx context.Context, afterOrderId string, beforeOrderId string, isRead bool) *GetMessagesResponse {
|
||||
func (fx *fixture) assertReadStatus(t *testing.T, ctx context.Context, afterOrderId string, beforeOrderId string, isRead bool, isMentionRead bool) *GetMessagesResponse {
|
||||
messageResp, err := fx.GetMessages(ctx, GetMessagesRequest{
|
||||
AfterOrderId: afterOrderId,
|
||||
BeforeOrderId: beforeOrderId,
|
||||
|
@ -437,17 +408,25 @@ func (fx *fixture) assertReadStatus(t *testing.T, ctx context.Context, afterOrde
|
|||
|
||||
for _, m := range messageResp.Messages {
|
||||
assert.Equal(t, isRead, m.Read)
|
||||
assert.Equal(t, isMentionRead, m.MentionRead)
|
||||
}
|
||||
return messageResp
|
||||
}
|
||||
|
||||
func (fx *fixture) generateOrderId(tx *storestate.StoreStateTx) string {
|
||||
if fx.generateOrderIdFunc != nil {
|
||||
return fx.generateOrderIdFunc(tx)
|
||||
}
|
||||
return tx.NextOrder(tx.GetMaxOrder())
|
||||
}
|
||||
|
||||
func (fx *fixture) applyToStore(ctx context.Context, params source.PushStoreChangeParams) (string, error) {
|
||||
changeId := bson.NewObjectId().Hex()
|
||||
tx, err := params.State.NewTx(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new tx: %w", err)
|
||||
}
|
||||
order := tx.NextOrder(tx.GetMaxOrder())
|
||||
order := fx.generateOrderId(tx)
|
||||
err = tx.ApplyChangeSet(storestate.ChangeSet{
|
||||
Id: changeId,
|
||||
Order: order,
|
||||
|
@ -466,71 +445,100 @@ func (fx *fixture) applyToStore(ctx context.Context, params source.PushStoreChan
|
|||
return changeId, nil
|
||||
}
|
||||
|
||||
func givenSimpleMessage(text string) *model.ChatMessage {
|
||||
return &model.ChatMessage{
|
||||
Id: "",
|
||||
OrderId: "",
|
||||
Creator: "",
|
||||
Read: false,
|
||||
Message: &model.ChatMessageMessageContent{
|
||||
Text: text,
|
||||
Style: model.BlockContentText_Paragraph,
|
||||
func givenSimpleMessage(text string) *Message {
|
||||
return &Message{
|
||||
ChatMessage: &model.ChatMessage{
|
||||
Id: "",
|
||||
OrderId: "",
|
||||
Creator: "",
|
||||
Read: true,
|
||||
MentionRead: true,
|
||||
Message: &model.ChatMessageMessageContent{
|
||||
Text: text,
|
||||
Style: model.BlockContentText_Paragraph,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func givenComplexMessage() *model.ChatMessage {
|
||||
return &model.ChatMessage{
|
||||
Id: "",
|
||||
OrderId: "",
|
||||
Creator: "",
|
||||
Read: false,
|
||||
ReplyToMessageId: "replyToMessageId1",
|
||||
Message: &model.ChatMessageMessageContent{
|
||||
Text: "text!",
|
||||
Style: model.BlockContentText_Quote,
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 1,
|
||||
func givenMessageWithMention(text string) *Message {
|
||||
return &Message{
|
||||
ChatMessage: &model.ChatMessage{
|
||||
Id: "",
|
||||
OrderId: "",
|
||||
Creator: "",
|
||||
Read: true,
|
||||
MentionRead: true,
|
||||
Message: &model.ChatMessageMessageContent{
|
||||
Text: text,
|
||||
Style: model.BlockContentText_Paragraph,
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: domain.NewParticipantId(testSpaceId, testCreator),
|
||||
Range: &model.Range{From: 0, To: 1},
|
||||
},
|
||||
Type: model.BlockContentTextMark_Link,
|
||||
Param: "https://example.com",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 2,
|
||||
To: 3,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Italic,
|
||||
},
|
||||
},
|
||||
},
|
||||
Attachments: []*model.ChatMessageAttachment{
|
||||
{
|
||||
Target: "attachmentId1",
|
||||
Type: model.ChatMessageAttachment_IMAGE,
|
||||
},
|
||||
{
|
||||
Target: "attachmentId2",
|
||||
Type: model.ChatMessageAttachment_LINK,
|
||||
},
|
||||
},
|
||||
Reactions: &model.ChatMessageReactions{
|
||||
Reactions: map[string]*model.ChatMessageReactionsIdentityList{
|
||||
"🥰": {
|
||||
Ids: []string{"identity1", "identity2"},
|
||||
},
|
||||
"🤔": {
|
||||
Ids: []string{"identity3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func assertMessagesEqual(t *testing.T, want, got *model.ChatMessage) {
|
||||
func givenComplexMessage() *Message {
|
||||
return &Message{
|
||||
ChatMessage: &model.ChatMessage{
|
||||
Id: "",
|
||||
OrderId: "",
|
||||
Creator: "",
|
||||
Read: true,
|
||||
MentionRead: true,
|
||||
ReplyToMessageId: "replyToMessageId1",
|
||||
Message: &model.ChatMessageMessageContent{
|
||||
Text: "text!",
|
||||
Style: model.BlockContentText_Quote,
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 1,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Link,
|
||||
Param: "https://example.com",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 2,
|
||||
To: 3,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Italic,
|
||||
},
|
||||
},
|
||||
},
|
||||
Attachments: []*model.ChatMessageAttachment{
|
||||
{
|
||||
Target: "attachmentId1",
|
||||
Type: model.ChatMessageAttachment_IMAGE,
|
||||
},
|
||||
{
|
||||
Target: "attachmentId2",
|
||||
Type: model.ChatMessageAttachment_LINK,
|
||||
},
|
||||
},
|
||||
Reactions: &model.ChatMessageReactions{
|
||||
Reactions: map[string]*model.ChatMessageReactionsIdentityList{
|
||||
"🥰": {
|
||||
Ids: []string{"identity1", "identity2"},
|
||||
},
|
||||
"🤔": {
|
||||
Ids: []string{"identity3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func assertMessagesEqual(t *testing.T, want, got *Message) {
|
||||
// Cleanup order id
|
||||
assert.NotEmpty(t, got.OrderId)
|
||||
got.OrderId = ""
|
||||
|
@ -538,8 +546,8 @@ func assertMessagesEqual(t *testing.T, want, got *model.ChatMessage) {
|
|||
assert.NotZero(t, got.CreatedAt)
|
||||
got.CreatedAt = 0
|
||||
|
||||
assert.NotZero(t, got.AddedAt)
|
||||
got.AddedAt = 0
|
||||
assert.NotEmpty(t, got.StateId)
|
||||
got.StateId = ""
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
|
|
@ -1,53 +1,67 @@
|
|||
package chatobject
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-store/anyenc"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
const (
|
||||
creatorKey = "creator"
|
||||
createdAtKey = "createdAt"
|
||||
modifiedAtKey = "modifiedAt"
|
||||
reactionsKey = "reactions"
|
||||
contentKey = "content"
|
||||
readKey = "read"
|
||||
addedKey = "a"
|
||||
orderKey = "_o"
|
||||
creatorKey = "creator"
|
||||
createdAtKey = "createdAt"
|
||||
modifiedAtKey = "modifiedAt"
|
||||
reactionsKey = "reactions"
|
||||
contentKey = "content"
|
||||
readKey = "read"
|
||||
mentionReadKey = "mentionRead"
|
||||
hasMentionKey = "hasMention"
|
||||
stateIdKey = "stateId"
|
||||
orderKey = "_o"
|
||||
)
|
||||
|
||||
type messageWrapper struct {
|
||||
val *anyenc.Value
|
||||
arena *anyenc.Arena
|
||||
type Message struct {
|
||||
*model.ChatMessage
|
||||
|
||||
// CurrentUserMentioned is memoized result of IsCurrentUserMentioned
|
||||
CurrentUserMentioned bool
|
||||
}
|
||||
|
||||
func newMessageWrapper(arena *anyenc.Arena, val *anyenc.Value) *messageWrapper {
|
||||
return &messageWrapper{arena: arena, val: val}
|
||||
}
|
||||
|
||||
func (m *messageWrapper) getCreator() string {
|
||||
return string(m.val.GetStringBytes(creatorKey))
|
||||
}
|
||||
|
||||
func (m *messageWrapper) setCreator(v string) {
|
||||
m.val.Set(creatorKey, m.arena.NewString(v))
|
||||
}
|
||||
|
||||
func (m *messageWrapper) setRead(v bool) {
|
||||
if v {
|
||||
m.val.Set(readKey, m.arena.NewTrue())
|
||||
} else {
|
||||
m.val.Set(readKey, m.arena.NewFalse())
|
||||
func (m *Message) IsCurrentUserMentioned(ctx context.Context, myParticipantId string, myIdentity string, repo *repository) (bool, error) {
|
||||
for _, mark := range m.Message.Marks {
|
||||
if mark.Type == model.BlockContentTextMark_Mention && mark.Param == myParticipantId {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if m.ReplyToMessageId != "" {
|
||||
msgs, err := repo.getMessagesByIds(ctx, []string{m.ReplyToMessageId})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get messages by id: %w", err)
|
||||
}
|
||||
if len(msgs) == 1 {
|
||||
msg := msgs[0]
|
||||
if msg.Creator == myIdentity {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *messageWrapper) setCreatedAt(v int64) {
|
||||
m.val.Set(createdAtKey, m.arena.NewNumberInt(int(v)))
|
||||
func unmarshalMessage(val *anyenc.Value) (*Message, error) {
|
||||
return newMessageWrapper(val).toModel()
|
||||
}
|
||||
|
||||
func (m *messageWrapper) setAddedAt(v int64) {
|
||||
m.val.Set(addedKey, m.arena.NewNumberInt(int(v)))
|
||||
type messageUnmarshaller struct {
|
||||
val *anyenc.Value
|
||||
}
|
||||
|
||||
func newMessageWrapper(val *anyenc.Value) *messageUnmarshaller {
|
||||
return &messageUnmarshaller{val: val}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -84,12 +98,12 @@ func (m *messageWrapper) setAddedAt(v int64) {
|
|||
|
||||
*/
|
||||
|
||||
func marshalModel(arena *anyenc.Arena, msg *model.ChatMessage) *anyenc.Value {
|
||||
func (m *Message) MarshalAnyenc(marshalTo *anyenc.Value, arena *anyenc.Arena) {
|
||||
message := arena.NewObject()
|
||||
message.Set("text", arena.NewString(msg.Message.Text))
|
||||
message.Set("style", arena.NewNumberInt(int(msg.Message.Style)))
|
||||
message.Set("text", arena.NewString(m.Message.Text))
|
||||
message.Set("style", arena.NewNumberInt(int(m.Message.Style)))
|
||||
marks := arena.NewArray()
|
||||
for i, inMark := range msg.Message.Marks {
|
||||
for i, inMark := range m.Message.Marks {
|
||||
mark := arena.NewObject()
|
||||
mark.Set("from", arena.NewNumberInt(int(inMark.Range.From)))
|
||||
mark.Set("to", arena.NewNumberInt(int(inMark.Range.To)))
|
||||
|
@ -102,7 +116,7 @@ func marshalModel(arena *anyenc.Arena, msg *model.ChatMessage) *anyenc.Value {
|
|||
message.Set("marks", marks)
|
||||
|
||||
attachments := arena.NewObject()
|
||||
for i, inAttachment := range msg.Attachments {
|
||||
for i, inAttachment := range m.Attachments {
|
||||
attachment := arena.NewObject()
|
||||
attachment.Set("type", arena.NewNumberInt(int(inAttachment.Type)))
|
||||
attachments.Set(inAttachment.Target, attachment)
|
||||
|
@ -114,7 +128,7 @@ func marshalModel(arena *anyenc.Arena, msg *model.ChatMessage) *anyenc.Value {
|
|||
content.Set("attachments", attachments)
|
||||
|
||||
reactions := arena.NewObject()
|
||||
for emoji, inReaction := range msg.GetReactions().GetReactions() {
|
||||
for emoji, inReaction := range m.GetReactions().GetReactions() {
|
||||
identities := arena.NewArray()
|
||||
for j, identity := range inReaction.Ids {
|
||||
identities.SetArrayItem(j, arena.NewString(identity))
|
||||
|
@ -122,41 +136,48 @@ func marshalModel(arena *anyenc.Arena, msg *model.ChatMessage) *anyenc.Value {
|
|||
reactions.Set(emoji, identities)
|
||||
}
|
||||
|
||||
root := arena.NewObject()
|
||||
root.Set(creatorKey, arena.NewString(msg.Creator))
|
||||
root.Set(createdAtKey, arena.NewNumberInt(int(msg.CreatedAt)))
|
||||
root.Set(modifiedAtKey, arena.NewNumberInt(int(msg.ModifiedAt)))
|
||||
root.Set("replyToMessageId", arena.NewString(msg.ReplyToMessageId))
|
||||
root.Set(contentKey, content)
|
||||
var read *anyenc.Value
|
||||
if msg.Read {
|
||||
read = arena.NewTrue()
|
||||
marshalTo.Set("id", arena.NewString(m.Id))
|
||||
marshalTo.Set(creatorKey, arena.NewString(m.Creator))
|
||||
marshalTo.Set(createdAtKey, arena.NewNumberInt(int(m.CreatedAt)))
|
||||
marshalTo.Set(modifiedAtKey, arena.NewNumberInt(int(m.ModifiedAt)))
|
||||
marshalTo.Set("replyToMessageId", arena.NewString(m.ReplyToMessageId))
|
||||
marshalTo.Set(contentKey, content)
|
||||
marshalTo.Set(readKey, arenaNewBool(arena, m.Read))
|
||||
marshalTo.Set(mentionReadKey, arenaNewBool(arena, m.MentionRead))
|
||||
marshalTo.Set(hasMentionKey, arenaNewBool(arena, m.CurrentUserMentioned))
|
||||
marshalTo.Set(stateIdKey, arena.NewString(m.StateId))
|
||||
marshalTo.Set(reactionsKey, reactions)
|
||||
}
|
||||
|
||||
func arenaNewBool(a *anyenc.Arena, value bool) *anyenc.Value {
|
||||
if value {
|
||||
return a.NewTrue()
|
||||
} else {
|
||||
read = arena.NewFalse()
|
||||
}
|
||||
root.Set(readKey, read)
|
||||
|
||||
root.Set(reactionsKey, reactions)
|
||||
return root
|
||||
}
|
||||
|
||||
func (m *messageWrapper) toModel() *model.ChatMessage {
|
||||
return &model.ChatMessage{
|
||||
Id: string(m.val.GetStringBytes("id")),
|
||||
Creator: string(m.val.GetStringBytes(creatorKey)),
|
||||
CreatedAt: int64(m.val.GetInt(createdAtKey)),
|
||||
ModifiedAt: int64(m.val.GetInt(modifiedAtKey)),
|
||||
AddedAt: int64(m.val.GetInt(addedKey)),
|
||||
OrderId: string(m.val.GetStringBytes("_o", "id")),
|
||||
ReplyToMessageId: string(m.val.GetStringBytes("replyToMessageId")),
|
||||
Message: m.contentToModel(),
|
||||
Read: m.val.GetBool(readKey),
|
||||
Attachments: m.attachmentsToModel(),
|
||||
Reactions: m.reactionsToModel(),
|
||||
return a.NewFalse()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *messageWrapper) contentToModel() *model.ChatMessageMessageContent {
|
||||
func (m *messageUnmarshaller) toModel() (*Message, error) {
|
||||
return &Message{
|
||||
ChatMessage: &model.ChatMessage{
|
||||
Id: string(m.val.GetStringBytes("id")),
|
||||
Creator: string(m.val.GetStringBytes(creatorKey)),
|
||||
CreatedAt: int64(m.val.GetInt(createdAtKey)),
|
||||
ModifiedAt: int64(m.val.GetInt(modifiedAtKey)),
|
||||
StateId: m.val.GetString(stateIdKey),
|
||||
OrderId: string(m.val.GetStringBytes("_o", "id")),
|
||||
ReplyToMessageId: string(m.val.GetStringBytes("replyToMessageId")),
|
||||
Message: m.contentToModel(),
|
||||
Read: m.val.GetBool(readKey),
|
||||
MentionRead: m.val.GetBool(mentionReadKey),
|
||||
Attachments: m.attachmentsToModel(),
|
||||
Reactions: m.reactionsToModel(),
|
||||
},
|
||||
CurrentUserMentioned: m.val.GetBool(hasMentionKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *messageUnmarshaller) contentToModel() *model.ChatMessageMessageContent {
|
||||
inMarks := m.val.GetArray(contentKey, "message", "marks")
|
||||
marks := make([]*model.BlockContentTextMark, 0, len(inMarks))
|
||||
for _, inMark := range inMarks {
|
||||
|
@ -177,7 +198,7 @@ func (m *messageWrapper) contentToModel() *model.ChatMessageMessageContent {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *messageWrapper) attachmentsToModel() []*model.ChatMessageAttachment {
|
||||
func (m *messageUnmarshaller) attachmentsToModel() []*model.ChatMessageAttachment {
|
||||
inAttachments := m.val.GetObject(contentKey, "attachments")
|
||||
var attachments []*model.ChatMessageAttachment
|
||||
if inAttachments != nil {
|
||||
|
@ -192,7 +213,7 @@ func (m *messageWrapper) attachmentsToModel() []*model.ChatMessageAttachment {
|
|||
return attachments
|
||||
}
|
||||
|
||||
func (m *messageWrapper) reactionsToModel() *model.ChatMessageReactions {
|
||||
func (m *messageUnmarshaller) reactionsToModel() *model.ChatMessageReactions {
|
||||
inReactions := m.val.GetObject(reactionsKey)
|
||||
reactions := &model.ChatMessageReactions{
|
||||
Reactions: map[string]*model.ChatMessageReactionsIdentityList{},
|
||||
|
|
240
core/block/editor/chatobject/reading.go
Normal file
240
core/block/editor/chatobject/reading.go
Normal file
|
@ -0,0 +1,240 @@
|
|||
package chatobject
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-store/anyenc"
|
||||
"github.com/anyproto/any-store/query"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
type CounterType int
|
||||
|
||||
const (
|
||||
CounterTypeMessage = CounterType(iota)
|
||||
CounterTypeMention
|
||||
)
|
||||
|
||||
type readHandler interface {
|
||||
getUnreadFilter() query.Filter
|
||||
getMessagesFilter() query.Filter
|
||||
getDiffManagerName() string
|
||||
getReadKey() string
|
||||
readModifier(value bool) query.Modifier
|
||||
|
||||
readMessages(newOldestOrderId string, idsModified []string)
|
||||
unreadMessages(newOldestOrderId string, lastStateId string, msgIds []string)
|
||||
}
|
||||
|
||||
type readMessagesHandler struct {
|
||||
subscription *subscription
|
||||
}
|
||||
|
||||
func (h *readMessagesHandler) getUnreadFilter() query.Filter {
|
||||
return query.Not{
|
||||
Filter: query.Key{Path: []string{readKey}, Filter: query.NewComp(query.CompOpEq, true)},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *readMessagesHandler) getMessagesFilter() query.Filter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *readMessagesHandler) getDiffManagerName() string {
|
||||
return diffManagerMessages
|
||||
}
|
||||
|
||||
func (h *readMessagesHandler) getReadKey() string {
|
||||
return readKey
|
||||
}
|
||||
|
||||
func (h *readMessagesHandler) readMessages(newOldestOrderId string, idsModified []string) {
|
||||
h.subscription.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
state.Messages.OldestOrderId = newOldestOrderId
|
||||
return state
|
||||
})
|
||||
h.subscription.updateMessageRead(idsModified, true)
|
||||
}
|
||||
|
||||
func (h *readMessagesHandler) unreadMessages(newOldestOrderId string, lastStateId string, msgIds []string) {
|
||||
h.subscription.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
state.Messages.OldestOrderId = newOldestOrderId
|
||||
state.LastStateId = lastStateId
|
||||
return state
|
||||
})
|
||||
h.subscription.updateMessageRead(msgIds, false)
|
||||
}
|
||||
|
||||
func (h *readMessagesHandler) readModifier(value bool) query.Modifier {
|
||||
return query.ModifyFunc(func(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error) {
|
||||
oldValue := v.GetBool(h.getReadKey())
|
||||
if oldValue != value {
|
||||
v.Set(h.getReadKey(), arenaNewBool(a, value))
|
||||
return v, true, nil
|
||||
}
|
||||
return v, false, nil
|
||||
})
|
||||
}
|
||||
|
||||
type readMentionsHandler struct {
|
||||
subscription *subscription
|
||||
}
|
||||
|
||||
func (h *readMentionsHandler) getUnreadFilter() query.Filter {
|
||||
return query.And{
|
||||
query.Key{Path: []string{hasMentionKey}, Filter: query.NewComp(query.CompOpEq, true)},
|
||||
query.Key{Path: []string{mentionReadKey}, Filter: query.NewComp(query.CompOpEq, false)},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *readMentionsHandler) getMessagesFilter() query.Filter {
|
||||
return query.Key{Path: []string{hasMentionKey}, Filter: query.NewComp(query.CompOpEq, true)}
|
||||
}
|
||||
|
||||
func (h *readMentionsHandler) getDiffManagerName() string {
|
||||
return diffManagerMentions
|
||||
}
|
||||
|
||||
func (h *readMentionsHandler) getReadKey() string {
|
||||
return mentionReadKey
|
||||
}
|
||||
|
||||
func (h *readMentionsHandler) readMessages(newOldestOrderId string, idsModified []string) {
|
||||
h.subscription.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
state.Mentions.OldestOrderId = newOldestOrderId
|
||||
return state
|
||||
})
|
||||
h.subscription.updateMentionRead(idsModified, true)
|
||||
}
|
||||
|
||||
func (h *readMentionsHandler) unreadMessages(newOldestOrderId string, lastStateId string, msgIds []string) {
|
||||
h.subscription.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
state.Mentions.OldestOrderId = newOldestOrderId
|
||||
state.LastStateId = lastStateId
|
||||
return state
|
||||
})
|
||||
h.subscription.updateMentionRead(msgIds, false)
|
||||
}
|
||||
|
||||
func (h *readMentionsHandler) readModifier(value bool) query.Modifier {
|
||||
return query.ModifyFunc(func(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error) {
|
||||
if v.GetBool(hasMentionKey) {
|
||||
oldValue := v.GetBool(h.getReadKey())
|
||||
if oldValue != value {
|
||||
v.Set(h.getReadKey(), arenaNewBool(a, value))
|
||||
return v, true, nil
|
||||
}
|
||||
}
|
||||
return v, false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func newReadHandler(counterType CounterType, subscription *subscription) readHandler {
|
||||
switch counterType {
|
||||
case CounterTypeMessage:
|
||||
return &readMessagesHandler{subscription: subscription}
|
||||
case CounterTypeMention:
|
||||
return &readMentionsHandler{subscription: subscription}
|
||||
default:
|
||||
panic("unknown counter type")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storeObject) MarkReadMessages(ctx context.Context, afterOrderId, beforeOrderId string, lastStateId string, counterType CounterType) error {
|
||||
handler := newReadHandler(counterType, s.subscription)
|
||||
// 1. select all messages with orderId < beforeOrderId and addedTime < lastDbState
|
||||
// 2. use the last(by orderId) message id as lastHead
|
||||
// 3. update the MarkSeenHeads
|
||||
// 2. mark messages as read in the DB
|
||||
|
||||
msgs, err := s.repository.getUnreadMessageIdsInRange(ctx, afterOrderId, beforeOrderId, lastStateId, handler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get message: %w", err)
|
||||
}
|
||||
|
||||
// mark the whole tree as seen from the current message
|
||||
return s.storeSource.MarkSeenHeads(ctx, handler.getDiffManagerName(), msgs)
|
||||
}
|
||||
|
||||
func (s *storeObject) MarkMessagesAsUnread(ctx context.Context, afterOrderId string, counterType CounterType) error {
|
||||
txn, err := s.repository.writeTx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create tx: %w", err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
handler := newReadHandler(counterType, s.subscription)
|
||||
messageIds, err := s.repository.getReadMessagesAfter(txn.Context(), afterOrderId, handler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get read messages: %w", err)
|
||||
}
|
||||
|
||||
if len(messageIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
idsModified := s.repository.setReadFlag(txn.Context(), s.Id(), messageIds, handler, false)
|
||||
if len(idsModified) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
newOldestOrderId, err := s.repository.getOldestOrderId(txn.Context(), handler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get oldest order id: %w", err)
|
||||
}
|
||||
|
||||
lastAdded, err := s.repository.getLastStateId(txn.Context())
|
||||
if err != nil {
|
||||
return fmt.Errorf("get last added date: %w", err)
|
||||
}
|
||||
|
||||
handler.unreadMessages(newOldestOrderId, lastAdded, idsModified)
|
||||
s.subscription.flush()
|
||||
|
||||
seenHeads, err := s.seenHeadsCollector.collectSeenHeads(ctx, afterOrderId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get seen heads: %w", err)
|
||||
}
|
||||
err = s.storeSource.InitDiffManager(ctx, diffManagerMessages, seenHeads)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init diff manager: %w", err)
|
||||
}
|
||||
err = s.storeSource.StoreSeenHeads(txn.Context(), diffManagerMessages)
|
||||
if err != nil {
|
||||
return fmt.Errorf("store seen heads: %w", err)
|
||||
}
|
||||
|
||||
return txn.Commit()
|
||||
}
|
||||
|
||||
func (s *storeObject) markReadMessages(changeIds []string, handler readHandler) error {
|
||||
if len(changeIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
txn, err := s.repository.writeTx(s.componentCtx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("start write tx: %w", err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
idsModified := s.repository.setReadFlag(txn.Context(), s.Id(), changeIds, handler, true)
|
||||
|
||||
if len(idsModified) > 0 {
|
||||
newOldestOrderId, err := s.repository.getOldestOrderId(txn.Context(), handler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get oldest order id: %w", err)
|
||||
}
|
||||
|
||||
err = txn.Commit()
|
||||
if err != nil {
|
||||
return fmt.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
handler.readMessages(newOldestOrderId, idsModified)
|
||||
s.subscription.flush()
|
||||
}
|
||||
return nil
|
||||
}
|
166
core/block/editor/chatobject/reading_test.go
Normal file
166
core/block/editor/chatobject/reading_test.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package chatobject
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/storestate"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
func TestReadMessages(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
fx.chatHandler.forceNotRead = true
|
||||
|
||||
const n = 10
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := fx.AddMessage(ctx, nil, givenSimpleMessage(fmt.Sprintf("message %d", i+1)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// All messages forced as not read
|
||||
messagesResp := fx.assertReadStatus(t, ctx, "", "", false, false)
|
||||
|
||||
err := fx.MarkReadMessages(ctx, "", messagesResp.Messages[2].OrderId, messagesResp.ChatState.LastStateId, CounterTypeMessage)
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.assertReadStatus(t, ctx, "", messagesResp.Messages[2].OrderId, true, false)
|
||||
fx.assertReadStatus(t, ctx, messagesResp.Messages[3].OrderId, "", false, false)
|
||||
}
|
||||
|
||||
func TestReadMessagesLoadedInBackground(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
fx.chatHandler.forceNotRead = true
|
||||
|
||||
firstMessageId, err := fx.AddMessage(ctx, nil, givenSimpleMessage(fmt.Sprintf("first message")))
|
||||
require.NoError(t, err)
|
||||
|
||||
firstMessage, err := fx.GetMessageById(ctx, firstMessageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.generateOrderIdFunc = func(tx *storestate.StoreStateTx) string {
|
||||
prev, err := storestate.LexId.NextBefore("", firstMessage.OrderId)
|
||||
require.NoError(t, err)
|
||||
return prev
|
||||
}
|
||||
|
||||
// The second messages is before the first one
|
||||
secondMessageId, err := fx.AddMessage(ctx, nil, givenSimpleMessage(fmt.Sprintf("second message")))
|
||||
require.NoError(t, err)
|
||||
|
||||
secondMessage, err := fx.GetMessageById(ctx, secondMessageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fx.MarkReadMessages(ctx, "", firstMessage.OrderId, firstMessage.StateId, CounterTypeMessage)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotResponse, err := fx.GetMessages(ctx, GetMessagesRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
firstMessage.Read = true
|
||||
wantMessages := []*Message{
|
||||
secondMessage,
|
||||
firstMessage,
|
||||
}
|
||||
|
||||
wantResponse := &GetMessagesResponse{
|
||||
Messages: wantMessages,
|
||||
ChatState: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
Counter: 1,
|
||||
OldestOrderId: secondMessage.OrderId,
|
||||
},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: secondMessage.StateId,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantResponse, gotResponse)
|
||||
}
|
||||
|
||||
func TestReadMentions(t *testing.T) {
|
||||
t.Run("mentioned directly in marks", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
fx.chatHandler.forceNotRead = true
|
||||
const n = 10
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := fx.AddMessage(ctx, nil, givenMessageWithMention(fmt.Sprintf("message %d", i+1)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// All messages forced as not read
|
||||
messagesResp := fx.assertReadStatus(t, ctx, "", "", false, false)
|
||||
|
||||
err := fx.MarkReadMessages(ctx, "", messagesResp.Messages[2].OrderId, messagesResp.ChatState.LastStateId, CounterTypeMention)
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.assertReadStatus(t, ctx, "", messagesResp.Messages[2].OrderId, false, true)
|
||||
fx.assertReadStatus(t, ctx, messagesResp.Messages[3].OrderId, "", false, false)
|
||||
})
|
||||
|
||||
t.Run("author of replied message", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
fx.chatHandler.forceNotRead = true
|
||||
|
||||
firstMessageId, err := fx.AddMessage(ctx, nil, givenSimpleMessage("message to reply to"))
|
||||
require.NoError(t, err)
|
||||
|
||||
secondMessageInput := givenSimpleMessage("a reply")
|
||||
secondMessageInput.ReplyToMessageId = firstMessageId
|
||||
|
||||
secondMessageId, err := fx.AddMessage(ctx, nil, secondMessageInput)
|
||||
require.NoError(t, err)
|
||||
|
||||
secondMessage, err := fx.GetMessageById(ctx, secondMessageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
// All messages forced as not read
|
||||
messagesResp := fx.assertReadStatus(t, ctx, "", "", false, false)
|
||||
|
||||
err = fx.MarkReadMessages(ctx, "", secondMessage.OrderId, messagesResp.ChatState.LastStateId, CounterTypeMention)
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.assertReadStatus(t, ctx, secondMessage.OrderId, secondMessage.OrderId, false, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMarkMessagesAsNotRead(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
const n = 10
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := fx.AddMessage(ctx, nil, givenSimpleMessage(fmt.Sprintf("message %d", i+1)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// All messages added by myself are read
|
||||
fx.assertReadStatus(t, ctx, "", "", true, true)
|
||||
|
||||
err := fx.MarkMessagesAsUnread(ctx, "", CounterTypeMessage)
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.assertReadStatus(t, ctx, "", "", false, true)
|
||||
}
|
||||
|
||||
func TestMarkMentionsAsNotRead(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
const n = 10
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := fx.AddMessage(ctx, nil, givenMessageWithMention(fmt.Sprintf("message %d", i+1)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// All messages added by myself are read
|
||||
fx.assertReadStatus(t, ctx, "", "", true, true)
|
||||
|
||||
err := fx.MarkMessagesAsUnread(ctx, "", CounterTypeMention)
|
||||
require.NoError(t, err)
|
||||
|
||||
fx.assertReadStatus(t, ctx, "", "", true, false)
|
||||
}
|
331
core/block/editor/chatobject/repository.go
Normal file
331
core/block/editor/chatobject/repository.go
Normal file
|
@ -0,0 +1,331 @@
|
|||
package chatobject
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
anystore "github.com/anyproto/any-store"
|
||||
"github.com/anyproto/any-store/anyenc"
|
||||
"github.com/anyproto/any-store/query"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
type repository struct {
|
||||
collection anystore.Collection
|
||||
arenaPool *anyenc.ArenaPool
|
||||
}
|
||||
|
||||
func (s *repository) writeTx(ctx context.Context) (anystore.WriteTx, error) {
|
||||
return s.collection.WriteTx(ctx)
|
||||
}
|
||||
|
||||
func (s *repository) readTx(ctx context.Context) (anystore.ReadTx, error) {
|
||||
return s.collection.ReadTx(ctx)
|
||||
}
|
||||
|
||||
func (s *repository) getLastStateId(ctx context.Context) (string, error) {
|
||||
lastAddedDate := s.collection.Find(nil).Sort(descStateId).Limit(1)
|
||||
iter, err := lastAddedDate.Iter(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find last added date: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
msg, err := unmarshalMessage(doc.Value())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshal message: %w", err)
|
||||
}
|
||||
return msg.StateId, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *repository) getPrevOrderId(ctx context.Context, orderId string) (string, error) {
|
||||
iter, err := s.collection.Find(query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpLt, orderId)}).
|
||||
Sort(descOrder).
|
||||
Limit(1).
|
||||
Iter(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("init iterator: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
if iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read doc: %w", err)
|
||||
}
|
||||
prevOrderId := doc.Value().GetString(orderKey, "id")
|
||||
return prevOrderId, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// initialChatState returns the initial chat state for the chat object from the DB
|
||||
func (s *repository) loadChatState(ctx context.Context) (*model.ChatState, error) {
|
||||
txn, err := s.readTx(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("start read tx: %w", err)
|
||||
}
|
||||
defer txn.Commit()
|
||||
|
||||
messagesState, err := s.loadChatStateByType(txn.Context(), CounterTypeMessage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get messages state: %w", err)
|
||||
}
|
||||
mentionsState, err := s.loadChatStateByType(txn.Context(), CounterTypeMention)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get mentions state: %w", err)
|
||||
}
|
||||
|
||||
lastStateId, err := s.getLastStateId(txn.Context())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get last added date: %w", err)
|
||||
}
|
||||
|
||||
return &model.ChatState{
|
||||
Messages: messagesState,
|
||||
Mentions: mentionsState,
|
||||
LastStateId: lastStateId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *repository) loadChatStateByType(ctx context.Context, counterType CounterType) (*model.ChatStateUnreadState, error) {
|
||||
opts := newReadHandler(counterType, nil)
|
||||
|
||||
oldestOrderId, err := s.getOldestOrderId(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get oldest order id: %w", err)
|
||||
}
|
||||
|
||||
count, err := s.countUnreadMessages(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("update messages: %w", err)
|
||||
}
|
||||
|
||||
return &model.ChatStateUnreadState{
|
||||
OldestOrderId: oldestOrderId,
|
||||
Counter: int32(count),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *repository) getOldestOrderId(ctx context.Context, handler readHandler) (string, error) {
|
||||
unreadQuery := s.collection.Find(handler.getUnreadFilter()).Sort(ascOrder)
|
||||
|
||||
iter, err := unreadQuery.Limit(1).Iter(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("init iter: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
orders := doc.Value().GetObject(orderKey)
|
||||
if orders != nil {
|
||||
return orders.Get("id").GetString(), nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *repository) countUnreadMessages(ctx context.Context, handler readHandler) (int, error) {
|
||||
unreadQuery := s.collection.Find(handler.getUnreadFilter())
|
||||
|
||||
return unreadQuery.Count(ctx)
|
||||
}
|
||||
|
||||
func (s *repository) getReadMessagesAfter(ctx context.Context, afterOrderId string, handler readHandler) ([]string, error) {
|
||||
filter := query.And{
|
||||
query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpGte, afterOrderId)},
|
||||
query.Key{Path: []string{handler.getReadKey()}, Filter: query.NewComp(query.CompOpEq, true)},
|
||||
}
|
||||
if handler.getMessagesFilter() != nil {
|
||||
filter = append(filter, handler.getMessagesFilter())
|
||||
}
|
||||
|
||||
iter, err := s.collection.Find(filter).Iter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init iterator: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var msgIds []string
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
msgIds = append(msgIds, doc.Value().GetString("id"))
|
||||
}
|
||||
return msgIds, iter.Err()
|
||||
}
|
||||
|
||||
func (s *repository) getUnreadMessageIdsInRange(ctx context.Context, afterOrderId, beforeOrderId string, lastStateId string, handler readHandler) ([]string, error) {
|
||||
qry := query.And{
|
||||
query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpGte, afterOrderId)},
|
||||
query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(query.CompOpLte, beforeOrderId)},
|
||||
query.Or{
|
||||
query.Not{query.Key{Path: []string{stateIdKey}, Filter: query.Exists{}}},
|
||||
query.Key{Path: []string{stateIdKey}, Filter: query.NewComp(query.CompOpLte, lastStateId)},
|
||||
},
|
||||
handler.getUnreadFilter(),
|
||||
}
|
||||
iter, err := s.collection.Find(qry).Iter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find id: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var msgIds []string
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
msgIds = append(msgIds, doc.Value().GetString("id"))
|
||||
}
|
||||
return msgIds, iter.Err()
|
||||
}
|
||||
|
||||
func (r *repository) setReadFlag(ctx context.Context, chatObjectId string, msgIds []string, handler readHandler, value bool) []string {
|
||||
var idsModified []string
|
||||
for _, id := range msgIds {
|
||||
if id == chatObjectId {
|
||||
// skip tree root
|
||||
continue
|
||||
}
|
||||
res, err := r.collection.UpdateId(ctx, id, handler.readModifier(value))
|
||||
// Not all changes are messages, skip them
|
||||
if errors.Is(err, anystore.ErrDocNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("markReadMessages: update message", zap.Error(err), zap.String("changeId", id), zap.String("chatObjectId", chatObjectId))
|
||||
continue
|
||||
}
|
||||
if res.Modified > 0 {
|
||||
idsModified = append(idsModified, id)
|
||||
}
|
||||
}
|
||||
return idsModified
|
||||
}
|
||||
|
||||
func (s *repository) getMessages(ctx context.Context, req GetMessagesRequest) ([]*Message, error) {
|
||||
var qry anystore.Query
|
||||
if req.AfterOrderId != "" {
|
||||
operator := query.CompOpGt
|
||||
if req.IncludeBoundary {
|
||||
operator = query.CompOpGte
|
||||
}
|
||||
qry = s.collection.Find(query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(operator, req.AfterOrderId)}).Sort(ascOrder).Limit(uint(req.Limit))
|
||||
} else if req.BeforeOrderId != "" {
|
||||
operator := query.CompOpLt
|
||||
if req.IncludeBoundary {
|
||||
operator = query.CompOpLte
|
||||
}
|
||||
qry = s.collection.Find(query.Key{Path: []string{orderKey, "id"}, Filter: query.NewComp(operator, req.BeforeOrderId)}).Sort(descOrder).Limit(uint(req.Limit))
|
||||
} else {
|
||||
qry = s.collection.Find(nil).Sort(descOrder).Limit(uint(req.Limit))
|
||||
}
|
||||
|
||||
msgs, err := s.queryMessages(ctx, qry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query messages: %w", err)
|
||||
}
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
func (s *repository) queryMessages(ctx context.Context, query anystore.Query) ([]*Message, error) {
|
||||
arena := s.arenaPool.Get()
|
||||
defer func() {
|
||||
arena.Reset()
|
||||
s.arenaPool.Put(arena)
|
||||
}()
|
||||
|
||||
iter, err := query.Iter(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find iter: %w", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var res []*Message
|
||||
for iter.Next() {
|
||||
doc, err := iter.Doc()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get doc: %w", err)
|
||||
}
|
||||
|
||||
msg, err := unmarshalMessage(doc.Value())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal message: %w", err)
|
||||
}
|
||||
res = append(res, msg)
|
||||
}
|
||||
// reverse
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
return res[i].OrderId < res[j].OrderId
|
||||
})
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *repository) hasMyReaction(ctx context.Context, myIdentity string, messageId string, emoji string) (bool, error) {
|
||||
doc, err := s.collection.FindId(ctx, messageId)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("find message: %w", err)
|
||||
}
|
||||
|
||||
msg, err := unmarshalMessage(doc.Value())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unmarshal message: %w", err)
|
||||
}
|
||||
if v, ok := msg.GetReactions().GetReactions()[emoji]; ok {
|
||||
if slices.Contains(v.GetIds(), myIdentity) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *repository) getMessagesByIds(ctx context.Context, messageIds []string) ([]*Message, error) {
|
||||
txn, err := s.readTx(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("start read tx: %w", err)
|
||||
}
|
||||
defer txn.Commit()
|
||||
|
||||
messages := make([]*Message, 0, len(messageIds))
|
||||
for _, messageId := range messageIds {
|
||||
obj, err := s.collection.FindId(txn.Context(), messageId)
|
||||
if errors.Is(err, anystore.ErrDocNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Join(txn.Commit(), fmt.Errorf("find id: %w", err))
|
||||
}
|
||||
msg, err := unmarshalMessage(obj.Value())
|
||||
if err != nil {
|
||||
return nil, errors.Join(txn.Commit(), fmt.Errorf("unmarshal message: %w", err))
|
||||
}
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
return messages, txn.Commit()
|
||||
}
|
||||
|
||||
func (s *repository) getLastMessages(ctx context.Context, limit uint) ([]*Message, error) {
|
||||
qry := s.collection.Find(nil).Sort(descOrder).Limit(limit)
|
||||
return s.queryMessages(ctx, qry)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package chatobject
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
|
@ -19,28 +20,40 @@ import (
|
|||
const LastMessageSubscriptionId = "lastMessage"
|
||||
|
||||
type subscription struct {
|
||||
spaceId string
|
||||
chatId string
|
||||
eventSender event.Sender
|
||||
spaceIndex spaceindex.Store
|
||||
componentCtx context.Context
|
||||
|
||||
spaceId string
|
||||
chatId string
|
||||
myIdentity string
|
||||
myParticipantId string
|
||||
|
||||
sessionContext session.Context
|
||||
eventsBuffer []*pb.EventMessage
|
||||
|
||||
eventsBuffer []*pb.EventMessage
|
||||
identityCache *expirable.LRU[string, *domain.Details]
|
||||
ids []string
|
||||
|
||||
chatState *model.ChatState
|
||||
needReloadState bool
|
||||
chatStateUpdated bool
|
||||
|
||||
// Deps
|
||||
spaceIndex spaceindex.Store
|
||||
eventSender event.Sender
|
||||
repository *repository
|
||||
}
|
||||
|
||||
func newSubscription(spaceId string, chatId string, eventSender event.Sender, spaceIndex spaceindex.Store) *subscription {
|
||||
func (s *storeObject) newSubscription(fullId domain.FullID, myIdentity string, myParticipantId string) *subscription {
|
||||
return &subscription{
|
||||
spaceId: spaceId,
|
||||
chatId: chatId,
|
||||
eventSender: eventSender,
|
||||
spaceIndex: spaceIndex,
|
||||
identityCache: expirable.NewLRU[string, *domain.Details](50, nil, time.Minute),
|
||||
componentCtx: s.componentCtx,
|
||||
spaceId: fullId.SpaceID,
|
||||
chatId: fullId.ObjectID,
|
||||
eventSender: s.eventSender,
|
||||
spaceIndex: s.spaceIndex,
|
||||
myIdentity: myIdentity,
|
||||
myParticipantId: myParticipantId,
|
||||
identityCache: expirable.NewLRU[string, *domain.Details](50, nil, time.Minute),
|
||||
repository: s.repository,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,20 +84,43 @@ func (s *subscription) setSessionContext(ctx session.Context) {
|
|||
s.sessionContext = ctx
|
||||
}
|
||||
|
||||
func (s *subscription) loadChatState(ctx context.Context) error {
|
||||
state, err := s.repository.loadChatState(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.chatState = state
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *subscription) getChatState() *model.ChatState {
|
||||
return copyChatState(s.chatState)
|
||||
}
|
||||
|
||||
func (s *subscription) updateChatState(updater func(*model.ChatState)) {
|
||||
updater(s.chatState)
|
||||
func (s *subscription) updateChatState(updater func(*model.ChatState) *model.ChatState) {
|
||||
s.chatState = updater(s.chatState)
|
||||
s.chatStateUpdated = true
|
||||
}
|
||||
|
||||
// flush is called after commiting changes
|
||||
func (s *subscription) flush() {
|
||||
if !s.canSend() {
|
||||
return
|
||||
}
|
||||
|
||||
// Reload ChatState after commit
|
||||
if s.needReloadState {
|
||||
s.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
newState, err := s.repository.loadChatState(s.componentCtx)
|
||||
if err != nil {
|
||||
log.Error("failed to reload chat state", zap.Error(err))
|
||||
return state
|
||||
}
|
||||
return newState
|
||||
})
|
||||
s.needReloadState = false
|
||||
}
|
||||
|
||||
events := slices.Clone(s.eventsBuffer)
|
||||
s.eventsBuffer = s.eventsBuffer[:0]
|
||||
|
||||
|
@ -123,18 +159,30 @@ func (s *subscription) getIdentityDetails(identity string) (*domain.Details, err
|
|||
return details, nil
|
||||
}
|
||||
|
||||
func (s *subscription) add(prevOrderId string, message *model.ChatMessage) {
|
||||
|
||||
s.updateChatState(func(state *model.ChatState) {
|
||||
func (s *subscription) add(ctx context.Context, prevOrderId string, message *Message) {
|
||||
s.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
if !message.Read {
|
||||
if message.OrderId < state.Messages.OldestOrderId {
|
||||
if message.OrderId < state.Messages.OldestOrderId || state.Messages.OldestOrderId == "" {
|
||||
state.Messages.OldestOrderId = message.OrderId
|
||||
}
|
||||
state.Messages.Counter++
|
||||
|
||||
isMentioned, err := message.IsCurrentUserMentioned(ctx, s.myParticipantId, s.myIdentity, s.repository)
|
||||
if err != nil {
|
||||
log.Error("subscription add: check if the current user is mentioned", zap.Error(err))
|
||||
}
|
||||
if isMentioned {
|
||||
state.Mentions.Counter++
|
||||
if message.OrderId < state.Mentions.OldestOrderId || state.Mentions.OldestOrderId == "" {
|
||||
state.Mentions.OldestOrderId = message.OrderId
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if message.AddedAt > state.DbTimestamp {
|
||||
state.DbTimestamp = message.AddedAt
|
||||
if message.StateId > state.LastStateId {
|
||||
state.LastStateId = message.StateId
|
||||
}
|
||||
return state
|
||||
})
|
||||
|
||||
if !s.canSend() {
|
||||
|
@ -143,7 +191,7 @@ func (s *subscription) add(prevOrderId string, message *model.ChatMessage) {
|
|||
|
||||
ev := &pb.EventChatAdd{
|
||||
Id: message.Id,
|
||||
Message: message,
|
||||
Message: message.ChatMessage,
|
||||
OrderId: message.OrderId,
|
||||
AfterOrderId: prevOrderId,
|
||||
SubIds: slices.Clone(s.ids),
|
||||
|
@ -153,7 +201,7 @@ func (s *subscription) add(prevOrderId string, message *model.ChatMessage) {
|
|||
identityDetails, err := s.getIdentityDetails(message.Creator)
|
||||
if err != nil {
|
||||
log.Error("get identity details", zap.Error(err))
|
||||
} else {
|
||||
} else if identityDetails.Len() > 0 {
|
||||
ev.Dependencies = append(ev.Dependencies, identityDetails.ToProto())
|
||||
}
|
||||
|
||||
|
@ -161,7 +209,7 @@ func (s *subscription) add(prevOrderId string, message *model.ChatMessage) {
|
|||
attachmentDetails, err := s.spaceIndex.GetDetails(attachment.Target)
|
||||
if err != nil {
|
||||
log.Error("get attachment details", zap.Error(err))
|
||||
} else {
|
||||
} else if attachmentDetails.Len() > 0 {
|
||||
ev.Dependencies = append(ev.Dependencies, attachmentDetails.ToProto())
|
||||
}
|
||||
}
|
||||
|
@ -179,15 +227,18 @@ func (s *subscription) delete(messageId string) {
|
|||
s.eventsBuffer = append(s.eventsBuffer, event.NewMessage(s.spaceId, &pb.EventMessageValueOfChatDelete{
|
||||
ChatDelete: ev,
|
||||
}))
|
||||
|
||||
// We can't reload chat state here because Delete operation hasn't been commited yet
|
||||
s.needReloadState = true
|
||||
}
|
||||
|
||||
func (s *subscription) updateFull(message *model.ChatMessage) {
|
||||
func (s *subscription) updateFull(message *Message) {
|
||||
if !s.canSend() {
|
||||
return
|
||||
}
|
||||
ev := &pb.EventChatUpdate{
|
||||
Id: message.Id,
|
||||
Message: message,
|
||||
Message: message.ChatMessage,
|
||||
SubIds: slices.Clone(s.ids),
|
||||
}
|
||||
s.eventsBuffer = append(s.eventsBuffer, event.NewMessage(s.spaceId, &pb.EventMessageValueOfChatUpdate{
|
||||
|
@ -195,7 +246,7 @@ func (s *subscription) updateFull(message *model.ChatMessage) {
|
|||
}))
|
||||
}
|
||||
|
||||
func (s *subscription) updateReactions(message *model.ChatMessage) {
|
||||
func (s *subscription) updateReactions(message *Message) {
|
||||
if !s.canSend() {
|
||||
return
|
||||
}
|
||||
|
@ -209,22 +260,45 @@ func (s *subscription) updateReactions(message *model.ChatMessage) {
|
|||
}))
|
||||
}
|
||||
|
||||
// updateReadStatus updates the read status of the messages with the given ids
|
||||
// updateMessageRead updates the read status of the messages with the given ids
|
||||
// read ids should ONLY contain ids if they were actually modified in the DB
|
||||
func (s *subscription) updateReadStatus(ids []string, read bool) {
|
||||
s.updateChatState(func(state *model.ChatState) {
|
||||
func (s *subscription) updateMessageRead(ids []string, read bool) {
|
||||
s.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
if read {
|
||||
state.Messages.Counter -= int32(len(ids))
|
||||
} else {
|
||||
state.Messages.Counter += int32(len(ids))
|
||||
}
|
||||
return state
|
||||
})
|
||||
|
||||
if !s.canSend() {
|
||||
return
|
||||
}
|
||||
s.eventsBuffer = append(s.eventsBuffer, event.NewMessage(s.spaceId, &pb.EventMessageValueOfChatUpdateReadStatus{
|
||||
ChatUpdateReadStatus: &pb.EventChatUpdateReadStatus{
|
||||
s.eventsBuffer = append(s.eventsBuffer, event.NewMessage(s.spaceId, &pb.EventMessageValueOfChatUpdateMessageReadStatus{
|
||||
ChatUpdateMessageReadStatus: &pb.EventChatUpdateMessageReadStatus{
|
||||
Ids: ids,
|
||||
IsRead: read,
|
||||
SubIds: slices.Clone(s.ids),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *subscription) updateMentionRead(ids []string, read bool) {
|
||||
s.updateChatState(func(state *model.ChatState) *model.ChatState {
|
||||
if read {
|
||||
state.Mentions.Counter -= int32(len(ids))
|
||||
} else {
|
||||
state.Mentions.Counter += int32(len(ids))
|
||||
}
|
||||
return state
|
||||
})
|
||||
|
||||
if !s.canSend() {
|
||||
return
|
||||
}
|
||||
s.eventsBuffer = append(s.eventsBuffer, event.NewMessage(s.spaceId, &pb.EventMessageValueOfChatUpdateMentionReadStatus{
|
||||
ChatUpdateMentionReadStatus: &pb.EventChatUpdateMentionReadStatus{
|
||||
Ids: ids,
|
||||
IsRead: read,
|
||||
SubIds: slices.Clone(s.ids),
|
||||
|
@ -249,7 +323,7 @@ func copyChatState(state *model.ChatState) *model.ChatState {
|
|||
return &model.ChatState{
|
||||
Messages: copyReadState(state.Messages),
|
||||
Mentions: copyReadState(state.Mentions),
|
||||
DbTimestamp: state.DbTimestamp,
|
||||
LastStateId: state.LastStateId,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,14 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
func TestSubscription(t *testing.T) {
|
||||
|
@ -28,6 +34,8 @@ func TestSubscription(t *testing.T) {
|
|||
assert.Equal(t, wantTexts[i], msg.Message.Text)
|
||||
}
|
||||
|
||||
lastOrderId := resp.Messages[len(resp.Messages)-1].OrderId
|
||||
var lastStateId string
|
||||
t.Run("add message", func(t *testing.T) {
|
||||
fx.events = nil
|
||||
|
||||
|
@ -35,13 +43,40 @@ func TestSubscription(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, fx.events, 2)
|
||||
|
||||
ev := fx.events[0].GetChatAdd()
|
||||
require.NotNil(t, ev)
|
||||
assert.Equal(t, messageId, ev.Id)
|
||||
message, err := fx.GetMessageById(ctx, messageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
evState := fx.events[1].GetChatStateUpdate()
|
||||
require.NotNil(t, evState)
|
||||
assert.True(t, evState.State.DbTimestamp > 0)
|
||||
lastStateId = message.StateId
|
||||
|
||||
wantEvents := []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatAdd{
|
||||
ChatAdd: &pb.EventChatAdd{
|
||||
Id: message.Id,
|
||||
OrderId: message.OrderId,
|
||||
AfterOrderId: lastOrderId,
|
||||
Message: message.ChatMessage,
|
||||
SubIds: []string{"subId"},
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: message.StateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
})
|
||||
|
||||
t.Run("edit message", func(t *testing.T) {
|
||||
|
@ -54,10 +89,22 @@ func TestSubscription(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, fx.events, 1)
|
||||
|
||||
ev := fx.events[0].GetChatUpdate()
|
||||
require.NotNil(t, ev)
|
||||
assert.Equal(t, resp.Messages[0].Id, ev.Id)
|
||||
assert.Equal(t, edited.Message.Text, ev.Message.Message.Text)
|
||||
message, err := fx.GetMessageById(ctx, resp.Messages[0].Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents := []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatUpdate{
|
||||
ChatUpdate: &pb.EventChatUpdate{
|
||||
Id: resp.Messages[0].Id,
|
||||
Message: message.ChatMessage,
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
})
|
||||
|
||||
t.Run("toggle message reaction", func(t *testing.T) {
|
||||
|
@ -67,11 +114,31 @@ func TestSubscription(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, fx.events, 1)
|
||||
|
||||
ev := fx.events[0].GetChatUpdateReactions()
|
||||
require.NotNil(t, ev)
|
||||
assert.Equal(t, resp.Messages[0].Id, ev.Id)
|
||||
_, ok := ev.Reactions.Reactions["👍"]
|
||||
assert.True(t, ok)
|
||||
wantEvents := []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatUpdateReactions{
|
||||
ChatUpdateReactions: &pb.EventChatUpdateReactions{
|
||||
Id: resp.Messages[0].Id,
|
||||
Reactions: &model.ChatMessageReactions{
|
||||
Reactions: map[string]*model.ChatMessageReactionsIdentityList{
|
||||
"👍": {
|
||||
Ids: []string{testCreator},
|
||||
},
|
||||
"🥰": {
|
||||
Ids: []string{"identity1", "identity2"},
|
||||
},
|
||||
"🤔": {
|
||||
Ids: []string{"identity3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
})
|
||||
|
||||
t.Run("delete message", func(t *testing.T) {
|
||||
|
@ -79,10 +146,384 @@ func TestSubscription(t *testing.T) {
|
|||
|
||||
err = fx.DeleteMessage(ctx, resp.Messages[0].Id)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fx.events, 1)
|
||||
require.Len(t, fx.events, 2)
|
||||
|
||||
ev := fx.events[0].GetChatDelete()
|
||||
require.NotNil(t, ev)
|
||||
assert.Equal(t, resp.Messages[0].Id, ev.Id)
|
||||
wantEvents := []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatDelete{
|
||||
ChatDelete: &pb.EventChatDelete{
|
||||
Id: resp.Messages[0].Id,
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: lastStateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSubscriptionMessageCounters(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
fx.chatHandler.forceNotRead = true
|
||||
|
||||
subscribeResp, err := fx.SubscribeLastMessages(ctx, "subId", 10, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, subscribeResp.Messages)
|
||||
assert.Equal(t, &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: "",
|
||||
}, subscribeResp.ChatState)
|
||||
|
||||
// Add first message
|
||||
firstMessageId, err := fx.AddMessage(ctx, nil, givenSimpleMessage("first"))
|
||||
require.NoError(t, err)
|
||||
firstMessage, err := fx.GetMessageById(ctx, firstMessageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents := []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatAdd{
|
||||
ChatAdd: &pb.EventChatAdd{
|
||||
Id: firstMessage.Id,
|
||||
OrderId: firstMessage.OrderId,
|
||||
AfterOrderId: "",
|
||||
Message: firstMessage.ChatMessage,
|
||||
SubIds: []string{"subId"},
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
Counter: 1,
|
||||
OldestOrderId: firstMessage.OrderId,
|
||||
},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: firstMessage.StateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
fx.events = nil
|
||||
|
||||
secondMessageId, err := fx.AddMessage(ctx, nil, givenSimpleMessage("second"))
|
||||
require.NoError(t, err)
|
||||
|
||||
secondMessage, err := fx.GetMessageById(ctx, secondMessageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents = []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatAdd{
|
||||
ChatAdd: &pb.EventChatAdd{
|
||||
Id: secondMessage.Id,
|
||||
OrderId: secondMessage.OrderId,
|
||||
AfterOrderId: firstMessage.OrderId,
|
||||
Message: secondMessage.ChatMessage,
|
||||
SubIds: []string{"subId"},
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
Counter: 2,
|
||||
OldestOrderId: firstMessage.OrderId,
|
||||
},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: secondMessage.StateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
|
||||
// Read first message
|
||||
|
||||
fx.events = nil
|
||||
|
||||
err = fx.MarkReadMessages(ctx, "", firstMessage.OrderId, secondMessage.StateId, CounterTypeMessage)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents = []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatUpdateMessageReadStatus{
|
||||
ChatUpdateMessageReadStatus: &pb.EventChatUpdateMessageReadStatus{
|
||||
SubIds: []string{"subId"},
|
||||
Ids: []string{firstMessageId},
|
||||
IsRead: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
Counter: 1,
|
||||
OldestOrderId: secondMessage.OrderId,
|
||||
},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: secondMessage.StateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
}
|
||||
|
||||
func TestSubscriptionMentionCounters(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
fx.chatHandler.forceNotRead = true
|
||||
|
||||
subscribeResp, err := fx.SubscribeLastMessages(ctx, "subId", 10, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, subscribeResp.Messages)
|
||||
assert.Equal(t, &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: "",
|
||||
}, subscribeResp.ChatState)
|
||||
|
||||
// Add first message
|
||||
firstMessageId, err := fx.AddMessage(ctx, nil, givenMessageWithMention("first"))
|
||||
require.NoError(t, err)
|
||||
firstMessage, err := fx.GetMessageById(ctx, firstMessageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents := []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatAdd{
|
||||
ChatAdd: &pb.EventChatAdd{
|
||||
Id: firstMessage.Id,
|
||||
OrderId: firstMessage.OrderId,
|
||||
AfterOrderId: "",
|
||||
Message: firstMessage.ChatMessage,
|
||||
SubIds: []string{"subId"},
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
Counter: 1,
|
||||
OldestOrderId: firstMessage.OrderId,
|
||||
},
|
||||
Mentions: &model.ChatStateUnreadState{
|
||||
Counter: 1,
|
||||
OldestOrderId: firstMessage.OrderId,
|
||||
},
|
||||
LastStateId: firstMessage.StateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
fx.events = nil
|
||||
|
||||
secondMessageId, err := fx.AddMessage(ctx, nil, givenMessageWithMention("second"))
|
||||
require.NoError(t, err)
|
||||
|
||||
secondMessage, err := fx.GetMessageById(ctx, secondMessageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents = []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatAdd{
|
||||
ChatAdd: &pb.EventChatAdd{
|
||||
Id: secondMessage.Id,
|
||||
OrderId: secondMessage.OrderId,
|
||||
AfterOrderId: firstMessage.OrderId,
|
||||
Message: secondMessage.ChatMessage,
|
||||
SubIds: []string{"subId"},
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
Counter: 2,
|
||||
OldestOrderId: firstMessage.OrderId,
|
||||
},
|
||||
Mentions: &model.ChatStateUnreadState{
|
||||
Counter: 2,
|
||||
OldestOrderId: firstMessage.OrderId,
|
||||
},
|
||||
LastStateId: secondMessage.StateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
|
||||
// Read first message
|
||||
|
||||
fx.events = nil
|
||||
|
||||
err = fx.MarkReadMessages(ctx, "", firstMessage.OrderId, secondMessage.StateId, CounterTypeMention)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents = []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatUpdateMentionReadStatus{
|
||||
ChatUpdateMentionReadStatus: &pb.EventChatUpdateMentionReadStatus{
|
||||
SubIds: []string{"subId"},
|
||||
Ids: []string{firstMessageId},
|
||||
IsRead: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{
|
||||
Counter: 2,
|
||||
OldestOrderId: firstMessage.OrderId,
|
||||
},
|
||||
Mentions: &model.ChatStateUnreadState{
|
||||
Counter: 1,
|
||||
OldestOrderId: secondMessage.OrderId,
|
||||
},
|
||||
LastStateId: secondMessage.StateId,
|
||||
},
|
||||
SubIds: []string{"subId"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
}
|
||||
|
||||
func TestSubscriptionWithDeps(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fx := newFixture(t)
|
||||
|
||||
_, err := fx.SubscribeLastMessages(ctx, LastMessageSubscriptionId, 10, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
myParticipantId := domain.NewParticipantId(testSpaceId, testCreator)
|
||||
|
||||
identityDetails := domain.NewDetailsFromMap(map[domain.RelationKey]domain.Value{
|
||||
bundle.RelationKeyId: domain.String(myParticipantId),
|
||||
bundle.RelationKeyName: domain.String("John Doe"),
|
||||
})
|
||||
err = fx.spaceIndex.UpdateObjectDetails(ctx, myParticipantId, identityDetails)
|
||||
require.NoError(t, err)
|
||||
|
||||
attachmentDetails := domain.NewDetailsFromMap(map[domain.RelationKey]domain.Value{
|
||||
bundle.RelationKeyId: domain.String("fileObjectId1"),
|
||||
bundle.RelationKeyName: domain.String("file 1"),
|
||||
})
|
||||
err = fx.spaceIndex.UpdateObjectDetails(ctx, "fileObjectId1", attachmentDetails)
|
||||
require.NoError(t, err)
|
||||
|
||||
inputMessage := givenSimpleMessage("hello!")
|
||||
inputMessage.Attachments = []*model.ChatMessageAttachment{
|
||||
{
|
||||
Target: attachmentDetails.GetString(bundle.RelationKeyId),
|
||||
Type: model.ChatMessageAttachment_FILE,
|
||||
},
|
||||
{
|
||||
Target: "unknown object id",
|
||||
Type: model.ChatMessageAttachment_FILE,
|
||||
},
|
||||
}
|
||||
|
||||
messageId, err := fx.AddMessage(ctx, nil, inputMessage)
|
||||
require.NoError(t, err)
|
||||
|
||||
message, err := fx.GetMessageById(ctx, messageId)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantEvents := []*pb.EventMessage{
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatAdd{
|
||||
ChatAdd: &pb.EventChatAdd{
|
||||
Id: message.Id,
|
||||
OrderId: message.OrderId,
|
||||
AfterOrderId: "",
|
||||
Message: message.ChatMessage,
|
||||
SubIds: []string{LastMessageSubscriptionId},
|
||||
Dependencies: []*types.Struct{
|
||||
identityDetails.ToProto(),
|
||||
attachmentDetails.ToProto(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SpaceId: testSpaceId,
|
||||
Value: &pb.EventMessageValueOfChatStateUpdate{
|
||||
ChatStateUpdate: &pb.EventChatUpdateState{
|
||||
State: &model.ChatState{
|
||||
Messages: &model.ChatStateUnreadState{},
|
||||
Mentions: &model.ChatStateUnreadState{},
|
||||
LastStateId: message.StateId,
|
||||
},
|
||||
SubIds: []string{LastMessageSubscriptionId},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantEvents, fx.events)
|
||||
}
|
||||
|
|
|
@ -158,9 +158,6 @@ func (c *layoutConverter) fromAnyToBookmark(st *state.State) error {
|
|||
}
|
||||
|
||||
func (c *layoutConverter) fromAnyToTodo(st *state.State) error {
|
||||
if err := st.SetAlign(model.Block_AlignLeft); err != nil {
|
||||
return err
|
||||
}
|
||||
template.InitTemplate(st,
|
||||
template.WithTitle,
|
||||
template.WithRelations([]domain.RelationKey{bundle.RelationKeyDone}),
|
||||
|
@ -181,8 +178,6 @@ func (c *layoutConverter) fromNoteToSet(st *state.State) error {
|
|||
|
||||
func (c *layoutConverter) fromAnyToSet(st *state.State) error {
|
||||
source := st.Details().GetStringList(bundle.RelationKeySetOf)
|
||||
addFeaturedRelationSetOf(st)
|
||||
|
||||
dvBlock, err := dataview.BlockBySource(c.objectStore.SpaceIndex(st.SpaceID()), source, "")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -194,14 +189,6 @@ func (c *layoutConverter) fromAnyToSet(st *state.State) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func addFeaturedRelationSetOf(st *state.State) {
|
||||
fr := st.Details().GetStringList(bundle.RelationKeyFeaturedRelations)
|
||||
if !slices.Contains(fr, bundle.RelationKeySetOf.String()) {
|
||||
fr = append(fr, bundle.RelationKeySetOf.String())
|
||||
}
|
||||
st.SetDetail(bundle.RelationKeyFeaturedRelations, domain.StringList(fr))
|
||||
}
|
||||
|
||||
func (c *layoutConverter) fromSetToCollection(st *state.State) error {
|
||||
dvBlock := st.Get(template.DataviewBlockId)
|
||||
if dvBlock == nil {
|
||||
|
|
|
@ -95,7 +95,7 @@ func (ot *ObjectType) Init(ctx *smartblock.InitContext) (err error) {
|
|||
|
||||
func (ot *ObjectType) CreationStateMigration(ctx *smartblock.InitContext) migration.Migration {
|
||||
return migration.Migration{
|
||||
Version: 2,
|
||||
Version: 4,
|
||||
Proc: func(s *state.State) {
|
||||
if len(ctx.ObjectTypeKeys) > 0 && len(ctx.State.ObjectTypeKeys()) == 0 {
|
||||
ctx.State.SetObjectTypeKeys(ctx.ObjectTypeKeys)
|
||||
|
@ -417,7 +417,7 @@ func (ot *ObjectType) dataviewTemplates() []template.StateTransformer {
|
|||
|
||||
dvContent.Dataview.TargetObjectId = ot.Id()
|
||||
return []template.StateTransformer{
|
||||
template.WithDataviewID(state.DataviewBlockID, dvContent, false),
|
||||
template.WithDataviewIDIfNotExists(state.DataviewBlockID, dvContent, false),
|
||||
template.WithForcedDetail(bundle.RelationKeySetOf, domain.StringList([]string{ot.Id()})),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,10 @@ const (
|
|||
IdFromChange = "$changeId"
|
||||
)
|
||||
|
||||
var lexId = lexid.Must(lexid.CharsAllNoEscape, 4, 100)
|
||||
var LexId = lexid.Must(lexid.CharsAllNoEscape, 4, 100)
|
||||
|
||||
const (
|
||||
collChangeOrders = "_change_orders"
|
||||
CollChangeOrders = "_change_orders"
|
||||
)
|
||||
|
||||
func New(ctx context.Context, id string, db anystore.DB, handlers ...Handler) (state *StoreState, err error) {
|
||||
|
@ -104,7 +104,7 @@ type StoreState struct {
|
|||
}
|
||||
|
||||
func (ss *StoreState) init(ctx context.Context) (err error) {
|
||||
if ss.collChangeOrders, err = ss.Collection(ctx, collChangeOrders); err != nil {
|
||||
if ss.collChangeOrders, err = ss.Collection(ctx, CollChangeOrders); err != nil {
|
||||
return
|
||||
}
|
||||
for _, h := range ss.handlers {
|
||||
|
|
|
@ -48,7 +48,7 @@ func (stx *StoreStateTx) GetMaxOrder() string {
|
|||
}
|
||||
|
||||
func (stx *StoreStateTx) NextOrder(prev string) string {
|
||||
return lexId.Next(prev)
|
||||
return LexId.Next(prev)
|
||||
}
|
||||
|
||||
func (stx *StoreStateTx) SetOrder(changeId, order string) (err error) {
|
||||
|
|
|
@ -405,6 +405,22 @@ var WithAllBlocksEditsRestricted = StateTransformer(func(s *state.State) {
|
|||
})
|
||||
})
|
||||
|
||||
var WithDataviewIDIfNotExists = func(id string, dataview *model.BlockContentOfDataview, forceViews bool) StateTransformer {
|
||||
return func(s *state.State) {
|
||||
WithEmpty(s)
|
||||
if !s.Exists(id) {
|
||||
s.Set(simple.New(&model.Block{Content: dataview, Id: id}))
|
||||
if !s.IsParentOf(s.RootId(), id) {
|
||||
err := s.InsertTo(s.RootId(), model.Block_Inner, id)
|
||||
if err != nil {
|
||||
log.Errorf("template WithDataview failed to insert: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var WithDataviewID = func(id string, dataview *model.BlockContentOfDataview, forceViews bool) StateTransformer {
|
||||
return func(s *state.State) {
|
||||
WithEmpty(s)
|
||||
|
@ -414,10 +430,8 @@ var WithDataviewID = func(id string, dataview *model.BlockContentOfDataview, for
|
|||
if dvBlock, ok := b.(simpleDataview.Block); !ok {
|
||||
return true
|
||||
} else {
|
||||
if len(dvBlock.Model().GetDataview().Relations) == 0 ||
|
||||
!slice.UnsortedEqual(dvBlock.Model().GetDataview().Source, dataview.Dataview.Source) ||
|
||||
if !slice.UnsortedEqual(dvBlock.Model().GetDataview().Source, dataview.Dataview.Source) ||
|
||||
len(dvBlock.Model().GetDataview().Views) == 0 ||
|
||||
forceViews && len(dvBlock.Model().GetDataview().Relations) != len(dataview.Dataview.Relations) ||
|
||||
forceViews && !pbtypes.DataviewViewsEqualSorted(dvBlock.Model().GetDataview().Views, dataview.Dataview.Views) {
|
||||
|
||||
/* log.With("object" s.RootId()).With("name", pbtypes.GetString(s.Details(), "name")).Warnf("dataview needs to be migrated: %v, %v, %v, %v",
|
||||
|
|
|
@ -166,7 +166,6 @@ func (oc *ObjectCreator) Create(dataObject *DataObject, sn *common.Snapshot) (*d
|
|||
|
||||
func canUpdateObject(sbType coresb.SmartBlockType) bool {
|
||||
return sbType != coresb.SmartBlockTypeRelation &&
|
||||
sbType != coresb.SmartBlockTypeObjectType &&
|
||||
sbType != coresb.SmartBlockTypeRelationOption &&
|
||||
sbType != coresb.SmartBlockTypeFileObject &&
|
||||
sbType != coresb.SmartBlockTypeParticipant
|
||||
|
@ -367,6 +366,13 @@ func (oc *ObjectCreator) setSpaceDashboardID(spaceID string, st *state.State) {
|
|||
func (oc *ObjectCreator) resetState(newID string, st *state.State) *domain.Details {
|
||||
var respDetails *domain.Details
|
||||
err := cache.Do(oc.objectGetterDeleter, newID, func(b smartblock.SmartBlock) error {
|
||||
currentRevision := b.Details().GetInt64(bundle.RelationKeyRevision)
|
||||
newRevision := st.Details().GetInt64(bundle.RelationKeyRevision)
|
||||
if currentRevision > newRevision {
|
||||
// never update objects with older revision
|
||||
// we use revision for bundled objects like relations and object types
|
||||
return nil
|
||||
}
|
||||
err := history.ResetToVersion(b, st)
|
||||
if err != nil {
|
||||
log.With(zap.String("object id", newID)).Errorf("failed to set state %s: %s", newID, err)
|
||||
|
|
|
@ -42,6 +42,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/filestore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
|
@ -494,7 +495,15 @@ func (i *Import) getObjectID(
|
|||
return err
|
||||
}
|
||||
oldIDToNew[snapshot.Id] = id
|
||||
if payload.RootRawChange != nil {
|
||||
var isBundled bool
|
||||
switch snapshot.Snapshot.SbType {
|
||||
case smartblock.SmartBlockTypeObjectType:
|
||||
isBundled = bundle.HasObjectTypeByKey(domain.TypeKey(snapshot.Snapshot.Data.Key))
|
||||
case smartblock.SmartBlockTypeRelation:
|
||||
isBundled = bundle.HasRelation(domain.RelationKey(snapshot.Snapshot.Data.Key))
|
||||
}
|
||||
// bundled types will be created and then updated, cause they can be installed asynchronously
|
||||
if payload.RootRawChange != nil && !isBundled {
|
||||
createPayloads[id] = payload
|
||||
}
|
||||
return i.extractInternalKey(snapshot, oldIDToNew)
|
||||
|
|
|
@ -54,6 +54,7 @@ var (
|
|||
model.Restrictions_Publish,
|
||||
}
|
||||
sysTypesRestrictions = ObjectRestrictions{
|
||||
model.Restrictions_Blocks,
|
||||
model.Restrictions_LayoutChange,
|
||||
model.Restrictions_TypeChange,
|
||||
model.Restrictions_Template,
|
||||
|
|
|
@ -109,7 +109,7 @@ type Service struct {
|
|||
}
|
||||
|
||||
type builtinObjects interface {
|
||||
CreateObjectsForUseCase(ctx session.Context, spaceID string, req pb.RpcObjectImportUseCaseRequestUseCase) (code pb.RpcObjectImportUseCaseResponseErrorCode, err error)
|
||||
CreateObjectsForUseCase(ctx session.Context, spaceID string, req pb.RpcObjectImportUseCaseRequestUseCase) (dashboardId string, code pb.RpcObjectImportUseCaseResponseErrorCode, err error)
|
||||
}
|
||||
|
||||
type openedObjects struct {
|
||||
|
|
|
@ -276,17 +276,17 @@ func (_c *MockStore_Id_Call) RunAndReturn(run func() string) *MockStore_Id_Call
|
|||
return _c
|
||||
}
|
||||
|
||||
// InitDiffManager provides a mock function with given fields: ctx, seenHeads
|
||||
func (_m *MockStore) InitDiffManager(ctx context.Context, seenHeads []string) error {
|
||||
ret := _m.Called(ctx, seenHeads)
|
||||
// InitDiffManager provides a mock function with given fields: ctx, name, seenHeads
|
||||
func (_m *MockStore) InitDiffManager(ctx context.Context, name string, seenHeads []string) error {
|
||||
ret := _m.Called(ctx, name, seenHeads)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for InitDiffManager")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok {
|
||||
r0 = rf(ctx, seenHeads)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok {
|
||||
r0 = rf(ctx, name, seenHeads)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
@ -301,14 +301,15 @@ type MockStore_InitDiffManager_Call struct {
|
|||
|
||||
// InitDiffManager is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - name string
|
||||
// - seenHeads []string
|
||||
func (_e *MockStore_Expecter) InitDiffManager(ctx interface{}, seenHeads interface{}) *MockStore_InitDiffManager_Call {
|
||||
return &MockStore_InitDiffManager_Call{Call: _e.mock.On("InitDiffManager", ctx, seenHeads)}
|
||||
func (_e *MockStore_Expecter) InitDiffManager(ctx interface{}, name interface{}, seenHeads interface{}) *MockStore_InitDiffManager_Call {
|
||||
return &MockStore_InitDiffManager_Call{Call: _e.mock.On("InitDiffManager", ctx, name, seenHeads)}
|
||||
}
|
||||
|
||||
func (_c *MockStore_InitDiffManager_Call) Run(run func(ctx context.Context, seenHeads []string)) *MockStore_InitDiffManager_Call {
|
||||
func (_c *MockStore_InitDiffManager_Call) Run(run func(ctx context.Context, name string, seenHeads []string)) *MockStore_InitDiffManager_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([]string))
|
||||
run(args[0].(context.Context), args[1].(string), args[2].([]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
@ -318,22 +319,22 @@ func (_c *MockStore_InitDiffManager_Call) Return(_a0 error) *MockStore_InitDiffM
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStore_InitDiffManager_Call) RunAndReturn(run func(context.Context, []string) error) *MockStore_InitDiffManager_Call {
|
||||
func (_c *MockStore_InitDiffManager_Call) RunAndReturn(run func(context.Context, string, []string) error) *MockStore_InitDiffManager_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// MarkSeenHeads provides a mock function with given fields: ctx, heads
|
||||
func (_m *MockStore) MarkSeenHeads(ctx context.Context, heads []string) error {
|
||||
ret := _m.Called(ctx, heads)
|
||||
// MarkSeenHeads provides a mock function with given fields: ctx, name, heads
|
||||
func (_m *MockStore) MarkSeenHeads(ctx context.Context, name string, heads []string) error {
|
||||
ret := _m.Called(ctx, name, heads)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for MarkSeenHeads")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok {
|
||||
r0 = rf(ctx, heads)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok {
|
||||
r0 = rf(ctx, name, heads)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
@ -348,14 +349,15 @@ type MockStore_MarkSeenHeads_Call struct {
|
|||
|
||||
// MarkSeenHeads is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - name string
|
||||
// - heads []string
|
||||
func (_e *MockStore_Expecter) MarkSeenHeads(ctx interface{}, heads interface{}) *MockStore_MarkSeenHeads_Call {
|
||||
return &MockStore_MarkSeenHeads_Call{Call: _e.mock.On("MarkSeenHeads", ctx, heads)}
|
||||
func (_e *MockStore_Expecter) MarkSeenHeads(ctx interface{}, name interface{}, heads interface{}) *MockStore_MarkSeenHeads_Call {
|
||||
return &MockStore_MarkSeenHeads_Call{Call: _e.mock.On("MarkSeenHeads", ctx, name, heads)}
|
||||
}
|
||||
|
||||
func (_c *MockStore_MarkSeenHeads_Call) Run(run func(ctx context.Context, heads []string)) *MockStore_MarkSeenHeads_Call {
|
||||
func (_c *MockStore_MarkSeenHeads_Call) Run(run func(ctx context.Context, name string, heads []string)) *MockStore_MarkSeenHeads_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([]string))
|
||||
run(args[0].(context.Context), args[1].(string), args[2].([]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
@ -365,7 +367,7 @@ func (_c *MockStore_MarkSeenHeads_Call) Return(_a0 error) *MockStore_MarkSeenHea
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStore_MarkSeenHeads_Call) RunAndReturn(run func(context.Context, []string) error) *MockStore_MarkSeenHeads_Call {
|
||||
func (_c *MockStore_MarkSeenHeads_Call) RunAndReturn(run func(context.Context, string, []string) error) *MockStore_MarkSeenHeads_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
@ -636,35 +638,36 @@ func (_c *MockStore_ReadStoreDoc_Call) RunAndReturn(run func(context.Context, *s
|
|||
return _c
|
||||
}
|
||||
|
||||
// SetDiffManagerOnRemoveHook provides a mock function with given fields: f
|
||||
func (_m *MockStore) SetDiffManagerOnRemoveHook(f func([]string)) {
|
||||
_m.Called(f)
|
||||
// RegisterDiffManager provides a mock function with given fields: name, onRemoveHook
|
||||
func (_m *MockStore) RegisterDiffManager(name string, onRemoveHook func([]string)) {
|
||||
_m.Called(name, onRemoveHook)
|
||||
}
|
||||
|
||||
// MockStore_SetDiffManagerOnRemoveHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetDiffManagerOnRemoveHook'
|
||||
type MockStore_SetDiffManagerOnRemoveHook_Call struct {
|
||||
// MockStore_RegisterDiffManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterDiffManager'
|
||||
type MockStore_RegisterDiffManager_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetDiffManagerOnRemoveHook is a helper method to define mock.On call
|
||||
// - f func([]string)
|
||||
func (_e *MockStore_Expecter) SetDiffManagerOnRemoveHook(f interface{}) *MockStore_SetDiffManagerOnRemoveHook_Call {
|
||||
return &MockStore_SetDiffManagerOnRemoveHook_Call{Call: _e.mock.On("SetDiffManagerOnRemoveHook", f)}
|
||||
// RegisterDiffManager is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - onRemoveHook func([]string)
|
||||
func (_e *MockStore_Expecter) RegisterDiffManager(name interface{}, onRemoveHook interface{}) *MockStore_RegisterDiffManager_Call {
|
||||
return &MockStore_RegisterDiffManager_Call{Call: _e.mock.On("RegisterDiffManager", name, onRemoveHook)}
|
||||
}
|
||||
|
||||
func (_c *MockStore_SetDiffManagerOnRemoveHook_Call) Run(run func(f func([]string))) *MockStore_SetDiffManagerOnRemoveHook_Call {
|
||||
func (_c *MockStore_RegisterDiffManager_Call) Run(run func(name string, onRemoveHook func([]string))) *MockStore_RegisterDiffManager_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(func([]string)))
|
||||
run(args[0].(string), args[1].(func([]string)))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStore_SetDiffManagerOnRemoveHook_Call) Return() *MockStore_SetDiffManagerOnRemoveHook_Call {
|
||||
func (_c *MockStore_RegisterDiffManager_Call) Return() *MockStore_RegisterDiffManager_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStore_SetDiffManagerOnRemoveHook_Call) RunAndReturn(run func(func([]string))) *MockStore_SetDiffManagerOnRemoveHook_Call {
|
||||
func (_c *MockStore_RegisterDiffManager_Call) RunAndReturn(run func(string, func([]string))) *MockStore_RegisterDiffManager_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
@ -747,17 +750,17 @@ func (_c *MockStore_SpaceID_Call) RunAndReturn(run func() string) *MockStore_Spa
|
|||
return _c
|
||||
}
|
||||
|
||||
// StoreSeenHeads provides a mock function with given fields: ctx
|
||||
func (_m *MockStore) StoreSeenHeads(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
// StoreSeenHeads provides a mock function with given fields: ctx, name
|
||||
func (_m *MockStore) StoreSeenHeads(ctx context.Context, name string) error {
|
||||
ret := _m.Called(ctx, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for StoreSeenHeads")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(ctx)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = rf(ctx, name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
@ -772,13 +775,14 @@ type MockStore_StoreSeenHeads_Call struct {
|
|||
|
||||
// StoreSeenHeads is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockStore_Expecter) StoreSeenHeads(ctx interface{}) *MockStore_StoreSeenHeads_Call {
|
||||
return &MockStore_StoreSeenHeads_Call{Call: _e.mock.On("StoreSeenHeads", ctx)}
|
||||
// - name string
|
||||
func (_e *MockStore_Expecter) StoreSeenHeads(ctx interface{}, name interface{}) *MockStore_StoreSeenHeads_Call {
|
||||
return &MockStore_StoreSeenHeads_Call{Call: _e.mock.On("StoreSeenHeads", ctx, name)}
|
||||
}
|
||||
|
||||
func (_c *MockStore_StoreSeenHeads_Call) Run(run func(ctx context.Context)) *MockStore_StoreSeenHeads_Call {
|
||||
func (_c *MockStore_StoreSeenHeads_Call) Run(run func(ctx context.Context, name string)) *MockStore_StoreSeenHeads_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
@ -788,7 +792,7 @@ func (_c *MockStore_StoreSeenHeads_Call) Return(_a0 error) *MockStore_StoreSeenH
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStore_StoreSeenHeads_Call) RunAndReturn(run func(context.Context) error) *MockStore_StoreSeenHeads_Call {
|
||||
func (_c *MockStore_StoreSeenHeads_Call) RunAndReturn(run func(context.Context, string) error) *MockStore_StoreSeenHeads_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ func (s *service) newTreeSource(ctx context.Context, space Space, id string, bui
|
|||
fileObjectMigrator: s.fileObjectMigrator,
|
||||
}
|
||||
if sbt == smartblock.SmartBlockTypeChatDerivedObject || sbt == smartblock.SmartBlockTypeAccountObject {
|
||||
return &store{source: src, sbType: sbt}, nil
|
||||
return &store{source: src, sbType: sbt, diffManagers: map[string]*diffManager{}}, nil
|
||||
}
|
||||
|
||||
return src, nil
|
||||
|
|
|
@ -36,12 +36,18 @@ type Store interface {
|
|||
ReadStoreDoc(ctx context.Context, stateStore *storestate.StoreState, onUpdateHook func()) (err error)
|
||||
PushStoreChange(ctx context.Context, params PushStoreChangeParams) (changeId string, err error)
|
||||
SetPushChangeHook(onPushChange PushChangeHook)
|
||||
|
||||
// RegisterDiffManager sets a hook that will be called when a change is removed (marked as read) from the diff manager
|
||||
// must be called before ReadStoreDoc.
|
||||
//
|
||||
// If a head is marked as read in the diff manager, all earlier heads for that branch marked as read as well
|
||||
RegisterDiffManager(name string, onRemoveHook func(removed []string))
|
||||
// MarkSeenHeads marks heads as seen in a diff manager. Then the diff manager will call a hook from SetDiffManagerOnRemoveHook
|
||||
MarkSeenHeads(ctx context.Context, heads []string) error
|
||||
SetDiffManagerOnRemoveHook(f func(removed []string))
|
||||
MarkSeenHeads(ctx context.Context, name string, heads []string) error
|
||||
// StoreSeenHeads persists current seen heads in any-store
|
||||
StoreSeenHeads(ctx context.Context) error
|
||||
InitDiffManager(ctx context.Context, seenHeads []string) error
|
||||
StoreSeenHeads(ctx context.Context, name string) error
|
||||
// InitDiffManager initializes a diff manager with specified seen heads
|
||||
InitDiffManager(ctx context.Context, name string, seenHeads []string) error
|
||||
}
|
||||
|
||||
type PushStoreChangeParams struct {
|
||||
|
@ -57,12 +63,17 @@ var (
|
|||
|
||||
type store struct {
|
||||
*source
|
||||
store *storestate.StoreState
|
||||
onUpdateHook func()
|
||||
onPushChange PushChangeHook
|
||||
onDiffManagerRemove func(removed []string)
|
||||
diffManager *objecttree.DiffManager
|
||||
sbType smartblock.SmartBlockType
|
||||
store *storestate.StoreState
|
||||
onUpdateHook func()
|
||||
onPushChange PushChangeHook
|
||||
sbType smartblock.SmartBlockType
|
||||
|
||||
diffManagers map[string]*diffManager
|
||||
}
|
||||
|
||||
type diffManager struct {
|
||||
diffManager *objecttree.DiffManager
|
||||
onRemove func(removed []string)
|
||||
}
|
||||
|
||||
func (s *store) GetFileKeysSnapshot() []*pb.ChangeFileKeys {
|
||||
|
@ -73,13 +84,34 @@ func (s *store) SetPushChangeHook(onPushChange PushChangeHook) {
|
|||
s.onPushChange = onPushChange
|
||||
}
|
||||
|
||||
// SetDiffManagerOnRemoveHook sets a hook that will be called when a change is removed from the diff manager
|
||||
// must be called only before ReadStoreDoc
|
||||
func (s *store) SetDiffManagerOnRemoveHook(f func(removed []string)) {
|
||||
s.onDiffManagerRemove = f
|
||||
func (s *store) RegisterDiffManager(name string, onRemoveHook func(removed []string)) {
|
||||
if _, ok := s.diffManagers[name]; !ok {
|
||||
s.diffManagers[name] = &diffManager{
|
||||
onRemove: onRemoveHook,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) InitDiffManager(ctx context.Context, seenHeads []string) (err error) {
|
||||
func (s *store) initDiffManagers(ctx context.Context) error {
|
||||
for name := range s.diffManagers {
|
||||
seenHeads, err := s.loadSeenHeads(ctx, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load seen heads: %w", err)
|
||||
}
|
||||
err = s.InitDiffManager(ctx, name, seenHeads)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init diff manager: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) InitDiffManager(ctx context.Context, name string, seenHeads []string) (err error) {
|
||||
manager, ok := s.diffManagers[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
curTreeHeads := s.source.Tree().Heads()
|
||||
|
||||
buildTree := func(heads []string) (objecttree.ReadableObjectTree, error) {
|
||||
|
@ -89,11 +121,16 @@ func (s *store) InitDiffManager(ctx context.Context, seenHeads []string) (err er
|
|||
})
|
||||
}
|
||||
onRemove := func(removed []string) {
|
||||
if s.onDiffManagerRemove != nil {
|
||||
s.onDiffManagerRemove(removed)
|
||||
if manager.onRemove != nil {
|
||||
manager.onRemove(removed)
|
||||
}
|
||||
}
|
||||
s.diffManager, err = objecttree.NewDiffManager(seenHeads, curTreeHeads, buildTree, onRemove)
|
||||
|
||||
manager.diffManager, err = objecttree.NewDiffManager(seenHeads, curTreeHeads, buildTree, onRemove)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init diff manager: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -136,11 +173,7 @@ func (s *store) ReadStoreDoc(ctx context.Context, storeState *storestate.StoreSt
|
|||
s.onUpdateHook = onUpdateHook
|
||||
s.store = storeState
|
||||
|
||||
seenHeads, err := s.loadSeenHeads(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load seen heads: %w", err)
|
||||
}
|
||||
err = s.InitDiffManager(ctx, seenHeads)
|
||||
err = s.initDiffManagers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -216,10 +249,16 @@ func (s *store) PushStoreChange(ctx context.Context, params PushStoreChangeParam
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.diffManager.Add(&objecttree.Change{
|
||||
Id: changeId,
|
||||
PreviousIds: ch.PreviousIds,
|
||||
})
|
||||
|
||||
for _, m := range s.diffManagers {
|
||||
if m.diffManager != nil {
|
||||
m.diffManager.Add(&objecttree.Change{
|
||||
Id: changeId,
|
||||
PreviousIds: ch.PreviousIds,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return changeId, err
|
||||
}
|
||||
|
||||
|
@ -237,25 +276,42 @@ func (s *store) update(ctx context.Context, tree objecttree.ObjectTree) error {
|
|||
return errors.Join(tx.Rollback(), err)
|
||||
}
|
||||
err = tx.Commit()
|
||||
s.diffManager.Update(tree)
|
||||
for _, m := range s.diffManagers {
|
||||
if m.diffManager != nil {
|
||||
m.diffManager.Update(tree)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
s.onUpdateHook()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *store) MarkSeenHeads(ctx context.Context, heads []string) error {
|
||||
s.diffManager.Remove(heads)
|
||||
return s.StoreSeenHeads(ctx)
|
||||
func (s *store) MarkSeenHeads(ctx context.Context, name string, heads []string) error {
|
||||
manager, ok := s.diffManagers[name]
|
||||
if ok {
|
||||
manager.diffManager.Remove(heads)
|
||||
return s.StoreSeenHeads(ctx, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) StoreSeenHeads(ctx context.Context) error {
|
||||
coll, err := s.store.Collection(ctx, "seenHeads")
|
||||
func seenHeadsCollectionName(name string) string {
|
||||
return "seenHeads/" + name
|
||||
}
|
||||
|
||||
func (s *store) StoreSeenHeads(ctx context.Context, name string) error {
|
||||
manager, ok := s.diffManagers[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
coll, err := s.store.Collection(ctx, seenHeadsCollectionName(name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get collection: %w", err)
|
||||
}
|
||||
|
||||
seenHeads := s.diffManager.SeenHeads()
|
||||
seenHeads := manager.diffManager.SeenHeads()
|
||||
raw, err := json.Marshal(seenHeads)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal seen heads: %w", err)
|
||||
|
@ -268,8 +324,8 @@ func (s *store) StoreSeenHeads(ctx context.Context) error {
|
|||
return coll.UpsertOne(ctx, doc)
|
||||
}
|
||||
|
||||
func (s *store) loadSeenHeads(ctx context.Context) ([]string, error) {
|
||||
coll, err := s.store.Collection(ctx, "seenHeads")
|
||||
func (s *store) loadSeenHeads(ctx context.Context, name string) ([]string, error) {
|
||||
coll, err := s.store.Collection(ctx, seenHeadsCollectionName(name))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get collection: %w", err)
|
||||
}
|
||||
|
|
209
core/chats.go
209
core/chats.go
|
@ -8,61 +8,75 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/chats"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/chatobject"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
func (mw *Middleware) ChatAddMessage(cctx context.Context, req *pb.RpcChatAddMessageRequest) *pb.RpcChatAddMessageResponse {
|
||||
ctx := mw.newContext(cctx)
|
||||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
messageId, err := chatService.AddMessage(cctx, ctx, req.ChatObjectId, req.Message)
|
||||
code := mapErrorCode[pb.RpcChatAddMessageResponseErrorCode](err)
|
||||
messageId, err := chatService.AddMessage(cctx, ctx, req.ChatObjectId, &chatobject.Message{ChatMessage: req.Message})
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatAddMessageResponseErrorCode](err)
|
||||
return &pb.RpcChatAddMessageResponse{
|
||||
Error: &pb.RpcChatAddMessageResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatAddMessageResponse{
|
||||
MessageId: messageId,
|
||||
Event: ctx.GetResponseEvent(),
|
||||
Error: &pb.RpcChatAddMessageResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *Middleware) ChatEditMessageContent(cctx context.Context, req *pb.RpcChatEditMessageContentRequest) *pb.RpcChatEditMessageContentResponse {
|
||||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
err := chatService.EditMessage(cctx, req.ChatObjectId, req.MessageId, req.EditedMessage)
|
||||
code := mapErrorCode[pb.RpcChatEditMessageContentResponseErrorCode](err)
|
||||
return &pb.RpcChatEditMessageContentResponse{
|
||||
Error: &pb.RpcChatEditMessageContentResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
err := chatService.EditMessage(cctx, req.ChatObjectId, req.MessageId, &chatobject.Message{ChatMessage: req.EditedMessage})
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatEditMessageContentResponseErrorCode](err)
|
||||
return &pb.RpcChatEditMessageContentResponse{
|
||||
Error: &pb.RpcChatEditMessageContentResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatEditMessageContentResponse{}
|
||||
}
|
||||
|
||||
func (mw *Middleware) ChatToggleMessageReaction(cctx context.Context, req *pb.RpcChatToggleMessageReactionRequest) *pb.RpcChatToggleMessageReactionResponse {
|
||||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
err := chatService.ToggleMessageReaction(cctx, req.ChatObjectId, req.MessageId, req.Emoji)
|
||||
code := mapErrorCode[pb.RpcChatToggleMessageReactionResponseErrorCode](err)
|
||||
return &pb.RpcChatToggleMessageReactionResponse{
|
||||
Error: &pb.RpcChatToggleMessageReactionResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatToggleMessageReactionResponseErrorCode](err)
|
||||
return &pb.RpcChatToggleMessageReactionResponse{
|
||||
Error: &pb.RpcChatToggleMessageReactionResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatToggleMessageReactionResponse{}
|
||||
}
|
||||
|
||||
func (mw *Middleware) ChatDeleteMessage(cctx context.Context, req *pb.RpcChatDeleteMessageRequest) *pb.RpcChatDeleteMessageResponse {
|
||||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
err := chatService.DeleteMessage(cctx, req.ChatObjectId, req.MessageId)
|
||||
code := mapErrorCode[pb.RpcChatDeleteMessageResponseErrorCode](err)
|
||||
return &pb.RpcChatDeleteMessageResponse{
|
||||
Error: &pb.RpcChatDeleteMessageResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatDeleteMessageResponseErrorCode](err)
|
||||
return &pb.RpcChatDeleteMessageResponse{
|
||||
Error: &pb.RpcChatDeleteMessageResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatDeleteMessageResponse{}
|
||||
}
|
||||
|
||||
func (mw *Middleware) ChatGetMessages(cctx context.Context, req *pb.RpcChatGetMessagesRequest) *pb.RpcChatGetMessagesResponse {
|
||||
|
@ -74,14 +88,19 @@ func (mw *Middleware) ChatGetMessages(cctx context.Context, req *pb.RpcChatGetMe
|
|||
Limit: int(req.Limit),
|
||||
IncludeBoundary: req.IncludeBoundary,
|
||||
})
|
||||
code := mapErrorCode[pb.RpcChatGetMessagesResponseErrorCode](err)
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatGetMessagesResponseErrorCode](err)
|
||||
return &pb.RpcChatGetMessagesResponse{
|
||||
Error: &pb.RpcChatGetMessagesResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.RpcChatGetMessagesResponse{
|
||||
Messages: resp.Messages,
|
||||
Messages: messagesToProto(resp.Messages),
|
||||
ChatState: resp.ChatState,
|
||||
Error: &pb.RpcChatGetMessagesResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,13 +108,17 @@ func (mw *Middleware) ChatGetMessagesByIds(cctx context.Context, req *pb.RpcChat
|
|||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
messages, err := chatService.GetMessagesByIds(cctx, req.ChatObjectId, req.MessageIds)
|
||||
code := mapErrorCode[pb.RpcChatGetMessagesByIdsResponseErrorCode](err)
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatGetMessagesByIdsResponseErrorCode](err)
|
||||
return &pb.RpcChatGetMessagesByIdsResponse{
|
||||
Error: &pb.RpcChatGetMessagesByIdsResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatGetMessagesByIdsResponse{
|
||||
Messages: messages,
|
||||
Error: &pb.RpcChatGetMessagesByIdsResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
Messages: messagesToProto(messages),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,15 +126,19 @@ func (mw *Middleware) ChatSubscribeLastMessages(cctx context.Context, req *pb.Rp
|
|||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
resp, err := chatService.SubscribeLastMessages(cctx, req.ChatObjectId, int(req.Limit), req.SubId)
|
||||
code := mapErrorCode[pb.RpcChatSubscribeLastMessagesResponseErrorCode](err)
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatSubscribeLastMessagesResponseErrorCode](err)
|
||||
return &pb.RpcChatSubscribeLastMessagesResponse{
|
||||
Error: &pb.RpcChatSubscribeLastMessagesResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatSubscribeLastMessagesResponse{
|
||||
Messages: resp.Messages,
|
||||
Messages: messagesToProto(resp.Messages),
|
||||
NumMessagesBefore: 0,
|
||||
ChatState: resp.ChatState,
|
||||
Error: &pb.RpcChatSubscribeLastMessagesResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,26 +146,33 @@ func (mw *Middleware) ChatUnsubscribe(cctx context.Context, req *pb.RpcChatUnsub
|
|||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
err := chatService.Unsubscribe(req.ChatObjectId, req.SubId)
|
||||
code := mapErrorCode[pb.RpcChatUnsubscribeResponseErrorCode](err)
|
||||
return &pb.RpcChatUnsubscribeResponse{
|
||||
Error: &pb.RpcChatUnsubscribeResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatUnsubscribeResponseErrorCode](err)
|
||||
return &pb.RpcChatUnsubscribeResponse{
|
||||
Error: &pb.RpcChatUnsubscribeResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatUnsubscribeResponse{}
|
||||
}
|
||||
|
||||
func (mw *Middleware) ChatSubscribeToMessagePreviews(cctx context.Context, req *pb.RpcChatSubscribeToMessagePreviewsRequest) *pb.RpcChatSubscribeToMessagePreviewsResponse {
|
||||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
subId, err := chatService.SubscribeToMessagePreviews(cctx)
|
||||
code := mapErrorCode[pb.RpcChatSubscribeToMessagePreviewsResponseErrorCode](err)
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatSubscribeToMessagePreviewsResponseErrorCode](err)
|
||||
return &pb.RpcChatSubscribeToMessagePreviewsResponse{
|
||||
Error: &pb.RpcChatSubscribeToMessagePreviewsResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatSubscribeToMessagePreviewsResponse{
|
||||
SubId: subId,
|
||||
Error: &pb.RpcChatSubscribeToMessagePreviewsResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,37 +180,60 @@ func (mw *Middleware) ChatUnsubscribeFromMessagePreviews(cctx context.Context, r
|
|||
chatService := mustService[chats.Service](mw)
|
||||
|
||||
err := chatService.UnsubscribeFromMessagePreviews()
|
||||
code := mapErrorCode[pb.RpcChatUnsubscribeFromMessagePreviewsResponseErrorCode](err)
|
||||
return &pb.RpcChatUnsubscribeFromMessagePreviewsResponse{
|
||||
Error: &pb.RpcChatUnsubscribeFromMessagePreviewsResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatUnsubscribeFromMessagePreviewsResponseErrorCode](err)
|
||||
return &pb.RpcChatUnsubscribeFromMessagePreviewsResponse{
|
||||
Error: &pb.RpcChatUnsubscribeFromMessagePreviewsResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatUnsubscribeFromMessagePreviewsResponse{}
|
||||
}
|
||||
|
||||
func (mw *Middleware) ChatReadMessages(cctx context.Context, request *pb.RpcChatReadMessagesRequest) *pb.RpcChatReadMessagesResponse {
|
||||
chatService := mustService[chats.Service](mw)
|
||||
err := chatService.ReadMessages(cctx, request.ChatObjectId, request.AfterOrderId, request.BeforeOrderId, request.LastDbTimestamp)
|
||||
code := mapErrorCode(err,
|
||||
errToCode(anystore.ErrDocNotFound, pb.RpcChatReadMessagesResponseError_MESSAGES_NOT_FOUND),
|
||||
)
|
||||
return &pb.RpcChatReadMessagesResponse{
|
||||
Error: &pb.RpcChatReadMessagesResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
err := chatService.ReadMessages(cctx, chats.ReadMessagesRequest{
|
||||
ChatObjectId: request.ChatObjectId,
|
||||
AfterOrderId: request.AfterOrderId,
|
||||
BeforeOrderId: request.BeforeOrderId,
|
||||
LastStateId: request.LastStateId,
|
||||
CounterType: chatobject.CounterType(request.Type),
|
||||
})
|
||||
if err != nil {
|
||||
code := mapErrorCode(err,
|
||||
errToCode(anystore.ErrDocNotFound, pb.RpcChatReadMessagesResponseError_MESSAGES_NOT_FOUND),
|
||||
)
|
||||
return &pb.RpcChatReadMessagesResponse{
|
||||
Error: &pb.RpcChatReadMessagesResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatReadMessagesResponse{}
|
||||
}
|
||||
|
||||
func (mw *Middleware) ChatUnreadMessages(cctx context.Context, request *pb.RpcChatUnreadRequest) *pb.RpcChatUnreadResponse {
|
||||
chatService := mustService[chats.Service](mw)
|
||||
err := chatService.UnreadMessages(cctx, request.ChatObjectId, request.AfterOrderId)
|
||||
code := mapErrorCode[pb.RpcChatUnreadResponseErrorCode](err)
|
||||
return &pb.RpcChatUnreadResponse{
|
||||
Error: &pb.RpcChatUnreadResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
err := chatService.UnreadMessages(cctx, request.ChatObjectId, request.AfterOrderId, chatobject.CounterType(request.Type))
|
||||
if err != nil {
|
||||
code := mapErrorCode[pb.RpcChatUnreadResponseErrorCode](err)
|
||||
return &pb.RpcChatUnreadResponse{
|
||||
Error: &pb.RpcChatUnreadResponseError{
|
||||
Code: code,
|
||||
Description: getErrorDescription(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &pb.RpcChatUnreadResponse{}
|
||||
}
|
||||
|
||||
func messagesToProto(msgs []*chatobject.Message) []*model.ChatMessage {
|
||||
res := make([]*model.ChatMessage, 0, len(msgs))
|
||||
for _, msg := range msgs {
|
||||
res = append(res, msg.ChatMessage)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ func NewParticipantId(spaceId, identity string) string {
|
|||
return fmt.Sprintf("%s%s_%s", ParticipantPrefix, spaceId, identity)
|
||||
}
|
||||
|
||||
func ParseParticipantId(participantId string) (string, string, error) {
|
||||
func ParseParticipantId(participantId string) (spaceId string, identity string, err error) {
|
||||
if !strings.HasPrefix(participantId, ParticipantPrefix) {
|
||||
return "", "", fmt.Errorf("participant id must start with _participant_")
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ type reindexFlags struct {
|
|||
deletedObjects bool
|
||||
eraseLinks bool
|
||||
removeParticipants bool
|
||||
chats bool
|
||||
}
|
||||
|
||||
func (f *reindexFlags) any() bool {
|
||||
|
@ -29,7 +30,8 @@ func (f *reindexFlags) any() bool {
|
|||
f.removeOldFiles ||
|
||||
f.deletedObjects ||
|
||||
f.removeParticipants ||
|
||||
f.eraseLinks
|
||||
f.eraseLinks ||
|
||||
f.chats
|
||||
}
|
||||
|
||||
func (f *reindexFlags) enableAll() {
|
||||
|
@ -45,6 +47,7 @@ func (f *reindexFlags) enableAll() {
|
|||
f.deletedObjects = true
|
||||
f.removeParticipants = true
|
||||
f.eraseLinks = true
|
||||
f.chats = true
|
||||
}
|
||||
|
||||
func (f *reindexFlags) String() string {
|
||||
|
|
|
@ -10,7 +10,9 @@ import (
|
|||
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/chatobject"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/storestate"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/objectcache"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/core/syncstatus/detailsupdater/helper"
|
||||
|
@ -49,6 +51,7 @@ const (
|
|||
ForceReindexDeletedObjectsCounter int32 = 1
|
||||
|
||||
ForceReindexParticipantsCounter int32 = 1
|
||||
ForceReindexChatsCounter int32 = 1
|
||||
)
|
||||
|
||||
type allDeletedIdsProvider interface {
|
||||
|
@ -76,6 +79,7 @@ func (i *indexer) buildFlags(spaceID string) (reindexFlags, error) {
|
|||
AreOldFilesRemoved: true,
|
||||
ReindexDeletedObjects: 0, // Set to zero to force reindexing of deleted objects when objectstore was deleted
|
||||
ReindexParticipants: ForceReindexParticipantsCounter,
|
||||
ReindexChats: ForceReindexChatsCounter,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,6 +120,9 @@ func (i *indexer) buildFlags(spaceID string) (reindexFlags, error) {
|
|||
if checksums.LinksErase != ForceLinksReindexCounter {
|
||||
flags.eraseLinks = true
|
||||
}
|
||||
if checksums.ReindexChats != ForceReindexChatsCounter {
|
||||
flags.chats = true
|
||||
}
|
||||
if spaceID == addr.AnytypeMarketplaceWorkspace && checksums.MarketplaceForceReindexCounter != ForceMarketplaceReindex {
|
||||
flags.enableAll()
|
||||
}
|
||||
|
@ -201,6 +208,13 @@ func (i *indexer) ReindexSpace(space clientspace.Space) (err error) {
|
|||
}()
|
||||
}
|
||||
|
||||
if flags.chats {
|
||||
err = i.reindexChats(ctx, space)
|
||||
if err != nil {
|
||||
log.Error("reindex chats", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if flags.deletedObjects {
|
||||
err = i.reindexDeletedObjects(space)
|
||||
if err != nil {
|
||||
|
@ -220,6 +234,56 @@ func (i *indexer) ReindexSpace(space clientspace.Space) (err error) {
|
|||
return i.saveLatestChecksums(space.Id())
|
||||
}
|
||||
|
||||
func (i *indexer) reindexChats(ctx context.Context, space clientspace.Space) error {
|
||||
ids, err := i.getIdsForTypes(space, coresb.SmartBlockTypeChatDerivedObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
db := i.store.GetCrdtDb(space.Id())
|
||||
|
||||
txn, err := db.WriteTx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write tx: %w", err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
for _, id := range ids {
|
||||
col, err := db.OpenCollection(txn.Context(), id+chatobject.CollectionName)
|
||||
if errors.Is(err, anystore.ErrCollectionNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("open collection: %w", err)
|
||||
}
|
||||
err = col.Drop(txn.Context())
|
||||
if err != nil {
|
||||
return fmt.Errorf("drop chat collection: %w", err)
|
||||
}
|
||||
|
||||
col, err = db.OpenCollection(txn.Context(), id+storestate.CollChangeOrders)
|
||||
if errors.Is(err, anystore.ErrCollectionNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("open orders collection: %w", err)
|
||||
}
|
||||
err = col.Drop(txn.Context())
|
||||
if err != nil {
|
||||
return fmt.Errorf("drop chat orders collection: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = txn.Commit()
|
||||
if err != nil {
|
||||
return fmt.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *indexer) addSyncDetails(space clientspace.Space) {
|
||||
typesForSyncRelations := helper.SyncRelationsSmartblockTypes()
|
||||
syncStatus := domain.ObjectSyncStatusSynced
|
||||
|
@ -511,6 +575,7 @@ func (i *indexer) getLatestChecksums(isMarketplace bool) (checksums model.Object
|
|||
LinksErase: ForceLinksReindexCounter,
|
||||
ReindexDeletedObjects: ForceReindexDeletedObjectsCounter,
|
||||
ReindexParticipants: ForceReindexParticipantsCounter,
|
||||
ReindexChats: ForceReindexChatsCounter,
|
||||
}
|
||||
if isMarketplace {
|
||||
checksums.MarketplaceForceReindexCounter = ForceMarketplaceReindex
|
||||
|
|
|
@ -693,7 +693,7 @@ func (mw *Middleware) ObjectImportNotionValidateToken(ctx context.Context,
|
|||
func (mw *Middleware) ObjectImportUseCase(cctx context.Context, req *pb.RpcObjectImportUseCaseRequest) *pb.RpcObjectImportUseCaseResponse {
|
||||
ctx := mw.newContext(cctx)
|
||||
|
||||
response := func(code pb.RpcObjectImportUseCaseResponseErrorCode, err error) *pb.RpcObjectImportUseCaseResponse {
|
||||
response := func(_ string, code pb.RpcObjectImportUseCaseResponseErrorCode, err error) *pb.RpcObjectImportUseCaseResponse {
|
||||
resp := &pb.RpcObjectImportUseCaseResponse{
|
||||
Error: &pb.RpcObjectImportUseCaseResponseError{
|
||||
Code: code,
|
||||
|
|
|
@ -18,6 +18,10 @@ var log = logging.Logger(CName).Desugar()
|
|||
|
||||
const CName = "core.subscription.crossspacesub"
|
||||
|
||||
var (
|
||||
ErrSubscriptionNotFound = fmt.Errorf("subscription not found")
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
app.ComponentRunnable
|
||||
Subscribe(req subscriptionservice.SubscribeRequest) (resp *subscriptionservice.SubscribeResponse, err error)
|
||||
|
@ -110,7 +114,7 @@ func (s *service) Unsubscribe(subId string) error {
|
|||
|
||||
sub, ok := s.subscriptions[subId]
|
||||
if !ok {
|
||||
return fmt.Errorf("subscription not found")
|
||||
return ErrSubscriptionNotFound
|
||||
}
|
||||
|
||||
err := sub.close()
|
||||
|
|
|
@ -1485,6 +1485,7 @@
|
|||
- [Rpc.Chat.SubscribeLastMessages.Response.Error.Code](#anytype-Rpc-Chat-SubscribeLastMessages-Response-Error-Code)
|
||||
- [Rpc.Chat.SubscribeToMessagePreviews.Response.Error.Code](#anytype-Rpc-Chat-SubscribeToMessagePreviews-Response-Error-Code)
|
||||
- [Rpc.Chat.ToggleMessageReaction.Response.Error.Code](#anytype-Rpc-Chat-ToggleMessageReaction-Response-Error-Code)
|
||||
- [Rpc.Chat.Unread.ReadType](#anytype-Rpc-Chat-Unread-ReadType)
|
||||
- [Rpc.Chat.Unread.Response.Error.Code](#anytype-Rpc-Chat-Unread-Response-Error-Code)
|
||||
- [Rpc.Chat.Unsubscribe.Response.Error.Code](#anytype-Rpc-Chat-Unsubscribe-Response-Error-Code)
|
||||
- [Rpc.Chat.UnsubscribeFromMessagePreviews.Response.Error.Code](#anytype-Rpc-Chat-UnsubscribeFromMessagePreviews-Response-Error-Code)
|
||||
|
@ -1817,8 +1818,9 @@
|
|||
- [Event.Chat.Add](#anytype-Event-Chat-Add)
|
||||
- [Event.Chat.Delete](#anytype-Event-Chat-Delete)
|
||||
- [Event.Chat.Update](#anytype-Event-Chat-Update)
|
||||
- [Event.Chat.UpdateMentionReadStatus](#anytype-Event-Chat-UpdateMentionReadStatus)
|
||||
- [Event.Chat.UpdateMessageReadStatus](#anytype-Event-Chat-UpdateMessageReadStatus)
|
||||
- [Event.Chat.UpdateReactions](#anytype-Event-Chat-UpdateReactions)
|
||||
- [Event.Chat.UpdateReadStatus](#anytype-Event-Chat-UpdateReadStatus)
|
||||
- [Event.Chat.UpdateState](#anytype-Event-Chat-UpdateState)
|
||||
- [Event.File](#anytype-Event-File)
|
||||
- [Event.File.LimitReached](#anytype-Event-File-LimitReached)
|
||||
|
@ -10868,7 +10870,7 @@ Get marks list in the selected range in text block.
|
|||
| chatObjectId | [string](#string) | | id of the chat object |
|
||||
| afterOrderId | [string](#string) | | read from this orderId; if empty - read from the beginning of the chat |
|
||||
| beforeOrderId | [string](#string) | | read til this orderId |
|
||||
| lastDbTimestamp | [int64](#int64) | | dbTimestamp from the last processed ChatState event(or GetMessages). Used to prevent race conditions |
|
||||
| lastStateId | [string](#string) | | stateId from the last processed ChatState event(or GetMessages). Used to prevent race conditions |
|
||||
|
||||
|
||||
|
||||
|
@ -11096,7 +11098,8 @@ Get marks list in the selected range in text block.
|
|||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| chatObjectId | [string](#string) | | id of the chat object |
|
||||
| type | [Rpc.Chat.Unread.ReadType](#anytype-Rpc-Chat-Unread-ReadType) | | |
|
||||
| chatObjectId | [string](#string) | | |
|
||||
| afterOrderId | [string](#string) | | |
|
||||
|
||||
|
||||
|
@ -23741,8 +23744,8 @@ Middleware-to-front-end response, that can contain a NULL error or a non-NULL er
|
|||
|
||||
| Name | Number | Description |
|
||||
| ---- | ------ | ----------- |
|
||||
| messages | 0 | |
|
||||
| replies | 1 | |
|
||||
| Messages | 0 | |
|
||||
| Mentions | 1 | |
|
||||
|
||||
|
||||
|
||||
|
@ -23799,6 +23802,18 @@ Middleware-to-front-end response, that can contain a NULL error or a non-NULL er
|
|||
|
||||
|
||||
|
||||
<a name="anytype-Rpc-Chat-Unread-ReadType"></a>
|
||||
|
||||
### Rpc.Chat.Unread.ReadType
|
||||
|
||||
|
||||
| Name | Number | Description |
|
||||
| ---- | ------ | ----------- |
|
||||
| Messages | 0 | |
|
||||
| Mentions | 1 | |
|
||||
|
||||
|
||||
|
||||
<a name="anytype-Rpc-Chat-Unread-Response-Error-Code"></a>
|
||||
|
||||
### Rpc.Chat.Unread.Response.Error.Code
|
||||
|
@ -28666,7 +28681,6 @@ Precondition: user A opened a block
|
|||
| ----- | ---- | ----- | ----------- |
|
||||
| id | [string](#string) | | |
|
||||
| subIds | [string](#string) | repeated | |
|
||||
| state | [model.ChatState](#anytype-model-ChatState) | | Chat state. dbState should be persisted after rendered |
|
||||
|
||||
|
||||
|
||||
|
@ -28690,6 +28704,40 @@ Precondition: user A opened a block
|
|||
|
||||
|
||||
|
||||
<a name="anytype-Event-Chat-UpdateMentionReadStatus"></a>
|
||||
|
||||
### Event.Chat.UpdateMentionReadStatus
|
||||
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| ids | [string](#string) | repeated | |
|
||||
| isRead | [bool](#bool) | | |
|
||||
| subIds | [string](#string) | repeated | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="anytype-Event-Chat-UpdateMessageReadStatus"></a>
|
||||
|
||||
### Event.Chat.UpdateMessageReadStatus
|
||||
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| ids | [string](#string) | repeated | |
|
||||
| isRead | [bool](#bool) | | |
|
||||
| subIds | [string](#string) | repeated | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="anytype-Event-Chat-UpdateReactions"></a>
|
||||
|
||||
### Event.Chat.UpdateReactions
|
||||
|
@ -28707,23 +28755,6 @@ Precondition: user A opened a block
|
|||
|
||||
|
||||
|
||||
<a name="anytype-Event-Chat-UpdateReadStatus"></a>
|
||||
|
||||
### Event.Chat.UpdateReadStatus
|
||||
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| ids | [string](#string) | repeated | |
|
||||
| isRead | [bool](#bool) | | |
|
||||
| subIds | [string](#string) | repeated | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="anytype-Event-Chat-UpdateState"></a>
|
||||
|
||||
### Event.Chat.UpdateState
|
||||
|
@ -28975,7 +29006,8 @@ Precondition: user A opened a block
|
|||
| chatAdd | [Event.Chat.Add](#anytype-Event-Chat-Add) | | |
|
||||
| chatUpdate | [Event.Chat.Update](#anytype-Event-Chat-Update) | | |
|
||||
| chatUpdateReactions | [Event.Chat.UpdateReactions](#anytype-Event-Chat-UpdateReactions) | | |
|
||||
| chatUpdateReadStatus | [Event.Chat.UpdateReadStatus](#anytype-Event-Chat-UpdateReadStatus) | | received to update per-message read status (if needed to highlight the unread messages in the UI) |
|
||||
| chatUpdateMessageReadStatus | [Event.Chat.UpdateMessageReadStatus](#anytype-Event-Chat-UpdateMessageReadStatus) | | received to update per-message read status (if needed to highlight the unread messages in the UI) |
|
||||
| chatUpdateMentionReadStatus | [Event.Chat.UpdateMentionReadStatus](#anytype-Event-Chat-UpdateMentionReadStatus) | | received to update per-message mention read status (if needed to highlight the unread mentions in the UI) |
|
||||
| chatDelete | [Event.Chat.Delete](#anytype-Event-Chat-Delete) | | |
|
||||
| chatStateUpdate | [Event.Chat.UpdateState](#anytype-Event-Chat-UpdateState) | | in case new unread messages received or chat state changed (e.g. message read on another device) |
|
||||
| keyUpdate | [Event.Key.Update](#anytype-Event-Key-Update) | | |
|
||||
|
@ -30127,6 +30159,7 @@ Precondition: user A and user B opened the same block
|
|||
| marketplaceForceReindexCounter | [int32](#int32) | | |
|
||||
| reindexDeletedObjects | [int32](#int32) | | |
|
||||
| reindexParticipants | [int32](#int32) | | |
|
||||
| reindexChats | [int32](#int32) | | |
|
||||
|
||||
|
||||
|
||||
|
@ -30869,16 +30902,17 @@ Used to decode block meta only, without the content itself
|
|||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| id | [string](#string) | | Unique message identifier |
|
||||
| orderId | [string](#string) | | Used for subscriptions |
|
||||
| orderId | [string](#string) | | Lexicographical id for message in order of tree traversal |
|
||||
| creator | [string](#string) | | Identifier for the message creator |
|
||||
| createdAt | [int64](#int64) | | |
|
||||
| modifiedAt | [int64](#int64) | | |
|
||||
| addedAt | [int64](#int64) | | Message received and added to db at |
|
||||
| stateId | [string](#string) | | stateId is ever-increasing id (BSON ObjectId) for this message. Unlike orderId, this ID is ordered by the time messages are added. For example, it's useful to prevent accidental reading of messages from the past when a ChatReadMessages request is sent: a message from the past may appear, but the client is still unaware of it |
|
||||
| replyToMessageId | [string](#string) | | Identifier for the message being replied to |
|
||||
| message | [ChatMessage.MessageContent](#anytype-model-ChatMessage-MessageContent) | | Message content |
|
||||
| attachments | [ChatMessage.Attachment](#anytype-model-ChatMessage-Attachment) | repeated | Attachments slice |
|
||||
| reactions | [ChatMessage.Reactions](#anytype-model-ChatMessage-Reactions) | | Reactions to the message |
|
||||
| read | [bool](#bool) | | Message read status |
|
||||
| mentionRead | [bool](#bool) | | |
|
||||
|
||||
|
||||
|
||||
|
@ -30974,7 +31008,7 @@ Used to decode block meta only, without the content itself
|
|||
| ----- | ---- | ----- | ----------- |
|
||||
| messages | [ChatState.UnreadState](#anytype-model-ChatState-UnreadState) | | unread messages |
|
||||
| mentions | [ChatState.UnreadState](#anytype-model-ChatState-UnreadState) | | unread mentions |
|
||||
| dbTimestamp | [int64](#int64) | | reflects the state of the chat db at the moment of sending response/event that includes this state |
|
||||
| lastStateId | [string](#string) | | reflects the state of the chat db at the moment of sending response/event that includes this state |
|
||||
|
||||
|
||||
|
||||
|
|
3580
pb/commands.pb.go
3580
pb/commands.pb.go
File diff suppressed because it is too large
Load diff
1689
pb/events.pb.go
1689
pb/events.pb.go
File diff suppressed because it is too large
Load diff
|
@ -8312,15 +8312,16 @@ message Rpc {
|
|||
|
||||
message ReadMessages {
|
||||
enum ReadType {
|
||||
messages = 0;
|
||||
replies = 1;
|
||||
Messages = 0;
|
||||
Mentions = 1;
|
||||
}
|
||||
|
||||
message Request {
|
||||
ReadType type = 1;
|
||||
string chatObjectId = 2; // id of the chat object
|
||||
string afterOrderId = 3; // read from this orderId; if empty - read from the beginning of the chat
|
||||
string beforeOrderId = 4; // read til this orderId
|
||||
int64 lastDbTimestamp = 5; // dbTimestamp from the last processed ChatState event(or GetMessages). Used to prevent race conditions
|
||||
string lastStateId = 5; // stateId from the last processed ChatState event(or GetMessages). Used to prevent race conditions
|
||||
}
|
||||
|
||||
message Response {
|
||||
|
@ -8343,8 +8344,14 @@ message Rpc {
|
|||
}
|
||||
|
||||
message Unread {
|
||||
enum ReadType {
|
||||
Messages = 0;
|
||||
Mentions = 1;
|
||||
}
|
||||
|
||||
message Request {
|
||||
string chatObjectId = 2; // id of the chat object
|
||||
ReadType type = 1;
|
||||
string chatObjectId = 2;
|
||||
string afterOrderId = 3;
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,8 @@ message Event {
|
|||
Chat.Add chatAdd = 128;
|
||||
Chat.Update chatUpdate = 129;
|
||||
Chat.UpdateReactions chatUpdateReactions = 130;
|
||||
Chat.UpdateReadStatus chatUpdateReadStatus = 134; // received to update per-message read status (if needed to highlight the unread messages in the UI)
|
||||
Chat.UpdateMessageReadStatus chatUpdateMessageReadStatus = 134; // received to update per-message read status (if needed to highlight the unread messages in the UI)
|
||||
Chat.UpdateMentionReadStatus chatUpdateMentionReadStatus = 135; // received to update per-message mention read status (if needed to highlight the unread mentions in the UI)
|
||||
|
||||
Chat.Delete chatDelete = 131;
|
||||
Chat.UpdateState chatStateUpdate = 133; // in case new unread messages received or chat state changed (e.g. message read on another device)
|
||||
|
@ -137,7 +138,6 @@ message Event {
|
|||
message Delete {
|
||||
string id = 1;
|
||||
repeated string subIds = 2;
|
||||
model.ChatState state = 3; // Chat state. dbState should be persisted after rendered
|
||||
}
|
||||
message Update {
|
||||
string id = 1;
|
||||
|
@ -150,7 +150,12 @@ message Event {
|
|||
repeated string subIds = 3;
|
||||
}
|
||||
|
||||
message UpdateReadStatus {
|
||||
message UpdateMessageReadStatus {
|
||||
repeated string ids = 1;
|
||||
bool isRead = 2;
|
||||
repeated string subIds = 3;
|
||||
}
|
||||
message UpdateMentionReadStatus {
|
||||
repeated string ids = 1;
|
||||
bool isRead = 2;
|
||||
repeated string subIds = 3;
|
||||
|
|
|
@ -90,7 +90,7 @@ func makeFilterByCondition(spaceID string, rawFilter FilterRequest, store Object
|
|||
relationKey := domain.RelationKey(parts[0])
|
||||
nestedRelationKey := domain.RelationKey(parts[1])
|
||||
|
||||
if rawFilter.Condition == model.BlockContentDataviewFilter_NotEqual {
|
||||
if rawFilter.Condition == model.BlockContentDataviewFilter_NotEqual || rawFilter.Condition == model.BlockContentDataviewFilter_NotIn {
|
||||
return makeFilterNestedNotIn(spaceID, rawFilter, store, relationKey, nestedRelationKey)
|
||||
} else {
|
||||
return makeFilterNestedIn(spaceID, rawFilter, store, relationKey, nestedRelationKey)
|
||||
|
@ -858,13 +858,29 @@ type FilterNestedNotIn struct {
|
|||
IDs []string
|
||||
}
|
||||
|
||||
func negativeConditionToPositive(cond model.BlockContentDataviewFilterCondition) (model.BlockContentDataviewFilterCondition, error) {
|
||||
switch cond {
|
||||
case model.BlockContentDataviewFilter_NotEqual:
|
||||
return model.BlockContentDataviewFilter_Equal, nil
|
||||
case model.BlockContentDataviewFilter_NotIn:
|
||||
return model.BlockContentDataviewFilter_In, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("condition %d is not supported", cond)
|
||||
}
|
||||
}
|
||||
|
||||
func makeFilterNestedNotIn(spaceID string, rawFilter FilterRequest, store ObjectStore, relationKey domain.RelationKey, nestedRelationKey domain.RelationKey) (Filter, error) {
|
||||
rawNestedFilter := rawFilter
|
||||
rawNestedFilter.RelationKey = nestedRelationKey
|
||||
|
||||
cond, err := negativeConditionToPositive(rawFilter.Condition)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert condition: %w", err)
|
||||
}
|
||||
|
||||
subQueryRawFilter := rawFilter
|
||||
subQueryRawFilter.RelationKey = nestedRelationKey
|
||||
subQueryRawFilter.Condition = model.BlockContentDataviewFilter_Equal
|
||||
subQueryRawFilter.Condition = cond
|
||||
|
||||
subQueryFilter, err := MakeFilter(spaceID, subQueryRawFilter, store)
|
||||
if err != nil {
|
||||
|
|
|
@ -1263,3 +1263,67 @@ func TestDsObjectStore_QueryAndProcess(t *testing.T) {
|
|||
assert.Equal(t, []string{"id3"}, ids)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNestedFilters(t *testing.T) {
|
||||
t.Run("not in", func(t *testing.T) {
|
||||
store := NewStoreFixture(t)
|
||||
|
||||
store.AddObjects(t, []TestObject{
|
||||
{
|
||||
bundle.RelationKeyId: domain.String("id1"),
|
||||
bundle.RelationKeyType: domain.String("templateType"),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: domain.String("id2"),
|
||||
bundle.RelationKeyType: domain.String("pageType"),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: domain.String("id3"),
|
||||
bundle.RelationKeyType: domain.String("pageType"),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: domain.String("id4"),
|
||||
bundle.RelationKeyType: domain.String("hiddenType"),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: domain.String("templateType"),
|
||||
bundle.RelationKeyUniqueKey: domain.String("ot-template"),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: domain.String("pageType"),
|
||||
bundle.RelationKeyUniqueKey: domain.String("ot-page"),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: domain.String("hiddenType"),
|
||||
bundle.RelationKeyUniqueKey: domain.String("ot-hidden"),
|
||||
},
|
||||
})
|
||||
|
||||
got, err := store.Query(database.Query{
|
||||
Filters: []database.FilterRequest{
|
||||
{
|
||||
RelationKey: "type.uniqueKey",
|
||||
Condition: model.BlockContentDataviewFilter_NotIn,
|
||||
Value: domain.StringList([]string{"ot-hidden", "ot-template"}),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assertRecordsHaveIds(t, got, []string{"id2", "id3", "templateType", "pageType", "hiddenType"})
|
||||
})
|
||||
}
|
||||
|
||||
func assertRecordsHaveIds(t *testing.T, records []database.Record, wantIds []string) {
|
||||
require.Equal(t, len(wantIds), len(records))
|
||||
|
||||
gotIds := map[string]struct{}{}
|
||||
for _, r := range records {
|
||||
gotIds[r.Details.GetString(bundle.RelationKeyId)] = struct{}{}
|
||||
}
|
||||
|
||||
for _, id := range wantIds {
|
||||
_, ok := gotIds[id]
|
||||
require.True(t, ok)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -453,6 +453,7 @@ type ObjectStoreChecksums struct {
|
|||
MarketplaceForceReindexCounter int32 `protobuf:"varint,15,opt,name=marketplaceForceReindexCounter,proto3" json:"marketplaceForceReindexCounter,omitempty"`
|
||||
ReindexDeletedObjects int32 `protobuf:"varint,16,opt,name=reindexDeletedObjects,proto3" json:"reindexDeletedObjects,omitempty"`
|
||||
ReindexParticipants int32 `protobuf:"varint,17,opt,name=reindexParticipants,proto3" json:"reindexParticipants,omitempty"`
|
||||
ReindexChats int32 `protobuf:"varint,18,opt,name=reindexChats,proto3" json:"reindexChats,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ObjectStoreChecksums) Reset() { *m = ObjectStoreChecksums{} }
|
||||
|
@ -607,6 +608,13 @@ func (m *ObjectStoreChecksums) GetReindexParticipants() int32 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (m *ObjectStoreChecksums) GetReindexChats() int32 {
|
||||
if m != nil {
|
||||
return m.ReindexChats
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*ObjectInfo)(nil), "anytype.model.ObjectInfo")
|
||||
proto.RegisterType((*ObjectDetails)(nil), "anytype.model.ObjectDetails")
|
||||
|
@ -623,55 +631,56 @@ func init() {
|
|||
}
|
||||
|
||||
var fileDescriptor_9c35df71910469a5 = []byte{
|
||||
// 766 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcb, 0x6e, 0xd3, 0x40,
|
||||
0x14, 0xad, 0x93, 0x26, 0x69, 0x6e, 0x48, 0x1f, 0x53, 0x10, 0x43, 0x41, 0x96, 0x15, 0x55, 0x28,
|
||||
0xaa, 0x20, 0x81, 0x3e, 0x36, 0x2c, 0x40, 0x6a, 0x4b, 0xa5, 0x42, 0xa5, 0x20, 0xb7, 0x08, 0x89,
|
||||
0x9d, 0x1d, 0x4f, 0xda, 0x21, 0x13, 0x8f, 0xe5, 0x19, 0xa3, 0x66, 0xc1, 0x92, 0x0d, 0x2b, 0x7e,
|
||||
0x80, 0xff, 0x61, 0xd9, 0x25, 0x4b, 0xd4, 0xfe, 0x01, 0xe2, 0x03, 0x90, 0x67, 0x9c, 0xd4, 0x71,
|
||||
0xdd, 0x14, 0x09, 0x96, 0x3e, 0xf7, 0x9c, 0xe3, 0x33, 0xf7, 0x7a, 0xae, 0xa1, 0x19, 0xf4, 0x8f,
|
||||
0xdb, 0x8c, 0xba, 0xed, 0xc0, 0x6d, 0x0f, 0xb8, 0x47, 0x58, 0x3b, 0x08, 0xb9, 0xe4, 0xa2, 0xcd,
|
||||
0x78, 0xd7, 0x61, 0x42, 0xf2, 0x90, 0xb4, 0x14, 0x82, 0xea, 0x8e, 0x3f, 0x94, 0xc3, 0x80, 0xb4,
|
||||
0x14, 0x6d, 0xe5, 0xc1, 0x31, 0xe7, 0xc7, 0x8c, 0x68, 0xba, 0x1b, 0xf5, 0xda, 0x42, 0x86, 0x51,
|
||||
0x57, 0x6a, 0xf2, 0xca, 0xea, 0x75, 0xb6, 0xea, 0x41, 0x68, 0x56, 0xe3, 0x97, 0x01, 0xd0, 0x71,
|
||||
0x3f, 0x90, 0xae, 0xdc, 0xf7, 0x7b, 0x1c, 0xcd, 0x43, 0x81, 0x7a, 0xd8, 0xb0, 0x8c, 0x66, 0xd5,
|
||||
0x2e, 0x50, 0x0f, 0x3d, 0x84, 0x79, 0xae, 0xaa, 0x47, 0xc3, 0x80, 0xbc, 0x0d, 0x99, 0xc0, 0x05,
|
||||
0xab, 0xd8, 0xac, 0xda, 0x19, 0x14, 0x3d, 0x85, 0x8a, 0x47, 0xa4, 0x43, 0x99, 0xc0, 0x45, 0xcb,
|
||||
0x68, 0xd6, 0xd6, 0xef, 0xb6, 0x74, 0xb8, 0xd6, 0x28, 0x5c, 0xeb, 0x50, 0x85, 0xb3, 0x47, 0x3c,
|
||||
0xb4, 0x05, 0xd5, 0x90, 0x30, 0x47, 0x52, 0xee, 0x0b, 0x3c, 0x6b, 0x15, 0x95, 0x68, 0xe2, 0x80,
|
||||
0x2d, 0x3b, 0xa9, 0xdb, 0x97, 0x4c, 0x84, 0xa1, 0x22, 0x7c, 0x1a, 0x04, 0x44, 0xe2, 0x92, 0x8a,
|
||||
0x39, 0x7a, 0x44, 0x4d, 0x58, 0x38, 0x71, 0xc4, 0xbe, 0xef, 0xf2, 0xc8, 0xf7, 0x0e, 0xa8, 0xdf,
|
||||
0x17, 0xb8, 0x6c, 0x19, 0xcd, 0x39, 0x3b, 0x0b, 0x37, 0xb6, 0xa1, 0xae, 0xcf, 0xbc, 0x9b, 0x64,
|
||||
0x49, 0xc5, 0x37, 0xfe, 0x2e, 0x7e, 0xa3, 0x03, 0x35, 0xed, 0xa1, 0x2c, 0x91, 0x09, 0x40, 0xf5,
|
||||
0x2b, 0xf6, 0x77, 0x63, 0x93, 0xb8, 0x49, 0x29, 0x04, 0x59, 0x50, 0xe3, 0x91, 0x1c, 0x13, 0x74,
|
||||
0x17, 0xd3, 0x50, 0xe3, 0x13, 0x2c, 0xa4, 0x0c, 0xd5, 0x34, 0x36, 0xa0, 0x92, 0x58, 0x28, 0xc7,
|
||||
0xda, 0xfa, 0xbd, 0x4c, 0x83, 0x2e, 0x27, 0x67, 0x8f, 0x98, 0x68, 0x0b, 0xe6, 0x46, 0xb6, 0xea,
|
||||
0x35, 0x53, 0x55, 0x63, 0x6a, 0xe3, 0x8b, 0x01, 0xcb, 0x97, 0x85, 0x77, 0x54, 0x9e, 0xe8, 0x83,
|
||||
0x65, 0xbf, 0x88, 0xc7, 0x30, 0x4b, 0xfd, 0x1e, 0xc7, 0x05, 0xd5, 0xa7, 0x29, 0xd6, 0x8a, 0x86,
|
||||
0x36, 0xa1, 0xc4, 0xd4, 0x28, 0xf4, 0x67, 0x61, 0xe6, 0xf2, 0xc7, 0x27, 0xb6, 0x35, 0xb9, 0xf1,
|
||||
0xcd, 0x80, 0xfb, 0x93, 0x61, 0x3a, 0x49, 0xce, 0xff, 0x12, 0xea, 0x05, 0xd4, 0x79, 0xda, 0x0f,
|
||||
0x17, 0x6f, 0xea, 0xd3, 0x24, 0xbf, 0xf1, 0xd9, 0x00, 0x73, 0x4a, 0xbe, 0x78, 0xe0, 0xff, 0x18,
|
||||
0x71, 0x35, 0x2f, 0x62, 0x35, 0x9b, 0xe3, 0x77, 0x19, 0x6e, 0x6b, 0xe9, 0x61, 0xbc, 0x26, 0x76,
|
||||
0x4e, 0x48, 0xb7, 0x2f, 0xa2, 0x81, 0x40, 0x2d, 0x40, 0x6e, 0xe4, 0x7b, 0x8c, 0x78, 0x9d, 0xf1,
|
||||
0x45, 0x15, 0x49, 0x9a, 0x9c, 0x0a, 0x5a, 0x83, 0xc5, 0x04, 0xb5, 0xc7, 0x77, 0xb2, 0xa0, 0xd8,
|
||||
0x57, 0xf0, 0x78, 0x27, 0x24, 0xd8, 0x81, 0x33, 0xe4, 0x91, 0xd4, 0xb3, 0xad, 0xda, 0x19, 0x14,
|
||||
0x3d, 0x87, 0x15, 0xbd, 0x25, 0xc4, 0x1e, 0x0f, 0xbb, 0xc4, 0x26, 0xd4, 0xf7, 0xc8, 0xe9, 0x0e,
|
||||
0x8f, 0x7c, 0x49, 0x42, 0x3c, 0x6b, 0x19, 0xcd, 0x92, 0x3d, 0x85, 0x81, 0x9e, 0x01, 0xee, 0x51,
|
||||
0x46, 0x72, 0xd5, 0x25, 0xa5, 0xbe, 0xb6, 0x8e, 0x1e, 0xc1, 0x12, 0xf5, 0x4e, 0x6d, 0xe2, 0x46,
|
||||
0x94, 0x79, 0x23, 0x51, 0x59, 0x89, 0xae, 0x16, 0xe2, 0xcd, 0xd1, 0x8b, 0x18, 0x93, 0xe4, 0x54,
|
||||
0x26, 0x15, 0x5c, 0x51, 0xdc, 0x2c, 0x1c, 0x8f, 0x65, 0x04, 0xbd, 0x0c, 0x1d, 0x41, 0x70, 0x4d,
|
||||
0xf1, 0x26, 0xc1, 0x54, 0x37, 0x8f, 0xc8, 0x20, 0x60, 0x8e, 0x24, 0x02, 0xcf, 0x4d, 0x74, 0x73,
|
||||
0x8c, 0xa7, 0xba, 0xa9, 0xe7, 0x21, 0x70, 0x55, 0x59, 0x66, 0x50, 0xf4, 0x0a, 0x2c, 0x75, 0xda,
|
||||
0x78, 0xce, 0xaf, 0xc9, 0x30, 0xb7, 0x2b, 0xa0, 0x94, 0x37, 0xf2, 0xe2, 0xaf, 0xc3, 0x09, 0x49,
|
||||
0x87, 0x79, 0x7b, 0x31, 0xd3, 0x26, 0x03, 0xfe, 0x91, 0x78, 0xf8, 0x96, 0x5a, 0x96, 0x39, 0x95,
|
||||
0x78, 0x92, 0x4e, 0x48, 0x76, 0x09, 0x23, 0x72, 0x1c, 0x28, 0xb1, 0x24, 0x1e, 0xae, 0x2b, 0xdd,
|
||||
0x14, 0x46, 0xbc, 0x1c, 0xd5, 0xbd, 0xd6, 0x2d, 0x9b, 0x57, 0x29, 0x53, 0x08, 0xda, 0x03, 0x73,
|
||||
0xe0, 0x84, 0x7d, 0x22, 0x03, 0xe6, 0x74, 0x49, 0xde, 0xc9, 0x16, 0x94, 0xe6, 0x06, 0x16, 0xda,
|
||||
0x84, 0x3b, 0xa1, 0x46, 0x26, 0x93, 0xe0, 0x45, 0x25, 0xcf, 0x2f, 0xa2, 0x27, 0xb0, 0x9c, 0x14,
|
||||
0xde, 0x38, 0xa1, 0xa4, 0x5d, 0x1a, 0x38, 0xbe, 0x14, 0x78, 0x49, 0x69, 0xf2, 0x4a, 0xdb, 0x6b,
|
||||
0xdf, 0xcf, 0x4d, 0xe3, 0xec, 0xdc, 0x34, 0x7e, 0x9e, 0x9b, 0xc6, 0xd7, 0x0b, 0x73, 0xe6, 0xec,
|
||||
0xc2, 0x9c, 0xf9, 0x71, 0x61, 0xce, 0xbc, 0x5f, 0xcc, 0xfe, 0x74, 0xdd, 0xb2, 0xfa, 0x83, 0x6c,
|
||||
0xfc, 0x09, 0x00, 0x00, 0xff, 0xff, 0x63, 0x84, 0x4f, 0x64, 0xe6, 0x07, 0x00, 0x00,
|
||||
// 783 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x4f, 0xdb, 0x48,
|
||||
0x14, 0xc6, 0x09, 0x21, 0xe4, 0x85, 0xf0, 0x63, 0xd8, 0xd5, 0xce, 0xb2, 0x2b, 0xcb, 0xb2, 0xd0,
|
||||
0x2a, 0x42, 0xbb, 0xc9, 0x96, 0x1f, 0x97, 0x1e, 0x5a, 0x09, 0x28, 0x12, 0x2d, 0x52, 0x2a, 0x43,
|
||||
0x55, 0xa9, 0x37, 0x3b, 0x9e, 0x90, 0x69, 0x26, 0x1e, 0xcb, 0x33, 0xae, 0xc8, 0xa1, 0xc7, 0x5e,
|
||||
0x7a, 0xea, 0xb1, 0x97, 0xfe, 0x3f, 0x3d, 0x72, 0xec, 0xb1, 0x82, 0xff, 0xa0, 0x7f, 0x41, 0xe5,
|
||||
0x19, 0x27, 0x38, 0xc6, 0x84, 0x4a, 0xed, 0xd1, 0xdf, 0xfb, 0xbe, 0xcf, 0xdf, 0xbc, 0xe7, 0x79,
|
||||
0x86, 0x66, 0x38, 0x38, 0x6f, 0x33, 0xea, 0xb5, 0x43, 0xaf, 0x3d, 0xe4, 0x3e, 0x61, 0xed, 0x30,
|
||||
0xe2, 0x92, 0x8b, 0x36, 0xe3, 0x5d, 0x97, 0x09, 0xc9, 0x23, 0xd2, 0x52, 0x08, 0x6a, 0xb8, 0xc1,
|
||||
0x48, 0x8e, 0x42, 0xd2, 0x52, 0xb4, 0x8d, 0xbf, 0xcf, 0x39, 0x3f, 0x67, 0x44, 0xd3, 0xbd, 0xb8,
|
||||
0xd7, 0x16, 0x32, 0x8a, 0xbb, 0x52, 0x93, 0x37, 0x36, 0xef, 0xb2, 0x55, 0x0f, 0x42, 0xb3, 0xec,
|
||||
0x6f, 0x06, 0x40, 0xc7, 0x7b, 0x4d, 0xba, 0xf2, 0x38, 0xe8, 0x71, 0xb4, 0x0c, 0x25, 0xea, 0x63,
|
||||
0xc3, 0x32, 0x9a, 0x35, 0xa7, 0x44, 0x7d, 0xf4, 0x0f, 0x2c, 0x73, 0x55, 0x3d, 0x1b, 0x85, 0xe4,
|
||||
0x45, 0xc4, 0x04, 0x2e, 0x59, 0xe5, 0x66, 0xcd, 0xc9, 0xa1, 0xe8, 0x01, 0x54, 0x7d, 0x22, 0x5d,
|
||||
0xca, 0x04, 0x2e, 0x5b, 0x46, 0xb3, 0xbe, 0xfd, 0x47, 0x4b, 0x87, 0x6b, 0x8d, 0xc3, 0xb5, 0x4e,
|
||||
0x55, 0x38, 0x67, 0xcc, 0x43, 0x7b, 0x50, 0x8b, 0x08, 0x73, 0x25, 0xe5, 0x81, 0xc0, 0xf3, 0x56,
|
||||
0x59, 0x89, 0xa6, 0x0e, 0xd8, 0x72, 0xd2, 0xba, 0x73, 0xc3, 0x44, 0x18, 0xaa, 0x22, 0xa0, 0x61,
|
||||
0x48, 0x24, 0xae, 0xa8, 0x98, 0xe3, 0x47, 0xd4, 0x84, 0x95, 0xbe, 0x2b, 0x8e, 0x03, 0x8f, 0xc7,
|
||||
0x81, 0x7f, 0x42, 0x83, 0x81, 0xc0, 0x0b, 0x96, 0xd1, 0x5c, 0x74, 0xf2, 0xb0, 0xbd, 0x0f, 0x0d,
|
||||
0x7d, 0xe6, 0xc3, 0x34, 0x4b, 0x26, 0xbe, 0xf1, 0x63, 0xf1, 0xed, 0x0e, 0xd4, 0xb5, 0x87, 0xb2,
|
||||
0x44, 0x26, 0x00, 0xd5, 0xaf, 0x38, 0x3e, 0x4c, 0x4c, 0x92, 0x26, 0x65, 0x10, 0x64, 0x41, 0x9d,
|
||||
0xc7, 0x72, 0x42, 0xd0, 0x5d, 0xcc, 0x42, 0xf6, 0x5b, 0x58, 0xc9, 0x18, 0xaa, 0x69, 0xec, 0x40,
|
||||
0x35, 0xb5, 0x50, 0x8e, 0xf5, 0xed, 0x3f, 0x73, 0x0d, 0xba, 0x99, 0x9c, 0x33, 0x66, 0xa2, 0x3d,
|
||||
0x58, 0x1c, 0xdb, 0xaa, 0xd7, 0xcc, 0x54, 0x4d, 0xa8, 0xf6, 0x7b, 0x03, 0xd6, 0x6f, 0x0a, 0x2f,
|
||||
0xa9, 0xec, 0xeb, 0x83, 0xe5, 0xbf, 0x88, 0xff, 0x60, 0x9e, 0x06, 0x3d, 0x8e, 0x4b, 0xaa, 0x4f,
|
||||
0x33, 0xac, 0x15, 0x0d, 0xed, 0x42, 0x85, 0xa9, 0x51, 0xe8, 0xcf, 0xc2, 0x2c, 0xe4, 0x4f, 0x4e,
|
||||
0xec, 0x68, 0xb2, 0xfd, 0xc9, 0x80, 0xbf, 0xa6, 0xc3, 0x74, 0xd2, 0x9c, 0xbf, 0x24, 0xd4, 0x63,
|
||||
0x68, 0xf0, 0xac, 0x1f, 0x2e, 0xdf, 0xd7, 0xa7, 0x69, 0xbe, 0xfd, 0xce, 0x00, 0x73, 0x46, 0xbe,
|
||||
0x64, 0xe0, 0x3f, 0x19, 0x71, 0xb3, 0x28, 0x62, 0x2d, 0x9f, 0xe3, 0x63, 0x15, 0x7e, 0xd3, 0xd2,
|
||||
0xd3, 0x64, 0x4d, 0x1c, 0xf4, 0x49, 0x77, 0x20, 0xe2, 0xa1, 0x40, 0x2d, 0x40, 0x5e, 0x1c, 0xf8,
|
||||
0x8c, 0xf8, 0x9d, 0xc9, 0x45, 0x15, 0x69, 0x9a, 0x82, 0x0a, 0xda, 0x82, 0xd5, 0x14, 0x75, 0x26,
|
||||
0x77, 0xb2, 0xa4, 0xd8, 0xb7, 0xf0, 0x64, 0x27, 0xa4, 0xd8, 0x89, 0x3b, 0xe2, 0xb1, 0xd4, 0xb3,
|
||||
0xad, 0x39, 0x39, 0x14, 0x3d, 0x82, 0x0d, 0xbd, 0x25, 0xc4, 0x11, 0x8f, 0xba, 0xc4, 0x21, 0x34,
|
||||
0xf0, 0xc9, 0xc5, 0x01, 0x8f, 0x03, 0x49, 0x22, 0x3c, 0x6f, 0x19, 0xcd, 0x8a, 0x33, 0x83, 0x81,
|
||||
0x1e, 0x02, 0xee, 0x51, 0x46, 0x0a, 0xd5, 0x15, 0xa5, 0xbe, 0xb3, 0x8e, 0xfe, 0x85, 0x35, 0xea,
|
||||
0x5f, 0x38, 0xc4, 0x8b, 0x29, 0xf3, 0xc7, 0xa2, 0x05, 0x25, 0xba, 0x5d, 0x48, 0x36, 0x47, 0x2f,
|
||||
0x66, 0x4c, 0x92, 0x0b, 0x99, 0x56, 0x70, 0x55, 0x71, 0xf3, 0x70, 0x32, 0x96, 0x31, 0xf4, 0x24,
|
||||
0x72, 0x05, 0xc1, 0x75, 0xc5, 0x9b, 0x06, 0x33, 0xdd, 0x3c, 0x23, 0xc3, 0x90, 0xb9, 0x92, 0x08,
|
||||
0xbc, 0x38, 0xd5, 0xcd, 0x09, 0x9e, 0xe9, 0xa6, 0x9e, 0x87, 0xc0, 0x35, 0x65, 0x99, 0x43, 0xd1,
|
||||
0x53, 0xb0, 0xd4, 0x69, 0x93, 0x39, 0x3f, 0x23, 0xa3, 0xc2, 0xae, 0x80, 0x52, 0xde, 0xcb, 0x4b,
|
||||
0xbe, 0x0e, 0x37, 0x22, 0x1d, 0xe6, 0x1f, 0x25, 0x4c, 0x87, 0x0c, 0xf9, 0x1b, 0xe2, 0xe3, 0x25,
|
||||
0xb5, 0x2c, 0x0b, 0x2a, 0xc9, 0x24, 0xdd, 0x88, 0x1c, 0x12, 0x46, 0xe4, 0x24, 0x50, 0x6a, 0x49,
|
||||
0x7c, 0xdc, 0x50, 0xba, 0x19, 0x8c, 0x64, 0x39, 0xaa, 0x7b, 0xad, 0x5b, 0xb6, 0xac, 0x52, 0x66,
|
||||
0x10, 0x74, 0x04, 0xe6, 0xd0, 0x8d, 0x06, 0x44, 0x86, 0xcc, 0xed, 0x92, 0xa2, 0x93, 0xad, 0x28,
|
||||
0xcd, 0x3d, 0x2c, 0xb4, 0x0b, 0xbf, 0x47, 0x1a, 0x99, 0x4e, 0x82, 0x57, 0x95, 0xbc, 0xb8, 0x88,
|
||||
0xfe, 0x87, 0xf5, 0xb4, 0xf0, 0xdc, 0x8d, 0x24, 0xed, 0xd2, 0xd0, 0x0d, 0xa4, 0xc0, 0x6b, 0x4a,
|
||||
0x53, 0x54, 0x42, 0x36, 0x2c, 0xa5, 0xf0, 0x41, 0xdf, 0x95, 0x02, 0x23, 0x45, 0x9d, 0xc2, 0xf6,
|
||||
0xb7, 0x3e, 0x5f, 0x99, 0xc6, 0xe5, 0x95, 0x69, 0x7c, 0xbd, 0x32, 0x8d, 0x0f, 0xd7, 0xe6, 0xdc,
|
||||
0xe5, 0xb5, 0x39, 0xf7, 0xe5, 0xda, 0x9c, 0x7b, 0xb5, 0x9a, 0xff, 0x31, 0x7b, 0x0b, 0xea, 0x2f,
|
||||
0xb3, 0xf3, 0x3d, 0x00, 0x00, 0xff, 0xff, 0x3c, 0xb3, 0x53, 0xec, 0x0a, 0x08, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *ObjectInfo) Marshal() (dAtA []byte, err error) {
|
||||
|
@ -1064,6 +1073,13 @@ func (m *ObjectStoreChecksums) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
|||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.ReindexChats != 0 {
|
||||
i = encodeVarintLocalstore(dAtA, i, uint64(m.ReindexChats))
|
||||
i--
|
||||
dAtA[i] = 0x1
|
||||
i--
|
||||
dAtA[i] = 0x90
|
||||
}
|
||||
if m.ReindexParticipants != 0 {
|
||||
i = encodeVarintLocalstore(dAtA, i, uint64(m.ReindexParticipants))
|
||||
i--
|
||||
|
@ -1404,6 +1420,9 @@ func (m *ObjectStoreChecksums) Size() (n int) {
|
|||
if m.ReindexParticipants != 0 {
|
||||
n += 2 + sovLocalstore(uint64(m.ReindexParticipants))
|
||||
}
|
||||
if m.ReindexChats != 0 {
|
||||
n += 2 + sovLocalstore(uint64(m.ReindexChats))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -2829,6 +2848,25 @@ func (m *ObjectStoreChecksums) Unmarshal(dAtA []byte) error {
|
|||
break
|
||||
}
|
||||
}
|
||||
case 18:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ReindexChats", wireType)
|
||||
}
|
||||
m.ReindexChats = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowLocalstore
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.ReindexChats |= int32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipLocalstore(dAtA[iNdEx:])
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -65,4 +65,5 @@ message ObjectStoreChecksums {
|
|||
int32 marketplaceForceReindexCounter = 15;
|
||||
int32 reindexDeletedObjects = 16;
|
||||
int32 reindexParticipants = 17;
|
||||
int32 reindexChats = 18;
|
||||
}
|
||||
|
|
|
@ -1394,21 +1394,25 @@ message ChatState {
|
|||
}
|
||||
UnreadState messages = 1; // unread messages
|
||||
UnreadState mentions = 2; // unread mentions
|
||||
int64 dbTimestamp = 3; // reflects the state of the chat db at the moment of sending response/event that includes this state
|
||||
string lastStateId = 3; // reflects the state of the chat db at the moment of sending response/event that includes this state
|
||||
}
|
||||
|
||||
message ChatMessage {
|
||||
string id = 1; // Unique message identifier
|
||||
string orderId = 2; // Used for subscriptions
|
||||
string orderId = 2; // Lexicographical id for message in order of tree traversal
|
||||
string creator = 3; // Identifier for the message creator
|
||||
int64 createdAt = 4;
|
||||
int64 modifiedAt = 9;
|
||||
int64 addedAt = 11; // Message received and added to db at
|
||||
|
||||
// stateId is ever-increasing id (BSON ObjectId) for this message. Unlike orderId, this ID is ordered by the time messages are added. For example, it's useful to prevent accidental reading of messages from the past when a ChatReadMessages request is sent: a message from the past may appear, but the client is still unaware of it
|
||||
string stateId = 11;
|
||||
|
||||
string replyToMessageId = 5; // Identifier for the message being replied to
|
||||
MessageContent message = 6; // Message content
|
||||
repeated Attachment attachments = 7; // Attachments slice
|
||||
Reactions reactions = 8; // Reactions to the message
|
||||
bool read = 10; // Message read status
|
||||
bool mentionRead = 12;
|
||||
message MessageContent {
|
||||
string text = 1; // The text content of the message part
|
||||
Block.Content.Text.Style style = 2; // The style/type of the message part
|
||||
|
|
|
@ -87,7 +87,7 @@ func createAccountAndStartApp(t *testing.T, defaultUsecase pb.RpcObjectImportUse
|
|||
eventQueue: eventQueue,
|
||||
}
|
||||
objCreator := getService[builtinobjects.BuiltinObjects](testApp)
|
||||
_, err = objCreator.CreateObjectsForUseCase(session.NewContext(), acc.Info.AccountSpaceId, defaultUsecase)
|
||||
_, _, err = objCreator.CreateObjectsForUseCase(session.NewContext(), acc.Info.AccountSpaceId, defaultUsecase)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
|
|
|
@ -87,7 +87,7 @@ var (
|
|||
type BuiltinObjects interface {
|
||||
app.Component
|
||||
|
||||
CreateObjectsForUseCase(ctx session.Context, spaceID string, req pb.RpcObjectImportUseCaseRequestUseCase) (code pb.RpcObjectImportUseCaseResponseErrorCode, err error)
|
||||
CreateObjectsForUseCase(ctx session.Context, spaceID string, req pb.RpcObjectImportUseCaseRequestUseCase) (dashboardId string, code pb.RpcObjectImportUseCaseResponseErrorCode, err error)
|
||||
CreateObjectsForExperience(ctx context.Context, spaceID, url, title string, newSpace bool) (err error)
|
||||
InjectMigrationDashboard(spaceID string) error
|
||||
}
|
||||
|
@ -127,21 +127,21 @@ func (b *builtinObjects) CreateObjectsForUseCase(
|
|||
ctx session.Context,
|
||||
spaceID string,
|
||||
useCase pb.RpcObjectImportUseCaseRequestUseCase,
|
||||
) (code pb.RpcObjectImportUseCaseResponseErrorCode, err error) {
|
||||
) (dashboardId string, code pb.RpcObjectImportUseCaseResponseErrorCode, err error) {
|
||||
if useCase == pb.RpcObjectImportUseCaseRequest_NONE {
|
||||
return pb.RpcObjectImportUseCaseResponseError_NULL, nil
|
||||
return "", pb.RpcObjectImportUseCaseResponseError_NULL, nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
archive, found := archives[useCase]
|
||||
if !found {
|
||||
return pb.RpcObjectImportUseCaseResponseError_BAD_INPUT,
|
||||
return "", pb.RpcObjectImportUseCaseResponseError_BAD_INPUT,
|
||||
fmt.Errorf("failed to import builtinObjects: invalid Use Case value: %v", useCase)
|
||||
}
|
||||
|
||||
if err = b.inject(ctx, spaceID, useCase, archive); err != nil {
|
||||
return pb.RpcObjectImportUseCaseResponseError_UNKNOWN_ERROR,
|
||||
if dashboardId, err = b.inject(ctx, spaceID, useCase, archive); err != nil {
|
||||
return "", pb.RpcObjectImportUseCaseResponseError_UNKNOWN_ERROR,
|
||||
fmt.Errorf("failed to import builtinObjects for Use Case %s: %w",
|
||||
pb.RpcObjectImportUseCaseRequestUseCase_name[int32(useCase)], err)
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ func (b *builtinObjects) CreateObjectsForUseCase(
|
|||
log.Debugf("built-in objects injection time exceeded timeout of %s and is %s", injectionTimeout.String(), spent.String())
|
||||
}
|
||||
|
||||
return pb.RpcObjectImportUseCaseResponseError_NULL, nil
|
||||
return dashboardId, pb.RpcObjectImportUseCaseResponseError_NULL, nil
|
||||
}
|
||||
|
||||
func (b *builtinObjects) CreateObjectsForExperience(ctx context.Context, spaceID, url, title string, isNewSpace bool) (err error) {
|
||||
|
@ -223,21 +223,22 @@ func (b *builtinObjects) provideNotification(spaceID string, progress process.Pr
|
|||
}
|
||||
|
||||
func (b *builtinObjects) InjectMigrationDashboard(spaceID string) error {
|
||||
return b.inject(nil, spaceID, migrationUseCase, migrationDashboardZip)
|
||||
_, err := b.inject(nil, spaceID, migrationUseCase, migrationDashboardZip)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *builtinObjects) inject(ctx session.Context, spaceID string, useCase pb.RpcObjectImportUseCaseRequestUseCase, archive []byte) (err error) {
|
||||
func (b *builtinObjects) inject(ctx session.Context, spaceID string, useCase pb.RpcObjectImportUseCaseRequestUseCase, archive []byte) (dashboardId string, err error) {
|
||||
path := filepath.Join(b.tempDirService.TempDir(), time.Now().Format("tmp.20060102.150405.99")+".zip")
|
||||
if err = os.WriteFile(path, archive, 0644); err != nil {
|
||||
return fmt.Errorf("failed to save use case archive to temporary file: %w", err)
|
||||
return "", fmt.Errorf("failed to save use case archive to temporary file: %w", err)
|
||||
}
|
||||
|
||||
if err = b.importArchive(context.Background(), spaceID, path, "", pb.RpcObjectImportRequestPbParams_SPACE, nil, false); err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO: GO-2627 Home page handling should be moved to importer
|
||||
b.handleHomePage(path, spaceID, func() {
|
||||
dashboardId = b.handleHomePage(path, spaceID, func() {
|
||||
if rmErr := os.Remove(path); rmErr != nil {
|
||||
log.Errorf("failed to remove temporary file: %v", anyerror.CleanupError(rmErr))
|
||||
}
|
||||
|
@ -259,7 +260,7 @@ func (b *builtinObjects) importArchive(
|
|||
importRequest := &importer.ImportRequest{
|
||||
RpcObjectImportRequest: &pb.RpcObjectImportRequest{
|
||||
SpaceId: spaceID,
|
||||
UpdateExistingObjects: false,
|
||||
UpdateExistingObjects: true,
|
||||
Type: model.Import_Pb,
|
||||
Mode: pb.RpcObjectImportRequest_ALL_OR_NOTHING,
|
||||
NoProgress: progress == nil,
|
||||
|
@ -282,7 +283,7 @@ func (b *builtinObjects) importArchive(
|
|||
return res.Err
|
||||
}
|
||||
|
||||
func (b *builtinObjects) handleHomePage(path, spaceId string, removeFunc func(), isMigration bool) {
|
||||
func (b *builtinObjects) handleHomePage(path, spaceId string, removeFunc func(), isMigration bool) (dashboardId string) {
|
||||
defer removeFunc()
|
||||
oldID := migrationDashboardName
|
||||
if !isMigration {
|
||||
|
@ -311,7 +312,9 @@ func (b *builtinObjects) handleHomePage(path, spaceId string, removeFunc func(),
|
|||
log.Errorf("failed to get space: %w", err)
|
||||
return
|
||||
}
|
||||
dashboardId = newID
|
||||
b.setHomePageIdToWorkspace(spc, newID)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *builtinObjects) getOldHomePageId(zipReader *zip.Reader) (id string, err error) {
|
||||
|
|
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue