ed98c78c9e
- PUT/DELETE /users/@me/blocks/{id}: блокировка снимает дружбу и заявки в обе
стороны, разблокировка возвращает возможность писать (AGENT.md 7.2)
- блокировка запрещает личные сообщения (ошибка dm.blocked), открытие беседы и
заявки в друзья с любой стороны
- клиент: раздел «Заблокированные» в друзьях со снятием блокировки, действие
«Заблокировать» в меню строки друга и в меню модерации участника
- тесты: Go (блокировка, заявки, беседа, сообщение) и Vitest (список, снятие,
блокировка из меню)
126 lines
4.3 KiB
Go
126 lines
4.3 KiB
Go
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
|
|
}
|