問題をデバッグし、潜在的な修正方法を見つけました(Gemini の助けを借りて)。
バージョン 3.4.5(動作することがわかっている)と最新バージョンで、このメッセージを作成しました(ドラッグされたファイルの内容は重要ではありません):
その後、別の 2 つのアカウントを使用して両方のファイルをダウンロードし、ページを再読み込みしました。
「without-attachement」(ソース v3.4.5 を使用した場合は動作していましたが)にはダウンロードカウンターが表示されませんでした。「with-attachment」にはダウンロードカウンターが一度も表示されず、私はそれを「完全性」のために追加しただけです。
デバッガでこれを実行したとき:
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}]}
つまり、クリック数は存在していましたが、プライベート/非公開トピックを指すリンクのクリック数をユーザーに表示しないためのルールによってフィルタリングされたため、UI がそれらを考慮しませんでした。問題は、新しいルールが内部リンクがトピックである場合もあれば内部ファイルである場合もあることを考慮に入れていないことです。現在、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 の 2 つのバージョンを提供してくれたおかげで、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 ルールを追加しました。しかし、誤ってファイルアップロードも巻き込んでしまいました。
without-attachment.funscript のアップロードリンクについて、SQL エンジンがどのように評価するかを正確に示します:
- #{link}.internal = false: アップロードが保存されると、Discourse はそれを internal = true としてマークします(ファイルがあなたのフォーラムにホストされているため)。したがって、これは FALSE として評価されます。
- #{target_topic}.id IS NOT NULL: これはファイルアップロードであり、別のフォーラム投稿へのリンクではないため、ターゲットトピックがありません。トピック ID は NULL です。したがって、これも FALSE として評価されます。
- FALSE OR FALSE = FALSE。
両方の条件が失敗するため、SQL クエリはファイルアップロードを完全にフィルタリングし、counts_for メソッドはシリアライザーに空の配列を返します。
(注:|attachment がまだ動作する理由は、フロントエンドが .attachment クラスをダウンロード追跡のための完全に別の API エンドポイント/メカニズムを介して処理し、TopicLink を完全にバイパスするためです!)
コードの修正方法
ローカル環境でこのバグを修正するには、SQL クエリに次のように伝える必要があります:「内部リンクにターゲットトピックがなくても、それが最初からあるべきではなかった場合(ファイルアップロードなど)は問題ない」。
Dev Container で app/models/topic_link.rb を開き、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 が ある けれども target_topic が欠落/非公開であるリンクは正常にブロックされます(これは Discourse の開発者が達成しようとしていたセキュリティ要件を満たします)。
