Ctrl K
ComunidadGuías…

internal/store/postgres/channel_message_fact_check.go

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

Archivo original ↗Preguntar a KiBot
package postgres

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/jackc/pgx/v5"

	"telesrv/internal/domain"
)

// EditFactCheck writes or deletes a message's fact-check label.
//
// Permissions match channel message editing: the message author themself, or the channel creator / an admin
// with the edit_messages right. The fact check hangs on the message row, so all subscribers read the same verdict.
//
// The write consumes one channel pts and produces an edit_message event, on which the client updates the fact-check
// label in place on the original message.
func (s *ChannelStore) EditFactCheck(ctx context.Context, req domain.EditFactCheckRequest) (domain.EditFactCheckResult, error) {
	if req.UserID == 0 || req.ChannelID == 0 || req.MessageID <= 0 {
		return domain.EditFactCheckResult{}, domain.ErrChannelInvalid
	}
	deleting := strings.TrimSpace(req.Text) == ""
	var check *domain.FactCheck
	if !deleting {
		candidate := domain.FactCheck{
			NeedCheck: req.NeedCheck,
			Country:   strings.ToUpper(strings.TrimSpace(req.Country)),
			Text:      req.Text,
			Entities:  req.Entities,
		}
		if err := domain.ValidateFactCheck(candidate); err != nil {
			return domain.EditFactCheckResult{}, err
		}
		candidate.Hash = domain.FactCheckHash(req.ChannelID, req.MessageID, candidate)
		check = &candidate
	}
	if req.Date == 0 {
		req.Date = nowUnix()
	}
	payload, err := encodeFactCheck(check)
	if err != nil {
		return domain.EditFactCheckResult{}, err
	}
	beginner, ok := s.db.(txBeginner)
	if !ok {
		return domain.EditFactCheckResult{}, fmt.Errorf("edit fact check: db does not support transactions")
	}
	tx, err := beginner.Begin(ctx)
	if err != nil {
		return domain.EditFactCheckResult{}, fmt.Errorf("begin edit fact check: %w", err)
	}
	committed := false
	defer func() {
		if !committed {
			_ = tx.Rollback(ctx)
		}
	}()
	channel, member, err := s.getChannelForMember(ctx, tx, req.UserID, req.ChannelID)
	if err != nil {
		return domain.EditFactCheckResult{}, err
	}
	if !channel.Broadcast {
		return domain.EditFactCheckResult{}, domain.ErrChannelInvalid
	}
	msg, err := s.getChannelMessage(ctx, tx, req.ChannelID, req.MessageID)
	if err != nil {
		return domain.EditFactCheckResult{}, err
	}
	if msg.Deleted || msg.Action != nil {
		return domain.EditFactCheckResult{}, domain.ErrMessageIDInvalid
	}
	if msg.SenderUserID != req.UserID && !canEditChannelMessage(member) {
		return domain.EditFactCheckResult{}, domain.ErrFactCheckForbidden
	}
	if domain.FactChecksEqual(msg.FactCheck, check) {
		return domain.EditFactCheckResult{}, domain.ErrMessageNotModified
	}
	pts, err := s.reserveChannelPts(ctx, tx, req.ChannelID)
	if err != nil {
		return domain.EditFactCheckResult{}, fmt.Errorf("allocate channel fact check pts: %w", err)
	}
	msg, err = scanChannelMessage(tx.QueryRow(ctx, `
UPDATE channel_messages
SET factcheck = $3,
    pts = $4,
    updated_at = now()
WHERE channel_id = $1 AND id = $2 AND NOT deleted
RETURNING `+channelMessageColumns,
		req.ChannelID, req.MessageID, string(payload), pts))
	if errors.Is(err, pgx.ErrNoRows) {
		return domain.EditFactCheckResult{}, domain.ErrMessageIDInvalid
	}
	if err != nil {
		return domain.EditFactCheckResult{}, fmt.Errorf("update channel fact check: %w", err)
	}
	event := domain.ChannelUpdateEvent{
		ChannelID:    req.ChannelID,
		Type:         domain.ChannelUpdateEditMessage,
		Pts:          pts,
		PtsCount:     1,
		Date:         req.Date,
		Message:      msg,
		SenderUserID: req.UserID,
	}
	if err := insertChannelEventTx(ctx, tx, event); err != nil {
		return domain.EditFactCheckResult{}, err
	}
	if _, err := tx.Exec(ctx, `UPDATE channels SET pts = $2, updated_at = now() WHERE id = $1`, req.ChannelID, pts); err != nil {
		return domain.EditFactCheckResult{}, fmt.Errorf("update channel fact check pts: %w", err)
	}
	if err := tx.Commit(ctx); err != nil {
		return domain.EditFactCheckResult{}, fmt.Errorf("commit edit fact check: %w", err)
	}
	committed = true
	channel.Pts = pts
	recipients, _ := s.ListActiveChannelMemberIDs(ctx, req.UserID, req.ChannelID, 0)
	return domain.EditFactCheckResult{Channel: channel, Message: msg, FactCheck: check, Event: event, Recipients: recipients}, nil
}