157 lines
4.7 KiB
Go
157 lines
4.7 KiB
Go
package memory
|
||
|
||
import (
|
||
"context"
|
||
"sort"
|
||
"time"
|
||
|
||
"telesrv/internal/domain"
|
||
)
|
||
|
||
// PinPrivateMessage 与 PG 实现同语义:非 pm_oneside 的 pin 双侧置位,
|
||
// unpin 恒双侧清除,状态未变化时幂等 no-op;服务消息不可置顶。
|
||
func (s *MessageStore) PinPrivateMessage(_ context.Context, req domain.PinPrivateMessageRequest) (domain.PinPrivateMessageResult, error) {
|
||
res := domain.PinPrivateMessageResult{OwnerUserID: req.OwnerUserID}
|
||
if req.OwnerUserID == 0 || req.Peer.Type != domain.PeerTypeUser || req.Peer.ID == 0 {
|
||
return res, domain.ErrMessageIDInvalid
|
||
}
|
||
if req.MessageID <= 0 || req.MessageID > domain.MaxMessageBoxID {
|
||
return res, domain.ErrMessageIDInvalid
|
||
}
|
||
if req.Date == 0 {
|
||
req.Date = int(time.Now().Unix())
|
||
}
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
ownIdx := -1
|
||
for i, msg := range s.m[req.OwnerUserID] {
|
||
if msg.Peer == req.Peer && msg.ID == req.MessageID {
|
||
ownIdx = i
|
||
break
|
||
}
|
||
}
|
||
if ownIdx < 0 {
|
||
return res, domain.ErrMessageIDInvalid
|
||
}
|
||
owned := s.m[req.OwnerUserID][ownIdx]
|
||
if owned.Media != nil && owned.Media.Kind == domain.MessageMediaKindService {
|
||
return res, domain.ErrMessageIDInvalid
|
||
}
|
||
if owned.Pinned == req.Pinned && (!req.Pinned || req.PmOneside) {
|
||
// 与 PG 同语义:unpin/oneside pin 幂等短路;共享 pin 仍需检查
|
||
// 对端传播。
|
||
return res, nil
|
||
}
|
||
type pinSide struct {
|
||
userID int64
|
||
peer domain.Peer
|
||
idx int
|
||
}
|
||
sides := []pinSide{{userID: req.OwnerUserID, peer: req.Peer, idx: ownIdx}}
|
||
if req.Peer.ID != req.OwnerUserID && (!req.Pinned || !req.PmOneside) {
|
||
for i, msg := range s.m[req.Peer.ID] {
|
||
if msg.UID == owned.UID {
|
||
sides = append(sides, pinSide{
|
||
userID: req.Peer.ID,
|
||
peer: domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID},
|
||
idx: i,
|
||
})
|
||
break
|
||
}
|
||
}
|
||
}
|
||
for _, side := range sides {
|
||
if s.m[side.userID][side.idx].Pinned == req.Pinned {
|
||
continue
|
||
}
|
||
s.m[side.userID][side.idx].Pinned = req.Pinned
|
||
boxID := s.m[side.userID][side.idx].ID
|
||
res.Updated = append(res.Updated, domain.PinnedMessagesForUser{
|
||
UserID: side.userID,
|
||
Peer: side.peer,
|
||
MessageIDs: []int{boxID},
|
||
Pinned: req.Pinned,
|
||
Event: domain.UpdateEvent{
|
||
UserID: side.userID,
|
||
Type: domain.UpdateEventPinnedMessages,
|
||
Pts: s.nextPtsLocked(side.userID),
|
||
PtsCount: 1,
|
||
Date: req.Date,
|
||
Peer: side.peer,
|
||
Bool: req.Pinned,
|
||
MessageIDs: []int{boxID},
|
||
},
|
||
})
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
// UnpinAllPrivateMessages 与 PG 实现同语义:本侧整批清除并经 UID 同步
|
||
// 清除对端共享置顶。
|
||
func (s *MessageStore) UnpinAllPrivateMessages(_ context.Context, req domain.UnpinAllPrivateMessagesRequest) (domain.PinPrivateMessageResult, error) {
|
||
res := domain.PinPrivateMessageResult{OwnerUserID: req.OwnerUserID}
|
||
if req.OwnerUserID == 0 || req.Peer.Type != domain.PeerTypeUser || req.Peer.ID == 0 {
|
||
return res, domain.ErrMessageIDInvalid
|
||
}
|
||
if req.Date == 0 {
|
||
req.Date = int(time.Now().Unix())
|
||
}
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
// 与 PG 同语义:按 box_id 降序取一批清除,剩余批次以 Offset=1 续清。
|
||
pinnedIdx := make([]int, 0)
|
||
for i, msg := range s.m[req.OwnerUserID] {
|
||
if msg.Peer == req.Peer && msg.Pinned {
|
||
pinnedIdx = append(pinnedIdx, i)
|
||
}
|
||
}
|
||
sort.Slice(pinnedIdx, func(a, b int) bool {
|
||
return s.m[req.OwnerUserID][pinnedIdx[a]].ID > s.m[req.OwnerUserID][pinnedIdx[b]].ID
|
||
})
|
||
if len(pinnedIdx) > domain.MaxUnpinAllBatch {
|
||
pinnedIdx = pinnedIdx[:domain.MaxUnpinAllBatch]
|
||
res.Offset = 1
|
||
}
|
||
ownIDs := make([]int, 0, len(pinnedIdx))
|
||
uids := make(map[int64]struct{})
|
||
for _, i := range pinnedIdx {
|
||
s.m[req.OwnerUserID][i].Pinned = false
|
||
ownIDs = append(ownIDs, s.m[req.OwnerUserID][i].ID)
|
||
uids[s.m[req.OwnerUserID][i].UID] = struct{}{}
|
||
}
|
||
if len(ownIDs) == 0 {
|
||
return res, nil
|
||
}
|
||
appendSide := func(userID int64, peer domain.Peer, ids []int) {
|
||
res.Updated = append(res.Updated, domain.PinnedMessagesForUser{
|
||
UserID: userID,
|
||
Peer: peer,
|
||
MessageIDs: ids,
|
||
Pinned: false,
|
||
Event: domain.UpdateEvent{
|
||
UserID: userID,
|
||
Type: domain.UpdateEventPinnedMessages,
|
||
Pts: s.nextPtsLocked(userID),
|
||
PtsCount: 1,
|
||
Date: req.Date,
|
||
Peer: peer,
|
||
Bool: false,
|
||
MessageIDs: ids,
|
||
},
|
||
})
|
||
}
|
||
appendSide(req.OwnerUserID, req.Peer, ownIDs)
|
||
if req.Peer.ID != req.OwnerUserID {
|
||
peerIDs := make([]int, 0)
|
||
for i, msg := range s.m[req.Peer.ID] {
|
||
if _, ok := uids[msg.UID]; ok && msg.Pinned {
|
||
s.m[req.Peer.ID][i].Pinned = false
|
||
peerIDs = append(peerIDs, msg.ID)
|
||
}
|
||
}
|
||
if len(peerIDs) > 0 {
|
||
appendSide(req.Peer.ID, domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID}, peerIDs)
|
||
}
|
||
}
|
||
return res, nil
|
||
}
|