Falta el contador de enlaces para los archivos adjuntos

He debugged the problem and has a potential fix (with the help of Gemini).

I created this message (the content of the dragged file is not important) on version 3.4.5 (which I knew worked) and the latest version:

I then used 2 others accounts to download both files and refreshed the page.

I didn’t see a download counter for the “without-attachement” (even though it worked when I used source v3.4.5). “with-attachment” never showed download counter, I only added it for “completeness”.

When execution this in the debugger:

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

I got:

=> {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}]}

So the clicks were there, but the UI didn’t consider them because there were filtered by the rule to prevent users from seeing click counts for links pointing to private/hidden topics. The problem is that the new rule doesn’t take into account that an internal link can be a topic OR an internal file. Anything internal and not a topic is currently discarded.

I was able to resolve the problem by changing (in 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 (#{target_posts}.id IS NOT NULL AND #{target_posts}.deleted_at IS NULL))
    SQL
  end

to this:

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 (#{target_posts}.id IS NOT NULL AND #{target_posts}.deleted_at IS NULL))
    SQL
  end
Explicación completa de Gemini

¡Encontraste la prueba definitiva! Al proporcionar las dos versiones de topic_link.rb, queda clarísimo exactamente qué rompió esta funcionalidad en la última versión de Discourse.

El problema es causado enteramente por un nuevo filtro de seguridad añadido a la consulta de base de datos en counts_for.

El culpable: el nuevo método apply_link_visibility_filters

Si miras cerca del final del nuevo archivo topic_link.rb, encontrarás este nuevo método:

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)  <--- ESTO ES EL BUG
      AND (#{link}.link_post_id IS NULL OR (#{target_posts}.id IS NOT NULL AND #{target_posts}.deleted_at IS NULL))
    SQL
  end

Por qué esto rompe las cargas de archivos estándar

Los desarrolladores de Discourse añadieron esta regla SQL para prevenir que los usuarios vieran las cuentas de clics de enlaces que apuntan a temas privados/ocultos a los que no tienen permiso para ver. Sin embargo, accidentalmente atraparon las cargas de archivos en el fuego cruzado.

Aquí está exactamente cómo el motor SQL evalúa tu enlace de carga without-attachment.funscript:

  1. #{link}.internal = false: Cuando una carga se guarda, Discourse la marca como internal = true (porque el archivo está alojado en tu foro). Por lo tanto, esto se evalúa como FALSO.
  2. #{target_topic}.id IS NOT NULL: Como esta es una carga de archivo y no un enlace a otra publicación del foro, no tiene un tema objetivo. El ID del tema es NULL. Por lo tanto, esto también se evalúa como FALSO.
  3. FALSO O FALSO = FALSO.

Como ambas condiciones fallan, la consulta SQL filtra por completo tu carga de archivo, y el método counts_for devuelve un array vacío al serializador.

(Nota: La razón por la que |attachment sigue funcionando es porque el frontend maneja las clases .attachment a través de un punto final de API/mecanismo completamente separado para el seguimiento de descargas, ¡eludiendo TopicLink por completo!)

Cómo corregir el código

Para corregir este error en tu entorno local, necesitas decirle a la consulta SQL: “Está bien si un enlace interno no tiene un tema objetivo, siempre y cuando nunca se haya supuesto que tuviera uno desde el principio (como una carga de archivo).”

Abre app/models/topic_link.rb en tu Dev Container, encuentra el método apply_link_visibility_filters (alrededor de la línea 185) y cambia la línea rota a esto:

Cambia esto:

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

Por esto:

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

Por qué esto lo corrige:
Al añadir #{link}.link_topic_id IS NULL, le estás diciendo a la base de datos que permita enlaces internos que son puramente archivos/cargas. Todavía bloqueará con éxito los enlaces que sí tienen un link_topic_id pero el target_topic está ausente/oculto (lo cual satisface el requisito de seguridad que los desarrolladores de Discourse estaban intentando lograr).