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,282 @@
package postgres
import (
"context"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"strings"
"telesrv/internal/domain"
)
func (s *ChannelStore) CheckInvite(ctx context.Context, userID int64, hash string, date int) (domain.CheckChannelInviteResult, error) {
if userID == 0 || strings.TrimSpace(hash) == "" {
return domain.CheckChannelInviteResult{}, domain.ErrInviteHashEmpty
}
if date == 0 {
date = nowUnix()
}
channel, invite, err := s.getInviteByHash(ctx, s.db, strings.TrimSpace(hash))
if err != nil {
return domain.CheckChannelInviteResult{}, err
}
if invite.ExpireDate > 0 && invite.ExpireDate < date {
return domain.CheckChannelInviteResult{}, domain.ErrInviteHashExpired
}
member, err := s.getChannelMember(ctx, s.db, channel.ID, userID)
already := false
if err == nil {
if member.Status == domain.ChannelMemberKicked || member.Status == domain.ChannelMemberBanned || member.BannedRights.ViewMessages {
return domain.CheckChannelInviteResult{}, domain.ErrInviteHashInvalid
}
already = member.Status == domain.ChannelMemberActive
} else if !errors.Is(err, domain.ErrChannelPrivate) {
return domain.CheckChannelInviteResult{}, err
}
return domain.CheckChannelInviteResult{Channel: channel, Invite: invite, Already: already, Self: member}, nil
}
func (s *ChannelStore) ImportInvite(ctx context.Context, req domain.ImportChannelInviteRequest) (domain.CreateChannelResult, error) {
if req.UserID == 0 || strings.TrimSpace(req.Hash) == "" {
return domain.CreateChannelResult{}, domain.ErrInviteHashEmpty
}
if req.Date == 0 {
req.Date = nowUnix()
}
beginner, ok := s.db.(txBeginner)
if !ok {
return domain.CreateChannelResult{}, fmt.Errorf("import channel invite: db does not support transactions")
}
tx, err := beginner.Begin(ctx)
if err != nil {
return domain.CreateChannelResult{}, fmt.Errorf("begin import channel invite: %w", err)
}
committed := false
defer func() {
if !committed {
_ = tx.Rollback(ctx)
}
}()
channel, invite, err := s.getInviteByHashForUpdate(ctx, tx, strings.TrimSpace(req.Hash))
if err != nil {
return domain.CreateChannelResult{}, err
}
if invite.ExpireDate > 0 && invite.ExpireDate < req.Date {
return domain.CreateChannelResult{}, domain.ErrInviteHashExpired
}
if invite.RequestNeeded {
if err := s.recordPendingInviteImporterTx(ctx, tx, invite, req.UserID, req.Date); err != nil {
return domain.CreateChannelResult{}, err
}
if err := tx.Commit(ctx); err != nil {
return domain.CreateChannelResult{}, fmt.Errorf("commit pending channel invite request: %w", err)
}
committed = true
return domain.CreateChannelResult{Channel: channel}, domain.ErrInviteRequestSent
}
result, err := s.approveInviteImporterTx(ctx, tx, channel, invite, req.UserID, 0, req.Date)
if err != nil {
return domain.CreateChannelResult{}, err
}
if err := tx.Commit(ctx); err != nil {
return domain.CreateChannelResult{}, fmt.Errorf("commit import channel invite: %w", err)
}
committed = true
recipients, _ := s.ListActiveChannelMemberIDs(ctx, req.UserID, result.Channel.ID, 0)
result.Recipients = recipients
return result, nil
}
func (s *ChannelStore) approveInviteImporterTx(ctx context.Context, tx pgx.Tx, channel domain.Channel, invite domain.ChannelInvite, userID, approvedBy int64, date int) (domain.CreateChannelResult, error) {
if invite.InviteID != 0 && invite.UsageLimit > 0 && invite.UsageCount >= invite.UsageLimit {
return domain.CreateChannelResult{}, domain.ErrUsersTooMuch
}
channelID := channel.ID
if channelID == 0 {
channelID = invite.ChannelID
}
if existing, err := s.getChannelMember(ctx, tx, channelID, userID); err == nil {
if existing.Status == domain.ChannelMemberActive {
return domain.CreateChannelResult{}, domain.ErrUserAlreadyParticipant
}
if existing.Status == domain.ChannelMemberKicked || existing.Status == domain.ChannelMemberBanned || existing.BannedRights.ViewMessages {
return domain.CreateChannelResult{}, domain.ErrInviteHashInvalid
}
} else if !errors.Is(err, domain.ErrChannelPrivate) {
return domain.CreateChannelResult{}, err
}
preJoinTopID := channel.TopMessageID
minID := channelInitialAvailableMinID(channel)
inviterID := invite.AdminUserID
if inviterID == 0 {
inviterID = approvedBy
}
member := domain.ChannelMember{
ChannelID: channelID,
UserID: userID,
InviterUserID: inviterID,
Role: domain.ChannelRoleMember,
Status: domain.ChannelMemberActive,
JoinedAt: date,
AvailableMinID: minID,
AvailableMinPts: channelInitialAvailableMinPts(channel),
ReadInboxMaxID: maxInt(minID, preJoinTopID),
}
if err := upsertChannelMemberTx(ctx, tx, channel, member); err != nil {
return domain.CreateChannelResult{}, err
}
if err := s.insertChannelAdminLogTx(ctx, tx, domain.ChannelAdminLogEvent{
ChannelID: channelID,
UserID: userID,
Date: date,
Type: domain.ChannelAdminLogParticipantJoin,
}); err != nil {
return domain.CreateChannelResult{}, err
}
if invite.InviteID != 0 {
if _, err := tx.Exec(ctx, `
UPDATE channel_invites
SET requested_count = GREATEST(requested_count - 1, 0),
updated_at = now()
WHERE channel_id = $1
AND invite_id = (
SELECT invite_id
FROM channel_invite_importers
WHERE channel_id = $1 AND user_id = $2 AND requested
)`, channelID, userID); err != nil {
return domain.CreateChannelResult{}, fmt.Errorf("clear pending channel invite request: %w", err)
}
if _, err := tx.Exec(ctx, `
UPDATE channel_invites
SET usage_count = usage_count + 1,
updated_at = now()
WHERE channel_id = $1 AND invite_id = $2`, channelID, invite.InviteID); err != nil {
return domain.CreateChannelResult{}, fmt.Errorf("increment channel invite usage: %w", err)
}
}
if _, err := tx.Exec(ctx, `
INSERT INTO channel_invite_importers (channel_id, invite_id, user_id, date, requested, approved_by)
VALUES ($1, $2, $3, $4, false, $5)
ON CONFLICT (channel_id, user_id) DO UPDATE
SET invite_id = EXCLUDED.invite_id,
date = EXCLUDED.date,
requested = false,
approved_by = EXCLUDED.approved_by,
updated_at = now()`, channelID, invite.InviteID, userID, date, approvedBy); err != nil {
return domain.CreateChannelResult{}, fmt.Errorf("upsert channel invite importer: %w", err)
}
channel, err := refreshChannelCountsTx(ctx, tx, channel)
if err != nil {
return domain.CreateChannelResult{}, err
}
var msg domain.ChannelMessage
var event domain.ChannelUpdateEvent
if channel.Megagroup {
msg, event, err = s.insertServiceMessage(ctx, tx, channel, userID, date, domain.ChannelMessageAction{
Type: domain.ChannelActionChatJoinedByLink,
InviterUserID: invite.AdminUserID,
UserIDs: []int64{userID},
})
if err != nil {
return domain.CreateChannelResult{}, err
}
channel.TopMessageID = msg.ID
channel.Pts = event.Pts
}
member.ReadInboxMaxID = maxInt(member.ReadInboxMaxID, channel.TopMessageID)
if msg.ID != 0 {
member.ReadOutboxMaxID = maxInt(member.ReadOutboxMaxID, msg.ID)
}
if _, err := tx.Exec(ctx, `
UPDATE channel_members
SET read_inbox_max_id = $3, read_outbox_max_id = $4, updated_at = now()
WHERE channel_id = $1 AND user_id = $2`, channel.ID, userID, member.ReadInboxMaxID, member.ReadOutboxMaxID); err != nil {
return domain.CreateChannelResult{}, fmt.Errorf("update imported member read state: %w", err)
}
if err := upsertChannelDialogTx(ctx, tx, userID, channel, msg, member.ReadInboxMaxID, member.ReadOutboxMaxID); err != nil {
return domain.CreateChannelResult{}, err
}
// 经邀请链接重新入群同样是重进:按新 available_min_id 重算未读 reaction 计数,
// 清掉离群期间残留的幽灵角标(同 JoinChannel,门控自动对 prehistory-visible 保留真实计数)。
if err := refreshChannelUnreadReactionsCountTx(ctx, tx, userID, channel.ID); err != nil {
return domain.CreateChannelResult{}, err
}
return domain.CreateChannelResult{Channel: channel, Members: []domain.ChannelMember{member}, Message: msg, Event: event}, nil
}
func (s *ChannelStore) recordPendingInviteImporterTx(ctx context.Context, tx pgx.Tx, invite domain.ChannelInvite, userID int64, date int) error {
if existing, err := s.getChannelMember(ctx, tx, invite.ChannelID, userID); err == nil {
if existing.Status == domain.ChannelMemberActive {
return domain.ErrUserAlreadyParticipant
}
if existing.Status == domain.ChannelMemberKicked || existing.Status == domain.ChannelMemberBanned || existing.BannedRights.ViewMessages {
return domain.ErrInviteHashInvalid
}
} else if !errors.Is(err, domain.ErrChannelPrivate) {
return err
}
tag, err := tx.Exec(ctx, `
INSERT INTO channel_invite_importers (channel_id, invite_id, user_id, date, requested)
VALUES ($1, $2, $3, $4, true)
ON CONFLICT (channel_id, user_id) DO UPDATE
SET invite_id = EXCLUDED.invite_id,
date = EXCLUDED.date,
requested = true,
approved_by = 0,
updated_at = now()
WHERE NOT channel_invite_importers.requested`,
invite.ChannelID, invite.InviteID, userID, date)
if err != nil {
return fmt.Errorf("record pending channel invite importer: %w", err)
}
if tag.RowsAffected() == 0 {
return domain.ErrInviteRequestSent
}
if _, err := tx.Exec(ctx, `
UPDATE channel_invites
SET requested_count = requested_count + 1, updated_at = now()
WHERE channel_id = $1 AND invite_id = $2`, invite.ChannelID, invite.InviteID); err != nil {
return fmt.Errorf("increment channel invite requested count: %w", err)
}
return nil
}
func (s *ChannelStore) getPendingInviteImporterTx(ctx context.Context, tx pgx.Tx, channelID, userID int64, forUpdate bool) (domain.ChannelInviteImporter, error) {
lockClause := ""
if forUpdate {
lockClause = " FOR UPDATE"
}
row := tx.QueryRow(ctx, `
SELECT channel_id, invite_id, user_id, date, requested, approved_by, via_chatlist, about
FROM channel_invite_importers
WHERE channel_id = $1 AND user_id = $2 AND requested`+lockClause, channelID, userID)
var importer domain.ChannelInviteImporter
err := row.Scan(&importer.ChannelID, &importer.InviteID, &importer.UserID, &importer.Date, &importer.Requested, &importer.ApprovedBy, &importer.ViaChatlist, &importer.About)
if errors.Is(err, pgx.ErrNoRows) {
return domain.ChannelInviteImporter{}, domain.ErrHideRequesterMissing
}
return importer, err
}
func deletePendingInviteImporterTx(ctx context.Context, tx pgx.Tx, invite domain.ChannelInvite, userID int64) error {
tag, err := tx.Exec(ctx, `
DELETE FROM channel_invite_importers
WHERE channel_id = $1 AND user_id = $2 AND requested`, invite.ChannelID, userID)
if err != nil {
return fmt.Errorf("delete pending channel invite importer: %w", err)
}
if tag.RowsAffected() == 0 {
return domain.ErrHideRequesterMissing
}
if invite.InviteID == 0 {
return nil
}
if _, err := tx.Exec(ctx, `
UPDATE channel_invites
SET requested_count = CASE WHEN requested_count > 0 THEN requested_count - 1 ELSE 0 END,
updated_at = now()
WHERE channel_id = $1 AND invite_id = $2`, invite.ChannelID, invite.InviteID); err != nil {
return fmt.Errorf("decrement channel invite requested count: %w", err)
}
return nil
}