owpengram-server/internal/store/postgres/history_ttl.go

159 lines
4.4 KiB
Go

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`, 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
`, 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
}