chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
|
|
@ -16,12 +16,20 @@ import (
|
|||
|
||||
// DialogStore 用 PostgreSQL 实现 store.DialogStore。
|
||||
type DialogStore struct {
|
||||
q *sqlcgen.Queries
|
||||
db sqlcgen.DBTX
|
||||
q *sqlcgen.Queries
|
||||
}
|
||||
|
||||
// NewDialogStore 基于 pgx 连接池(或事务)创建 DialogStore。
|
||||
func NewDialogStore(db sqlcgen.DBTX) *DialogStore {
|
||||
return &DialogStore{q: sqlcgen.New(db)}
|
||||
return &DialogStore{db: db, q: sqlcgen.New(db)}
|
||||
}
|
||||
|
||||
func (s *DialogStore) enrichDialogTopMessages(ctx context.Context, userID int64, messages []domain.Message) error {
|
||||
if len(messages) == 0 {
|
||||
return nil
|
||||
}
|
||||
return NewMessageStore(s.db).enrichPrivateMessageReactions(ctx, s.db, userID, messages)
|
||||
}
|
||||
|
||||
func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domain.DialogFilter) (domain.DialogList, error) {
|
||||
|
|
@ -69,6 +77,9 @@ func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domai
|
|||
UnreadCount: int(row.UnreadCount),
|
||||
UnreadMentions: int(row.UnreadMentionsCount),
|
||||
UnreadReactions: int(row.UnreadReactionsCount),
|
||||
TTLPeriod: int(row.TtlPeriod),
|
||||
ThemeEmoticon: row.ThemeEmoticon,
|
||||
HasScheduled: row.HasScheduled,
|
||||
Pinned: row.Pinned,
|
||||
PinnedOrder: int(row.PinnedOrder),
|
||||
UnreadMark: row.UnreadMark,
|
||||
|
|
@ -125,6 +136,9 @@ func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domai
|
|||
UnreadCount: int(row.UnreadCount),
|
||||
UnreadMentions: int(row.UnreadMentionsCount),
|
||||
UnreadReactions: int(row.UnreadReactionsCount),
|
||||
TTLPeriod: int(row.TtlPeriod),
|
||||
ThemeEmoticon: row.ThemeEmoticon,
|
||||
HasScheduled: row.HasScheduled,
|
||||
Pinned: row.Pinned,
|
||||
PinnedOrder: int(row.PinnedOrder),
|
||||
UnreadMark: row.UnreadMark,
|
||||
|
|
@ -135,18 +149,23 @@ func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domai
|
|||
if _, ok := seenUsers[row.PeerUserID]; !ok {
|
||||
seenUsers[row.PeerUserID] = struct{}{}
|
||||
out.Users = append(out.Users, domain.User{
|
||||
ID: row.PeerUserID,
|
||||
AccessHash: row.PeerAccessHash,
|
||||
Phone: row.PeerPhone,
|
||||
FirstName: row.PeerFirstName,
|
||||
LastName: row.PeerLastName,
|
||||
Username: row.PeerUsername,
|
||||
CountryCode: row.PeerCountryCode,
|
||||
Verified: row.PeerVerified,
|
||||
Support: row.PeerSupport,
|
||||
LastSeenAt: int(row.PeerLastSeenAt),
|
||||
Contact: row.PeerContact,
|
||||
Mutual: row.PeerMutual,
|
||||
ID: row.PeerUserID,
|
||||
AccessHash: row.PeerAccessHash,
|
||||
Phone: row.PeerPhone,
|
||||
FirstName: row.PeerFirstName,
|
||||
LastName: row.PeerLastName,
|
||||
Username: row.PeerUsername,
|
||||
CountryCode: row.PeerCountryCode,
|
||||
Verified: row.PeerVerified,
|
||||
Support: row.PeerSupport,
|
||||
Bot: row.PeerIsBot,
|
||||
BotInfoVersion: int(row.PeerBotInfoVersion),
|
||||
PremiumUntil: int(row.PeerPremiumUntil),
|
||||
EmojiStatusDocumentID: row.PeerEmojiStatusDocumentID,
|
||||
EmojiStatusUntil: int(row.PeerEmojiStatusUntil),
|
||||
LastSeenAt: int(row.PeerLastSeenAt),
|
||||
Contact: row.PeerContact,
|
||||
Mutual: row.PeerMutual,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -159,19 +178,72 @@ func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domai
|
|||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message media: %w", err)
|
||||
}
|
||||
markup, err := decodeReplyMarkup(row.MessageReplyMarkupJson)
|
||||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message reply markup: %w", err)
|
||||
}
|
||||
rich, err := decodeRichMessage(row.MessageRichMessageJson)
|
||||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message rich message: %w", err)
|
||||
}
|
||||
// top message 必须带全量元数据:TDesktop 把 getDialogs 的消息
|
||||
// 先入缓存且不被后续 difference/getHistory 覆盖,缺 reply 等
|
||||
// 字段会让置顶/回复服务消息永久渲染成 "Deleted message"。
|
||||
silent, noforwards, reply, forward, err := messageMetadataFromFields(
|
||||
row.MessageSilent,
|
||||
row.MessageNoforwards,
|
||||
row.MessageReplyToMsgID,
|
||||
row.MessageReplyToPeerType,
|
||||
row.MessageReplyToPeerID,
|
||||
row.MessageReplyToTopID,
|
||||
row.MessageReplyToStoryID,
|
||||
row.MessageQuoteText,
|
||||
row.MessageQuoteEntitiesJson,
|
||||
row.MessageQuoteOffset,
|
||||
row.MessageFwdFromPeerType,
|
||||
row.MessageFwdFromPeerID,
|
||||
row.MessageFwdFromName,
|
||||
row.MessageFwdDate,
|
||||
row.MessageFwdSavedFromPeerType,
|
||||
row.MessageFwdSavedFromPeerID,
|
||||
row.MessageFwdSavedFromMsgID,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message metadata: %w", err)
|
||||
}
|
||||
out.Messages = append(out.Messages, domain.Message{
|
||||
ID: int(row.MessageID),
|
||||
OwnerUserID: row.UserID,
|
||||
Peer: dialog.Peer,
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: row.MessageFromUserID},
|
||||
Date: int(row.MessageDate),
|
||||
Out: row.MessageOutgoing,
|
||||
Body: row.MessageBody,
|
||||
Entities: entities,
|
||||
Media: media,
|
||||
ID: int(row.MessageID),
|
||||
UID: row.MessagePrivateMessageID,
|
||||
OwnerUserID: row.UserID,
|
||||
Peer: dialog.Peer,
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: row.MessageFromUserID},
|
||||
Date: int(row.MessageDate),
|
||||
EditDate: int(row.MessageEditDate),
|
||||
Out: row.MessageOutgoing,
|
||||
Silent: silent,
|
||||
NoForwards: noforwards,
|
||||
Body: row.MessageBody,
|
||||
Entities: entities,
|
||||
ReplyTo: reply,
|
||||
Forward: forward,
|
||||
Media: media,
|
||||
TTLPeriod: int(row.MessageTtlPeriod),
|
||||
ExpiresAt: int(row.MessageExpiresAt),
|
||||
MediaUnread: row.MessageMediaUnread,
|
||||
ReactionUnread: row.MessageReactionUnread,
|
||||
ViaBotID: row.MessageViaBotID,
|
||||
GroupedID: row.MessageGroupedID,
|
||||
Effect: row.MessageEffect,
|
||||
ReplyMarkup: markup,
|
||||
RichMessage: rich,
|
||||
Pinned: row.MessagePinned,
|
||||
SavedPeer: savedPeerFromFields(row.MessageSavedPeerType, row.MessageSavedPeerID),
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := s.enrichDialogTopMessages(ctx, userID, out.Messages); err != nil {
|
||||
return domain.DialogList{}, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
|
@ -216,6 +288,9 @@ func (s *DialogStore) ListByPeers(ctx context.Context, userID int64, peers []dom
|
|||
UnreadCount: int(row.UnreadCount),
|
||||
UnreadMentions: int(row.UnreadMentionsCount),
|
||||
UnreadReactions: int(row.UnreadReactionsCount),
|
||||
TTLPeriod: int(row.TtlPeriod),
|
||||
ThemeEmoticon: row.ThemeEmoticon,
|
||||
HasScheduled: row.HasScheduled,
|
||||
Pinned: row.Pinned,
|
||||
PinnedOrder: int(row.PinnedOrder),
|
||||
UnreadMark: row.UnreadMark,
|
||||
|
|
@ -226,18 +301,23 @@ func (s *DialogStore) ListByPeers(ctx context.Context, userID int64, peers []dom
|
|||
if _, ok := seenUsers[row.PeerUserID]; !ok {
|
||||
seenUsers[row.PeerUserID] = struct{}{}
|
||||
out.Users = append(out.Users, domain.User{
|
||||
ID: row.PeerUserID,
|
||||
AccessHash: row.PeerAccessHash,
|
||||
Phone: row.PeerPhone,
|
||||
FirstName: row.PeerFirstName,
|
||||
LastName: row.PeerLastName,
|
||||
Username: row.PeerUsername,
|
||||
CountryCode: row.PeerCountryCode,
|
||||
Verified: row.PeerVerified,
|
||||
Support: row.PeerSupport,
|
||||
LastSeenAt: int(row.PeerLastSeenAt),
|
||||
Contact: row.PeerContact,
|
||||
Mutual: row.PeerMutual,
|
||||
ID: row.PeerUserID,
|
||||
AccessHash: row.PeerAccessHash,
|
||||
Phone: row.PeerPhone,
|
||||
FirstName: row.PeerFirstName,
|
||||
LastName: row.PeerLastName,
|
||||
Username: row.PeerUsername,
|
||||
CountryCode: row.PeerCountryCode,
|
||||
Verified: row.PeerVerified,
|
||||
Support: row.PeerSupport,
|
||||
Bot: row.PeerIsBot,
|
||||
BotInfoVersion: int(row.PeerBotInfoVersion),
|
||||
PremiumUntil: int(row.PeerPremiumUntil),
|
||||
EmojiStatusDocumentID: row.PeerEmojiStatusDocumentID,
|
||||
EmojiStatusUntil: int(row.PeerEmojiStatusUntil),
|
||||
LastSeenAt: int(row.PeerLastSeenAt),
|
||||
Contact: row.PeerContact,
|
||||
Mutual: row.PeerMutual,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -250,19 +330,72 @@ func (s *DialogStore) ListByPeers(ctx context.Context, userID int64, peers []dom
|
|||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message media: %w", err)
|
||||
}
|
||||
markup, err := decodeReplyMarkup(row.MessageReplyMarkupJson)
|
||||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message reply markup: %w", err)
|
||||
}
|
||||
rich, err := decodeRichMessage(row.MessageRichMessageJson)
|
||||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message rich message: %w", err)
|
||||
}
|
||||
// top message 必须带全量元数据:TDesktop 把 getDialogs 的消息
|
||||
// 先入缓存且不被后续 difference/getHistory 覆盖,缺 reply 等
|
||||
// 字段会让置顶/回复服务消息永久渲染成 "Deleted message"。
|
||||
silent, noforwards, reply, forward, err := messageMetadataFromFields(
|
||||
row.MessageSilent,
|
||||
row.MessageNoforwards,
|
||||
row.MessageReplyToMsgID,
|
||||
row.MessageReplyToPeerType,
|
||||
row.MessageReplyToPeerID,
|
||||
row.MessageReplyToTopID,
|
||||
row.MessageReplyToStoryID,
|
||||
row.MessageQuoteText,
|
||||
row.MessageQuoteEntitiesJson,
|
||||
row.MessageQuoteOffset,
|
||||
row.MessageFwdFromPeerType,
|
||||
row.MessageFwdFromPeerID,
|
||||
row.MessageFwdFromName,
|
||||
row.MessageFwdDate,
|
||||
row.MessageFwdSavedFromPeerType,
|
||||
row.MessageFwdSavedFromPeerID,
|
||||
row.MessageFwdSavedFromMsgID,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.DialogList{}, fmt.Errorf("decode message metadata: %w", err)
|
||||
}
|
||||
out.Messages = append(out.Messages, domain.Message{
|
||||
ID: int(row.MessageID),
|
||||
OwnerUserID: row.UserID,
|
||||
Peer: dialog.Peer,
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: row.MessageFromUserID},
|
||||
Date: int(row.MessageDate),
|
||||
Out: row.MessageOutgoing,
|
||||
Body: row.MessageBody,
|
||||
Entities: entities,
|
||||
Media: media,
|
||||
ID: int(row.MessageID),
|
||||
UID: row.MessagePrivateMessageID,
|
||||
OwnerUserID: row.UserID,
|
||||
Peer: dialog.Peer,
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: row.MessageFromUserID},
|
||||
Date: int(row.MessageDate),
|
||||
EditDate: int(row.MessageEditDate),
|
||||
Out: row.MessageOutgoing,
|
||||
Silent: silent,
|
||||
NoForwards: noforwards,
|
||||
Body: row.MessageBody,
|
||||
Entities: entities,
|
||||
ReplyTo: reply,
|
||||
Forward: forward,
|
||||
Media: media,
|
||||
TTLPeriod: int(row.MessageTtlPeriod),
|
||||
ExpiresAt: int(row.MessageExpiresAt),
|
||||
MediaUnread: row.MessageMediaUnread,
|
||||
ReactionUnread: row.MessageReactionUnread,
|
||||
ViaBotID: row.MessageViaBotID,
|
||||
GroupedID: row.MessageGroupedID,
|
||||
Effect: row.MessageEffect,
|
||||
ReplyMarkup: markup,
|
||||
RichMessage: rich,
|
||||
Pinned: row.MessagePinned,
|
||||
SavedPeer: savedPeerFromFields(row.MessageSavedPeerType, row.MessageSavedPeerID),
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := s.enrichDialogTopMessages(ctx, userID, out.Messages); err != nil {
|
||||
return domain.DialogList{}, err
|
||||
}
|
||||
out.Count = len(out.Dialogs)
|
||||
out.Hash = dialogListHash(out.Dialogs)
|
||||
return out, nil
|
||||
|
|
@ -288,6 +421,19 @@ func (s *DialogStore) Upsert(ctx context.Context, userID int64, dialog domain.Di
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) UpsertInbox(ctx context.Context, userID int64, dialog domain.Dialog) error {
|
||||
if err := s.q.UpsertInboxDialog(ctx, sqlcgen.UpsertInboxDialogParams{
|
||||
UserID: userID,
|
||||
PeerType: string(dialog.Peer.Type),
|
||||
PeerID: dialog.Peer.ID,
|
||||
TopMessageID: int32(dialog.TopMessage),
|
||||
TopMessageDate: int32(dialog.TopMessageDate),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("upsert inbox dialog: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) SaveDraft(ctx context.Context, userID int64, draft domain.DialogDraft) error {
|
||||
data, err := json.Marshal(draft)
|
||||
if err != nil {
|
||||
|
|
@ -306,6 +452,26 @@ func (s *DialogStore) SaveDraft(ctx context.Context, userID int64, draft domain.
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) GetDraft(ctx context.Context, userID int64, peer domain.Peer, topMessageID int) (domain.DialogDraft, bool, error) {
|
||||
var data []byte
|
||||
err := s.db.QueryRow(ctx, `
|
||||
SELECT draft
|
||||
FROM dialog_drafts
|
||||
WHERE user_id = $1 AND peer_type = $2 AND peer_id = $3 AND top_message_id = $4`,
|
||||
userID, string(peer.Type), peer.ID, int32(topMessageID)).Scan(&data)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return domain.DialogDraft{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return domain.DialogDraft{}, false, fmt.Errorf("get dialog draft: %w", err)
|
||||
}
|
||||
var draft domain.DialogDraft
|
||||
if err := json.Unmarshal(data, &draft); err != nil {
|
||||
return domain.DialogDraft{}, false, fmt.Errorf("decode dialog draft: %w", err)
|
||||
}
|
||||
return draft, true, nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) DeleteDraft(ctx context.Context, userID int64, peer domain.Peer, topMessageID int) (bool, error) {
|
||||
changed, err := s.q.DeleteDialogDraft(ctx, sqlcgen.DeleteDialogDraftParams{
|
||||
UserID: userID,
|
||||
|
|
@ -366,41 +532,79 @@ func (s *DialogStore) MarkRead(ctx context.Context, userID int64, peer domain.Pe
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) SetPinned(ctx context.Context, userID int64, peer domain.Peer, pinned bool) (bool, error) {
|
||||
changed, err := s.q.SetDialogPinned(ctx, sqlcgen.SetDialogPinnedParams{
|
||||
func (s *DialogStore) SetPinned(ctx context.Context, userID int64, peer domain.Peer, pinned bool) (bool, int, error) {
|
||||
row, err := s.q.SetDialogPinned(ctx, sqlcgen.SetDialogPinnedParams{
|
||||
UserID: userID,
|
||||
PeerType: string(peer.Type),
|
||||
PeerID: peer.ID,
|
||||
Pinned: pinned,
|
||||
})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("set dialog pinned: %w", err)
|
||||
return false, 0, fmt.Errorf("set dialog pinned: %w", err)
|
||||
}
|
||||
return changed, nil
|
||||
return row.Changed, int(row.FolderID), nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) ReorderPinned(ctx context.Context, userID int64, order []domain.Peer, force bool) error {
|
||||
func (s *DialogStore) ReorderPinned(ctx context.Context, userID int64, folderID int, order []domain.Peer, force bool) (bool, error) {
|
||||
peerTypes, peerIDs := peerArrays(order)
|
||||
changed := false
|
||||
if force {
|
||||
if err := s.q.ClearPinnedDialogsNotInOrder(ctx, sqlcgen.ClearPinnedDialogsNotInOrderParams{
|
||||
UserID: userID,
|
||||
PeerTypes: peerTypes,
|
||||
PeerIds: peerIDs,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("clear pinned dialogs not in order: %w", err)
|
||||
tag, err := s.db.Exec(ctx, `
|
||||
WITH requested AS (
|
||||
SELECT ($3::text[])[i] AS peer_type, ($4::bigint[])[i] AS peer_id
|
||||
FROM generate_subscripts($4::bigint[], 1) AS g(i)
|
||||
WHERE i <= cardinality($3::text[])
|
||||
)
|
||||
UPDATE dialogs d
|
||||
SET pinned = false, pinned_order = 0, updated_at = now()
|
||||
WHERE d.user_id = $1
|
||||
AND d.pinned
|
||||
AND d.folder_id = $2::int
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM requested r
|
||||
WHERE r.peer_type = d.peer_type
|
||||
AND r.peer_id = d.peer_id
|
||||
)`, userID, folderID, peerTypes, peerIDs)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("clear pinned dialogs not in order: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() > 0 {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if len(peerTypes) == 0 {
|
||||
return nil
|
||||
return changed, nil
|
||||
}
|
||||
if err := s.q.ReorderPinnedDialogs(ctx, sqlcgen.ReorderPinnedDialogsParams{
|
||||
UserID: userID,
|
||||
PeerTypes: peerTypes,
|
||||
PeerIds: peerIDs,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("reorder pinned dialogs: %w", err)
|
||||
tag, err := s.db.Exec(ctx, `
|
||||
WITH requested AS (
|
||||
SELECT ($3::text[])[i] AS peer_type, ($4::bigint[])[i] AS peer_id, i::int AS pos
|
||||
FROM generate_subscripts($4::bigint[], 1) AS g(i)
|
||||
WHERE i <= cardinality($3::text[])
|
||||
),
|
||||
deduped AS (
|
||||
SELECT DISTINCT ON (peer_type, peer_id)
|
||||
peer_type,
|
||||
peer_id,
|
||||
(cardinality($4::bigint[]) - pos + 1)::int AS ord
|
||||
FROM requested
|
||||
ORDER BY peer_type, peer_id, pos
|
||||
)
|
||||
UPDATE dialogs d
|
||||
SET pinned = true, pinned_order = deduped.ord, updated_at = now()
|
||||
FROM deduped
|
||||
WHERE d.user_id = $1
|
||||
AND d.peer_type = deduped.peer_type
|
||||
AND d.peer_id = deduped.peer_id
|
||||
AND d.folder_id = $2::int
|
||||
AND (NOT d.pinned OR d.pinned_order IS DISTINCT FROM deduped.ord)`, userID, folderID, peerTypes, peerIDs)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("reorder pinned dialogs: %w", err)
|
||||
}
|
||||
return nil
|
||||
if tag.RowsAffected() > 0 {
|
||||
changed = true
|
||||
}
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) SetUnreadMark(ctx context.Context, userID int64, peer domain.Peer, unread bool) (bool, error) {
|
||||
|
|
@ -428,6 +632,44 @@ func (s *DialogStore) ListUnreadMarked(ctx context.Context, userID int64) ([]dom
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) SetChatTheme(ctx context.Context, userID int64, peer domain.Peer, emoticon string) (bool, error) {
|
||||
if userID == 0 || peer.Type == "" || peer.ID == 0 {
|
||||
return false, nil
|
||||
}
|
||||
var current string
|
||||
err := s.db.QueryRow(ctx, `
|
||||
SELECT theme_emoticon
|
||||
FROM dialogs
|
||||
WHERE user_id = $1 AND peer_type = $2 AND peer_id = $3`,
|
||||
userID, string(peer.Type), peer.ID).Scan(¤t)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
if emoticon == "" {
|
||||
return false, nil
|
||||
}
|
||||
if _, err := s.db.Exec(ctx, `
|
||||
INSERT INTO dialogs (user_id, peer_type, peer_id, theme_emoticon)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
userID, string(peer.Type), peer.ID, emoticon); err != nil {
|
||||
return false, fmt.Errorf("insert dialog chat theme: %w", err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get dialog chat theme: %w", err)
|
||||
}
|
||||
if current == emoticon {
|
||||
return false, nil
|
||||
}
|
||||
if _, err := s.db.Exec(ctx, `
|
||||
UPDATE dialogs
|
||||
SET theme_emoticon = $4, updated_at = now()
|
||||
WHERE user_id = $1 AND peer_type = $2 AND peer_id = $3`,
|
||||
userID, string(peer.Type), peer.ID, emoticon); err != nil {
|
||||
return false, fmt.Errorf("update dialog chat theme: %w", err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) SetPeerSettingsBarHidden(ctx context.Context, userID int64, peer domain.Peer) (bool, error) {
|
||||
changed, err := s.q.SetPeerSettingsBarHidden(ctx, sqlcgen.SetPeerSettingsBarHiddenParams{
|
||||
UserID: userID,
|
||||
|
|
@ -579,6 +821,37 @@ func (s *DialogStore) EditPeerFolders(ctx context.Context, userID int64, peers [
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) SetArchivePinned(ctx context.Context, userID int64, pinned bool) (bool, error) {
|
||||
changed, err := s.q.SetDialogArchivePinned(ctx, sqlcgen.SetDialogArchivePinnedParams{
|
||||
UserID: userID,
|
||||
Pinned: pinned,
|
||||
})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("set dialog archive pinned: %w", err)
|
||||
}
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) ArchivePinned(ctx context.Context, userID int64) (bool, error) {
|
||||
pinned, err := s.q.GetDialogArchivePinned(ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
// 官方默认 archive folder 行在置顶区。
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("get dialog archive pinned: %w", err)
|
||||
}
|
||||
return pinned, nil
|
||||
}
|
||||
|
||||
func (s *DialogStore) CountArchiveUnread(ctx context.Context, userID int64) (int, int, error) {
|
||||
row, err := s.q.CountArchiveUnreadDialogs(ctx, userID)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("count archive unread dialogs: %w", err)
|
||||
}
|
||||
return int(row.UnreadPeers), int(row.UnreadMessages), nil
|
||||
}
|
||||
|
||||
type dialogFolderParams struct {
|
||||
contacts bool
|
||||
nonContacts bool
|
||||
|
|
@ -727,6 +1000,7 @@ func dialogListHash(dialogs []domain.Dialog) int64 {
|
|||
buf[46] = 0
|
||||
}
|
||||
_, _ = h.Write(buf[:])
|
||||
_, _ = h.Write([]byte(d.ThemeEmoticon))
|
||||
}
|
||||
return int64(h.Sum64())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue