لقد قمت بتشخيص المشكلة ولدي حل محتمل (بمساعدة Gemini).
أنشأت هذه الرسالة (محتوى الملف المسحوب غير مهم) في الإصدار 3.4.5 (الذي كنت أعلم أنه يعمل) وفي أحدث إصدار:
ثم استخدمت حسابين آخرين لتحميل كلا الملفين وحدثت الصفحة.
لم أرَ عدّادًا للتحميلات لـ “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}]}
لذا كانت النقرات موجودة، لكن واجهة المستخدم لم تراها لأنها تم تصفيتها بواسطة القاعدة المصممة لمنع المستخدمين من رؤية عدد النقرات للروابط التي تشير إلى مواضيع خاصة/مخفية. المشكلة هي أن القاعدة الجديدة لا تأخذ في الاعتبار أن الرابط الداخلي يمكن أن يكون موضوعًا أو ملفًا داخليًا. أي شيء 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 (#{target_posts}.id IS NOT NULL AND #{target_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 (#{target_posts}.id IS NOT NULL AND #{target_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 (#{target_posts}.id IS NOT NULL AND #{target_posts}.deleted_at IS NULL))
SQL
end
لماذا يكسر هذا عمليات رفع الملفات القياسية
أضاف مطورو Discourse قاعدة SQL هذه لمنع المستخدمين من رؤية عدد النقرات للروابط التي تشير إلى مواضيع خاصة/مخفية لا يملكون إذنًا لعرضها. ومع ذلك، وقعوا بالخطأ في رفع الملفات في النيران الصديقة.
إليك بالضبط كيف يقيّم محرك SQL رابط رفع without-attachment.funscript الخاص بك:
- #{link}.internal = false: عند حفظ رفع، يحدد Discourse أنه internal = true (لأن الملف يستضاف على منتداك). لذلك يتحول هذا إلى FALSE.
- #{target_topic}.id IS NOT NULL: لأن هذا رفع ملف وليس رابطًا إلى منشور منتدى آخر، فلا يحتوي على موضوع مستهدف. معرّف الموضوع هو NULL. لذلك يتحول هذا أيضًا إلى FALSE.
- FALSE OR FALSE = FALSE.
نظرًا لفشل كلا الشرطين، يستبعد استعلام SQL رفع الملف الخاص بك بالكامل، وتعيد طريقة counts_for مصفوفة فارغة إلى المُسلسل (serializer).
(ملاحظة: السبب في أن |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 تحقيقه).