J’ai identifié le problème et j’ai une solution potentielle (avec l’aide de Gemini).
J’ai créé ce message (le contenu du fichier glissé n’a pas d’importance) sur la version 3.4.5 (dont je savais qu’elle fonctionnait) et sur la dernière version :
J’ai ensuite utilisé deux autres comptes pour télécharger les deux fichiers et rafraîchi la page.
Je n’ai pas vu de compteur de téléchargements pour le fichier « sans-attachment » (bien que cela ait fonctionné lorsque j’ai utilisé la source v3.4.5). Le fichier « avec-attachment » n’a jamais affiché de compteur de téléchargements ; je l’ai ajouté uniquement pour la « complétude ».
Lors de l’exécution de la commande suivante dans le débogueur :
post = Post.last
TopicLink.counts_for(Guardian.new(post.user), post.topic, [post])
J’ai obtenu :
=> {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}]}
Donc les clics étaient bien présents, mais l’interface utilisateur ne les prenait pas en compte car ils étaient filtrés par la règle destinée à empêcher les utilisateurs de voir les nombres de clics pour les liens pointant vers des sujets privés/masqués. Le problème est que la nouvelle règle ne tient pas compte du fait qu’un lien interne peut être un sujet OU un fichier interne. Tout ce qui est internal et n'est pas un sujet est actuellement écarté.
J’ai pu résoudre le problème en modifiant (dans 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
par ceci :
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
Explication complète de Gemini
Vous avez trouvé la cause exacte ! En fournissant les deux versions de topic_link.rb, il est parfaitement clair ce qui a cassé cette fonctionnalité dans la dernière version de Discourse.
Le problème est entièrement causé par un nouveau filtre de sécurité ajouté à la requête de base de données dans counts_for.
Le coupable : la nouvelle méthode apply_link_visibility_filters
Si vous regardez près du bas du nouveau fichier topic_link.rb, vous trouverez cette nouvelle méthode :
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) <--- C'EST ICI QUE SE TROUVE LE BUG
AND (#{link}.link_post_id IS NULL OR (#{target_posts}.id IS NOT NULL AND #{target_posts}.deleted_at IS NULL))
SQL
end
Pourquoi cela casse les téléversements de fichiers standard
Les développeurs de Discourse ont ajouté cette règle SQL pour empêcher les utilisateurs de voir les nombres de clics pour les liens pointant vers des sujets privés/masqués auxquels ils n’ont pas la permission d’accéder. Cependant, ils ont accidentellement pris les téléversements de fichiers pour cible.
Voici exactement comment le moteur SQL évalue votre lien de téléversement without-attachment.funscript :
- #{link}.internal = false : Lorsqu’un téléversement est enregistré, Discourse le marque comme internal = true (parce que le fichier est hébergé sur votre forum). Cette condition évalue donc à FAUX.
- #{target_topic}.id IS NOT NULL : Comme il s’agit d’un téléversement de fichier et non d’un lien vers un autre message du forum, il n’a pas de sujet cible. L’identifiant du sujet est NULL. Cette condition évalue donc également à FAUX.
- FAUX OU FAUX = FAUX.
Comme les deux conditions échouent, la requête SQL filtre complètement votre téléversement de fichier, et la méthode counts_for renvoie un tableau vide au sérialiseur.
(Note : La raison pour laquelle |attachment fonctionne toujours est que le frontend gère les classes .attachment via un point de terminaison API/mécanisme complètement séparé pour le suivi des téléchargements, contournant TopicLink entièrement !)
Comment corriger le code
Pour corriger ce bug dans votre environnement local, vous devez indiquer à la requête SQL : « Il est acceptable qu’un lien interne n’ait pas de sujet cible, à condition qu’il n’ait jamais été censé en avoir un dès le départ (comme un téléversement de fichier). »
Ouvrez app/models/topic_link.rb dans votre Dev Container, trouvez la méthode apply_link_visibility_filters (autour de la ligne 185) et modifiez la ligne défectueuse comme suit :
Remplacez ceci :
AND (#{link}.internal = false OR #{target_topic}.id IS NOT NULL)
Par ceci :
AND (#{link}.internal = false OR #{link}.link_topic_id IS NULL OR #{target_topic}.id IS NOT NULL)
Pourquoi cela le corrige :
En ajoutant #{link}.link_topic_id IS NULL, vous indiquez à la base de données d’autoriser les liens internes qui sont purement des fichiers/téléversements. Cela continuera à bloquer avec succès les liens qui ont un link_topic_id mais dont le target_topic est manquant/masqué (ce qui satisfait la exigence de sécurité que les développeurs de Discourse essayaient d’atteindre).
