fix: sync moderation flag convergence
This commit is contained in:
parent
5d5883a3d1
commit
3dd9c345d7
10 changed files with 564 additions and 5 deletions
|
|
@ -6,7 +6,11 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store/postgres/sqlcgen"
|
||||
)
|
||||
|
||||
func (s *ChannelStore) EditChannelTitle(ctx context.Context, req domain.EditChannelTitleRequest) (domain.EditChannelTitleResult, error) {
|
||||
|
|
@ -292,16 +296,80 @@ func (s *ChannelStore) SetChannelScamFake(ctx context.Context, channelID int64,
|
|||
if scam && fake {
|
||||
return domain.Channel{}, domain.ErrPeerModerationFlagsInvalid
|
||||
}
|
||||
channel, err := s.channelByID(ctx, s.db, channelID)
|
||||
beginner, ok := s.db.(txBeginner)
|
||||
if !ok {
|
||||
return domain.Channel{}, fmt.Errorf("set channel scam/fake: db does not support transactions")
|
||||
}
|
||||
tx, err := beginner.Begin(ctx)
|
||||
if err != nil {
|
||||
return domain.Channel{}, fmt.Errorf("begin set channel scam/fake: %w", err)
|
||||
}
|
||||
committed := false
|
||||
defer func() {
|
||||
if !committed {
|
||||
_ = tx.Rollback(ctx)
|
||||
}
|
||||
}()
|
||||
var currentScam, currentFake bool
|
||||
if err := tx.QueryRow(ctx, `
|
||||
SELECT scam, fake
|
||||
FROM channels
|
||||
WHERE id = $1 AND NOT deleted
|
||||
FOR UPDATE`, channelID).Scan(¤tScam, ¤tFake); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
return domain.Channel{}, fmt.Errorf("lock channel scam/fake: %w", err)
|
||||
}
|
||||
channel, err := s.channelByID(ctx, tx, channelID)
|
||||
if err != nil {
|
||||
return domain.Channel{}, err
|
||||
}
|
||||
if channel.Scam == scam && channel.Fake == fake {
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return domain.Channel{}, fmt.Errorf("commit unchanged channel scam/fake: %w", err)
|
||||
}
|
||||
committed = true
|
||||
return channel, nil
|
||||
}
|
||||
if _, err := s.db.Exec(ctx, `UPDATE channels SET scam = $2, fake = $3, updated_at = now() WHERE id = $1 AND NOT deleted`, channelID, scam, fake); err != nil {
|
||||
if currentScam != channel.Scam || currentFake != channel.Fake {
|
||||
return domain.Channel{}, fmt.Errorf("channel scam/fake snapshot changed while locked")
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `UPDATE channels SET scam = $2, fake = $3, updated_at = now() WHERE id = $1 AND NOT deleted`, channelID, scam, fake); err != nil {
|
||||
return domain.Channel{}, fmt.Errorf("set channel scam/fake: %w", err)
|
||||
}
|
||||
audience, err := channelModerationFlagAudience(ctx, tx, channelID, maxModerationFlagAudience)
|
||||
if err != nil {
|
||||
return domain.Channel{}, err
|
||||
}
|
||||
qtx := sqlcgen.New(tx)
|
||||
date := nowUnix()
|
||||
for _, viewerUserID := range audience {
|
||||
pts, err := reserveUserPts(ctx, tx, viewerUserID, 1)
|
||||
if err != nil {
|
||||
return domain.Channel{}, fmt.Errorf("reserve channel moderation flag pts for viewer %d: %w", viewerUserID, err)
|
||||
}
|
||||
event := domain.UpdateEvent{
|
||||
UserID: viewerUserID,
|
||||
Type: domain.UpdateEventChannelState,
|
||||
Pts: pts, PtsCount: 1, Date: date,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: channelID},
|
||||
}
|
||||
if err := appendUserUpdateEvent(ctx, tx, qtx, viewerUserID, event); err != nil {
|
||||
return domain.Channel{}, fmt.Errorf("append channel moderation flag event for viewer %d: %w", viewerUserID, err)
|
||||
}
|
||||
if err := enqueueDispatch(ctx, qtx, sqlcgen.EnqueueDispatchParams{
|
||||
TargetUserID: viewerUserID,
|
||||
Pts: int32(pts),
|
||||
EventType: string(event.Type),
|
||||
}); err != nil {
|
||||
return domain.Channel{}, fmt.Errorf("enqueue channel moderation flag dispatch for viewer %d: %w", viewerUserID, err)
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return domain.Channel{}, fmt.Errorf("commit channel scam/fake: %w", err)
|
||||
}
|
||||
committed = true
|
||||
if s.rowCache != nil {
|
||||
s.rowCache.delete(channelID)
|
||||
}
|
||||
|
|
@ -310,6 +378,37 @@ func (s *ChannelStore) SetChannelScamFake(ctx context.Context, channelID int64,
|
|||
return channel, nil
|
||||
}
|
||||
|
||||
func channelModerationFlagAudience(ctx context.Context, db sqlcgen.DBTX, channelID int64, limit int) ([]int64, error) {
|
||||
if channelID <= 0 || limit <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
rows, err := db.Query(ctx, `
|
||||
SELECT cm.user_id
|
||||
FROM channel_members cm
|
||||
JOIN users u ON u.id = cm.user_id AND u.deleted_at IS NULL
|
||||
WHERE cm.channel_id = $1 AND cm.status = 'active'
|
||||
ORDER BY cm.user_id
|
||||
LIMIT $2`, channelID, limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list channel moderation flag audience: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make([]int64, 0)
|
||||
for rows.Next() {
|
||||
var userID int64
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
return nil, fmt.Errorf("scan channel moderation flag audience: %w", err)
|
||||
}
|
||||
if userID != 0 {
|
||||
out = append(out, userID)
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate channel moderation flag audience: %w", err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SetChannelAdminSettings applies an admin-direct moderation-settings patch
|
||||
// (no membership/permission checks). nil fields are left unchanged.
|
||||
func (s *ChannelStore) SetChannelAdminSettings(ctx context.Context, channelID int64, patch domain.ChannelAdminSettings) (domain.Channel, error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue