Ctrl K
ComunidadGuías…

internal/store/postgres/history_ttl.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package postgres

import (
	"context"
	"errors"
	"fmt"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"

	"telesrv/internal/domain"
)

func (s *MessageStore) GetPrivateHistoryTTL(ctx context.Context, ownerUserID int64, peer domain.Peer) (int, error) {
	if ownerUserID == 0 || peer.Type != domain.PeerTypeUser || peer.ID == 0 {
		return 0, fmt.Errorf("private history ttl: invalid peer")
	}
	return privateHistoryTTLPeriod(ctx, s.db, ownerUserID, peer.ID)
}

func (s *MessageStore) SetPrivateHistoryTTL(ctx context.Context, ownerUserID int64, peer domain.Peer, period int) error {
	if ownerUserID == 0 || peer.Type != domain.PeerTypeUser || peer.ID == 0 {
		return fmt.Errorf("set private history ttl: invalid peer")
	}
	if period < 0 {
		return fmt.Errorf("set private history ttl: invalid period")
	}
	return withTx(ctx, s.db, "set private history ttl", func(tx pgx.Tx) error {
		if err := lockUsersForUpdate(ctx, tx, ownerUserID, peer.ID); err != nil {
			return fmt.Errorf("lock private ttl users: %w", err)
		}
		if err := upsertPrivateDialogTTL(ctx, tx, ownerUserID, peer.ID, period); err != nil {
			return err
		}
		if peer.ID != ownerUserID {
			if err := upsertPrivateDialogTTL(ctx, tx, peer.ID, ownerUserID, period); err != nil {
				return err
			}
		}
		return nil
	})
}

func (s *MessageStore) DefaultHistoryTTL(ctx context.Context, userID int64) (int, error) {
	if userID == 0 {
		return 0, nil
	}
	var period int
	err := s.db.QueryRow(ctx, `SELECT COALESCE(default_history_ttl_period, 0)::int FROM users WHERE id = $1 AND deleted_at IS NULL`, userID).Scan(&period)
	if errors.Is(err, pgx.ErrNoRows) {
		return 0, nil
	}
	if err != nil {
		return 0, fmt.Errorf("get default history ttl: %w", err)
	}
	if period < 0 {
		return 0, nil
	}
	return period, nil
}

func (s *MessageStore) SetDefaultHistoryTTL(ctx context.Context, userID int64, period int) error {
	if userID == 0 {
		return nil
	}
	if period < 0 {
		return fmt.Errorf("set default history ttl: invalid period")
	}
	tag, err := s.db.Exec(ctx, `
UPDATE users
SET default_history_ttl_period = $2,
    updated_at = now()
WHERE id = $1 AND deleted_at IS NULL
`, userID, period)
	if err != nil {
		return fmt.Errorf("set default history ttl: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("set default history ttl: user not found")
	}
	return nil
}

func (s *MessageStore) ClaimExpiredPrivateMessages(ctx context.Context, now, limit int) ([]domain.DeleteMessagesRequest, error) {
	if now <= 0 || limit <= 0 {
		return nil, nil
	}
	if limit > domain.MaxDeleteHistoryBatch {
		limit = domain.MaxDeleteHistoryBatch
	}
	rows, err := s.db.Query(ctx, `
WITH due AS (
  SELECT DISTINCT ON (m.message_sender_id, m.private_message_id)
    m.owner_user_id,
    m.peer_id,
    m.box_id,
    m.expires_at
  FROM message_boxes m
  WHERE m.expires_at > 0
    AND m.expires_at <= $1
    AND NOT m.deleted
  ORDER BY m.message_sender_id, m.private_message_id, m.expires_at ASC, m.owner_user_id ASC
  LIMIT $2
)
SELECT owner_user_id, peer_id, box_id
FROM due
ORDER BY owner_user_id ASC, peer_id ASC, box_id ASC
`, now, limit)
	if err != nil {
		return nil, fmt.Errorf("claim expired private messages: %w", err)
	}
	defer rows.Close()
	type key struct {
		owner int64
		peer  int64
	}
	index := make(map[key]int)
	out := make([]domain.DeleteMessagesRequest, 0)
	for rows.Next() {
		var owner, peer int64
		var id int
		if err := rows.Scan(&owner, &peer, &id); err != nil {
			return nil, fmt.Errorf("scan expired private message: %w", err)
		}
		k := key{owner: owner, peer: peer}
		pos, ok := index[k]
		if !ok {
			pos = len(out)
			index[k] = pos
			out = append(out, domain.DeleteMessagesRequest{
				OwnerUserID: owner,
				IDs:         make([]int, 0, 8),
				Revoke:      true,
				Date:        now,
			})
		}
		out[pos].IDs = append(out[pos].IDs, id)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate expired private messages: %w", err)
	}
	return out, nil
}

func upsertPrivateDialogTTL(ctx context.Context, db interface {
	Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
}, ownerUserID, peerUserID int64, period int) error {
	_, err := db.Exec(ctx, `
INSERT INTO dialogs (user_id, peer_type, peer_id, ttl_period)
VALUES ($1, 'user', $2, $3)
ON CONFLICT (user_id, peer_type, peer_id) DO UPDATE SET
  ttl_period = EXCLUDED.ttl_period,
  updated_at = now()
`, ownerUserID, peerUserID, period)
	if err != nil {
		return fmt.Errorf("upsert private dialog ttl: %w", err)
	}
	return nil
}