package store import ( "context" ) // Блокировка пользователя (AGENT.md 7.2, 8.2): запрещает личные сообщения и // заявки в друзья. Хранится той же таблицей связей, что друзья: тип `blocked` // сильнее остальных, поэтому при блокировке встречные связи снимаются. // BlockUser блокирует пользователя: дружба и заявки в обе стороны снимаются, // у блокирующего остаётся связь `blocked`. func (s *Store) BlockUser(ctx context.Context, userID, targetID uint64) error { if userID == targetID { return ErrConflict } tx, err := s.writer.BeginTx(ctx, nil) if err != nil { return err } defer func() { _ = tx.Rollback() }() // Снимаем все встречные связи: и дружбу, и заявки. if _, err := tx.ExecContext(ctx, `DELETE FROM relationships WHERE (user_id = ? AND target_id = ?) OR (user_id = ? AND target_id = ?)`, int64(userID), int64(targetID), int64(targetID), int64(userID)); err != nil { return err } if _, err := tx.ExecContext(ctx, ` INSERT INTO relationships (user_id, target_id, type, created_at, updated_at) VALUES (?, ?, 'blocked', ?, ?)`, int64(userID), int64(targetID), s.Now(), s.Now()); err != nil { return err } return tx.Commit() } // UnblockUser снимает блокировку; дружбу после неё нужно заводить заново. func (s *Store) UnblockUser(ctx context.Context, userID, targetID uint64) error { relation, err := s.GetRelationship(ctx, userID, targetID) if err != nil { return err } if relation.Type != RelationshipBlocked { return ErrNotFound } return s.RemoveRelationship(ctx, userID, targetID) } // IsBlocked сообщает, блокирует ли пользователь другого (в любую сторону). func (s *Store) IsBlocked(ctx context.Context, userID, targetID uint64) (bool, error) { var count int err := s.reader.QueryRowContext(ctx, ` SELECT COUNT(*) FROM relationships WHERE type = 'blocked' AND ((user_id = ? AND target_id = ?) OR (user_id = ? AND target_id = ?))`, int64(userID), int64(targetID), int64(targetID), int64(userID)).Scan(&count) if err != nil { return false, err } return count > 0, nil } // ListBlocked возвращает заблокированных пользователей (для списка в клиенте). func (s *Store) ListBlocked(ctx context.Context, userID uint64) ([]RelationshipProfile, error) { return s.ListRelationships(ctx, userID, RelationshipBlocked) } // BlockedIDs возвращает множество пользователей, которые заблокированы // пользователем: нужно для фильтрации поиска и списков. func (s *Store) BlockedIDs(ctx context.Context, userID uint64) (map[uint64]bool, error) { rows, err := s.reader.QueryContext(ctx, `SELECT target_id FROM relationships WHERE user_id = ? AND type = 'blocked'`, int64(userID)) if err != nil { return nil, err } defer rows.Close() ids := map[uint64]bool{} for rows.Next() { var id uint64 if err := rows.Scan(&id); err != nil { return nil, err } ids[id] = true } return ids, rows.Err() } // DMBlocked проверяет, есть ли среди участников личной беседы тот, кто // блокирует пользователя или кого блокирует он сам: переписка запрещена // (AGENT.md 7.2). func (s *Store) DMBlocked(ctx context.Context, channelID, userID uint64) (bool, error) { rows, err := s.reader.QueryContext(ctx, `SELECT user_id FROM dm_participants WHERE channel_id = ?`, int64(channelID)) if err != nil { return false, err } defer rows.Close() participants := make([]uint64, 0, 2) for rows.Next() { var id uint64 if err := rows.Scan(&id); err != nil { return false, err } participants = append(participants, id) } if err := rows.Err(); err != nil { return false, err } for _, participant := range participants { if participant == userID { continue } blocked, err := s.IsBlocked(ctx, userID, participant) if err != nil { return false, err } if blocked { return true, nil } } return false, nil }