Отсутствует счётчик ссылок для вложений

Недавно я выполнил обновление, и несколько пользователей сообщили о проблеме: у загруженных файлов (в основном текстовых) отсутствуют счётчики.

При загрузке файла формируется следующий синтаксис:

[test.txt|attachment](upload://ekKqBuaScWkd1tpIoytYePGOjGw.txt)

В этом случае счётчик кликов не отображается, однако если убрать часть |attachment, счётчик появляется.

За последние несколько месяцев это поведение изменилось: теперь счётчики не отображаются независимо от наличия |attachment.

Есть ли способ восстановить эту функциональность?

Разве вторая часть не предназначена для разрешения изображения? Мне кажется странным, что она вообще присутствует для не-изображений.

Я отладил проблему и нашёл потенциальное решение (с помощью Gemini).

Я создал это сообщение (содержимое перетаскиваемого файла не имеет значения) в версии 3.4.5 (которая, как я знал, работала) и в последней версии:

Затем я использовал два других аккаунта, чтобы скачать оба файла, и обновил страницу.

Я не видел счётчика загрузок для «без-вложения» (хотя он работал, когда я использовал исходную версию v3.4.5). Для «с-вложением» счётчик загрузок так и не отображался; я добавил его только ради «полноты картины».

При выполнении этого в отладчике:

post = Post.last
TopicLink.counts_for(Guardian.new(post.user), post.topic, [post])

я получил:

=> {8 =>
  [{url: "/uploads/short-url/iMUBjeLNs0eRWZhdYnh24XGXY0f.funscript", clicks: 2, title: nil, internal: true, reflection: false},
   {url: "/uploads/short-url/rgnPszmhmEizcRVBobULTkMP1UI.funscript", clicks: 0, title: nil, internal: true, reflection: false}]}

Так что клики были, но пользовательский интерфейс их не учитывал, потому что они были отфильтрованы правилом, предотвращающим пользователям видеть количество кликов по ссылкам, указывающим на закрытые/скрытые темы. Проблема в том, что новое правило не учитывает, что внутренняя ссылка может быть темой ИЛИ внутренним файлом. Любое, что является internal и не является темой, в настоящее время отбрасывается.

Мне удалось решить проблему, изменив (в topic_link.rb):

def self.apply_link_visibility_filters(builder, link:, target_topic:, target_posts:)
    builder.where(<<\~SQL)
      #{target_topic}.deleted_at IS NULL
      AND (#{target_topic}.id IS NULL OR #{target_topic}.visible = true)
      AND (#{link}.internal = false OR #{target_topic}.id IS NOT NULL)
      AND (#{link}.link_post_id IS NULL OR (#{link_posts}.id IS NOT NULL AND #{link_posts}.deleted_at IS NULL))
    SQL
  end

на это:

def self.apply_link_visibility_filters(builder, link:, target_topic:, target_posts:)
    builder.where(<<\~SQL)
      #{target_topic}.deleted_at IS NULL
      AND (#{target_topic}.id IS NULL OR #{target_topic}.visible = true)
      AND (#{link}.internal = false OR #{link}.link_topic_id IS NULL OR #{target_topic}.id IS NOT NULL)
      AND (#{link}.link_post_id IS NULL OR (#{link_posts}.id IS NOT NULL AND #{link_posts}.deleted_at IS NULL))
    SQL
  end
Полное объяснение от Gemini

Вы нашли точную «копья в копилку»! Предоставив две версии topic_link.rb, стало кристально ясно, что именно сломало эту функцию в последней версии Discourse.

Проблема полностью вызвана новым фильтром безопасности, добавленным в запрос к базе данных в counts_for.

Виновник: новый метод apply_link_visibility_filters

Если посмотреть в нижней части нового файла topic_link.rb, можно найти этот новый метод:

codeRuby

def self.apply_link_visibility_filters(builder, link:, target_topic:, target_posts:)
    builder.where(<<~SQL)
      #{target_topic}.deleted_at IS NULL
      AND (#{target_topic}.id IS NULL OR #{target_topic}.visible = true)
      AND (#{link}.internal = false OR #{target_topic}.id IS NOT NULL)  <--- ЭТО ОШИБКА
      AND (#{link}.link_post_id IS NULL OR (#{link_posts}.id IS NOT NULL AND #{link_posts}.deleted_at IS NULL))
    SQL
  end

Почему это ломает стандартные загрузки файлов

Разработчики Discourse добавили это SQL-правило, чтобы предотвратить пользователям видеть количество кликов по ссылкам, указывающим на закрытые/скрытые темы, к которым у них нет прав доступа. Однако они случайно затронули загрузки файлов.

Вот как именно SQL-движок оценивает вашу ссылку на загрузку without-attachment.funscript:

  1. #{link}.internal = false: Когда файл сохраняется, Discourse помечает его как internal = true (потому что файл размещён на вашем форуме). Поэтому это оценивается как FALSE.
  2. #{target_topic}.id IS NOT NULL: Поскольку это загрузка файла, а не ссылка на другой пост форума, у него нет целевой темы. ID темы равен NULL. Поэтому это также оценивается как FALSE.
  3. FALSE OR FALSE = FALSE.

Поскольку оба условия не выполняются, SQL-запрос полностью отфильтровывает вашу загрузку файла, и метод counts_for возвращает пустой массив сериализатору.

(Примечание: Причина, по которой |attachment всё ещё работает, заключается в том, что фронтенд обрабатывает классы .attachment через совершенно отдельный конечный пункт API/механизм для отслеживания загрузок, минуя TopicLink!)

Как исправить код

Чтобы исправить этот баг в вашей локальной среде, вам нужно сообщить SQL-запросу: «Нормально, если внутренняя ссылка не имеет целевой темы, если только она изначально и не должна была её иметь (как загрузка файла)».

Откройте app/models/topic_link.rb в вашем Dev Container, найдите метод apply_link_visibility_filters (около строки 185) и измените сломанную строку на эту:

Измените это:

AND (#{link}.internal = false OR #{target_topic}.id IS NOT NULL)

На это:

AND (#{link}.internal = false OR #{link}.link_topic_id IS NULL OR #{target_topic}.id IS NOT NULL)

Почему это исправляет проблему:
Добавляя #{link}.link_topic_id IS NULL, вы сообщаете базе данных разрешать внутренние ссылки, которые являются чисто файлами/загрузками. Это по-прежнему успешно блокирует ссылки, которые имеют link_topic_id, но целевая тема отсутствует/скрыта (что удовлетворяет требованию безопасности, которое пытались достичь разработчики Discourse).

Вам стоит отправить им pull request, чтобы исправить это.