chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,137 @@
package postgres
import (
"context"
"errors"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"telesrv/internal/domain"
"telesrv/internal/store/postgres/sqlcgen"
)
// 私聊消息 poll 投票/关闭:消息可见性在 message_boxes 校验(与 reaction 同款 SELECT FOR
// UPDATE 定位),poll 级语义委托 poll.go 的共享 SQL(polls 行 FOR UPDATE 防 quiz 并发双投)。
func (s *MessageStore) VoteMessagePoll(ctx context.Context, req domain.VotePrivateMessagePollRequest) (domain.PrivateMessagePollResult, error) {
return s.mutateMessagePoll(ctx, req.UserID, req.Peer, req.MessageID, req.Date, func(ctx context.Context, tx pgx.Tx, def domain.PollDefinition, date int) error {
return applyPollVote(ctx, tx, def, req.UserID, req.Options, date)
})
}
func (s *MessageStore) CloseMessagePoll(ctx context.Context, req domain.ClosePrivateMessagePollRequest) (domain.PrivateMessagePollResult, error) {
return s.mutateMessagePoll(ctx, req.UserID, req.Peer, req.MessageID, req.Date, func(ctx context.Context, tx pgx.Tx, def domain.PollDefinition, _ int) error {
return closePollAsCreator(ctx, tx, def, req.UserID)
})
}
func (s *MessageStore) mutateMessagePoll(
ctx context.Context,
userID int64,
peer domain.Peer,
messageID int,
date int,
mutate func(ctx context.Context, tx pgx.Tx, def domain.PollDefinition, date int) error,
) (domain.PrivateMessagePollResult, error) {
if userID == 0 || peer.Type != domain.PeerTypeUser || peer.ID == 0 || messageID <= 0 || messageID > domain.MaxMessageBoxID {
return domain.PrivateMessagePollResult{}, domain.ErrMessageIDInvalid
}
if date == 0 {
date = int(time.Now().Unix())
}
beginner, ok := s.db.(txBeginner)
if !ok {
return domain.PrivateMessagePollResult{}, fmt.Errorf("mutate message poll: db does not support transactions")
}
tx, err := beginner.Begin(ctx)
if err != nil {
return domain.PrivateMessagePollResult{}, fmt.Errorf("begin message poll tx: %w", err)
}
committed := false
defer func() {
if !committed {
_ = tx.Rollback(ctx)
}
}()
var target struct {
privateMessageID int64
messageSenderID int64
mediaJSON string
}
if err := tx.QueryRow(ctx, `
SELECT private_message_id, message_sender_id, COALESCE(media::text, '{}')
FROM message_boxes
WHERE owner_user_id = $1
AND box_id = $2
AND peer_type = $3
AND peer_id = $4
AND NOT deleted
LIMIT 1`, userID, int32(messageID), string(peer.Type), peer.ID).Scan(&target.privateMessageID, &target.messageSenderID, &target.mediaJSON); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.PrivateMessagePollResult{}, domain.ErrMessageIDInvalid
}
return domain.PrivateMessagePollResult{}, fmt.Errorf("get message for poll: %w", err)
}
media, err := decodeMessageMedia(target.mediaJSON)
if err != nil {
return domain.PrivateMessagePollResult{}, fmt.Errorf("decode poll message media: %w", err)
}
if media == nil || media.Kind != domain.MessageMediaKindPoll || media.Poll == nil || media.Poll.ID == 0 {
return domain.PrivateMessagePollResult{}, domain.ErrMessageIDInvalid
}
pollID := media.Poll.ID
defs, err := loadPollDefinitions(ctx, tx, []int64{pollID}, true)
if err != nil {
return domain.PrivateMessagePollResult{}, err
}
def, ok := defs[pollID]
if !ok {
return domain.PrivateMessagePollResult{}, domain.ErrPollNotFound
}
if err := mutate(ctx, tx, def, date); err != nil {
return domain.PrivateMessagePollResult{}, err
}
boxes, err := sqlcgen.New(tx).ListVisibleMessageBoxesByPrivateMessage(ctx, sqlcgen.ListVisibleMessageBoxesByPrivateMessageParams{
OwnerUserIds: privateMessageOwnerIDs(userID, peer.ID),
MessageSenderID: target.messageSenderID,
PrivateMessageID: target.privateMessageID,
})
if err != nil {
return domain.PrivateMessagePollResult{}, fmt.Errorf("list visible poll boxes: %w", err)
}
res := domain.PrivateMessagePollResult{PollID: pollID, Messages: make([]domain.Message, 0, len(boxes))}
for _, box := range boxes {
msg, err := messageFromVisibleBoxRow(box)
if err != nil {
return domain.PrivateMessagePollResult{}, err
}
res.Messages = append(res.Messages, msg)
}
// poll enrichment(按各 box owner 视角)由 enrichPrivateMessageReactions 统一挂载。
if err := s.enrichPrivateMessageReactions(ctx, tx, userID, res.Messages); err != nil {
return domain.PrivateMessagePollResult{}, err
}
if err := tx.Commit(ctx); err != nil {
return domain.PrivateMessagePollResult{}, fmt.Errorf("commit message poll tx: %w", err)
}
committed = true
return res, nil
}
// enrichPrivateMessagePolls 把页内全部 poll media 按各消息 owner 视角 enrich;
// 由 enrichPrivateMessageReactions 统一挂载(所有私聊读路径共用一个 choke point)。
func (s *MessageStore) enrichPrivateMessagePolls(ctx context.Context, db sqlcgen.DBTX, viewerUserID int64, messages []domain.Message) error {
refs := make([]pollMediaRef, 0, 2)
for i := range messages {
viewer := messages[i].OwnerUserID
if viewer == 0 {
viewer = viewerUserID
}
refs = append(refs, pollMediaRef{media: messages[i].Media, viewer: viewer})
}
return enrichPollMediaRefs(ctx, db, refs)
}