نقل منتدى NodeBB باستخدام MongoDB إلى Discourse

كما تعلمون جيدًا، يدعم NodeBB نوعين من قواعد البيانات الخلفية: Redis و MongoDB. يدعم سكريبت استيراد Discourse كلا النوعين. في هذا الدليل، سنتعلم كيفية نقل NodeBB باستخدام MongoDB كقاعدة بيانات خلفية. سنستخدم NodeBB Importer مع مُكيّف mongo. إذا كان منتدى NodeBB الخاص بك يستخدم Redis كقاعدة بيانات خلفية، فالرجاء اتباع هذا الدليل الذي يوضح مُكيّف redis.

الخطة

  • إعداد بيئة التطوير.
  • تصدير قاعدة البيانات من بيئة الإنتاج.
  • استيراد قاعدة بيانات الإنتاج إلى مثيل Discourse.
  • تشغيل سكريبت الاستيراد.

ما يمكن نقله

  • المجموعات
  • المرفقات
  • الفئات
    • الفئة الجذرية =\u003e الفئة الجذرية
    • الفئة الفرعية والفئة تحت الفرعية =\u003e الفئة الفرعية
  • المواضيع والمنشورات
    • الموضوع المثبت =\u003e موضوع مثبت
    • الموضوع المقفل =\u003e موضوع مغلق
    • مشاهدات الموضوع
    • المُصوّتون بالإعجاب (upvoted_by)
    • الأنماط، والإشارات، والرموز التعبيرية، والمرفقات.
  • المستخدمين (مع السمات التالية)
    • خلفية الملف الشخصي
    • الصور الرمزية (Avatars)
    • حالة الحظر
    • اسم المستخدم
    • الاسم
    • البريد الإلكتروني
    • المسؤول (Admin)
    • السيرة الذاتية (Bio)
    • المجموعة
    • الموقع الإلكتروني
    • الموقع الجغرافي
    • تاريخ الانضمام

إعداد بيئة التطوير المحلية

كما هو مذكور في خطتنا، نحتاج أولاً إلى إعداد بيئة التطوير لدينا. اتبع أحد هذه الأدلة لتثبيت Discourse نفسه:

:bulb: الرجاء مراجعة هذا الدليل إذا واجهت أي مشاكل في إعداد خادم Discourse.

ثم قم بتثبيت خادم قاعدة بيانات MongoDB.

Ubuntu-18-04:

$ wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add -
$ echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
$ sudo apt-get update
$ sudo apt-get install -y mongodb-org
$ sudo service mongod status

لمزيد من التفاصيل، راجع الدليل الرسمي.

Mac OS:

$ brew tap mongodb/brew
$ brew install mongodb-community@4.0
$ brew services start mongodb-community@4.0
$ brew services status mongodb-community@4.0

لمزيد من التفاصيل، راجع الدليل الرسمي.

Windows 10:

قم بتنزيل برنامج التثبيت وتثبيت MongoDB كخدمة Windows:

https://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-4.0.12-signed.msi

قم بتشغيل cmd بصلاحيات المسؤول للتحقق من عمل خادم mongo بشكل صحيح:

"C:\Program Files\MongoDB\Server\4.0\bin\mongo.exe"

لمزيد من التفاصيل، راجع الدليل الرسمي.

ستكون هذه البيئة هي خادم Discourse لدينا.

تصدير نسخة احتياطية من قاعدة بيانات الإنتاج:

أوقف تشغيل منتدى NodeBB الخاص بك (خادم الإنتاج).

$ cd /path_to_nodebb
$ ./nodebb stop

أوقف تشغيل قاعدة البيانات:

$ sudo service mongodb stop

قم بنسخ قاعدة البيانات احتياطيًا:

$ mongodump --out ~/my_backup_path/

سيكون مخرجات الأمر السابق شبيهًا بهذا:

2019-08-16T15:17:09.845+0300 done dumping admin.system.users (1 document)
2019-08-16T15:17:09.846+0300 done dumping admin.system.version (2 documents)
2019-08-16T15:17:09.849+0300 done dumping nodebb.sessions (10 documents)
2019-08-16T15:17:09.850+0300 done dumping nodebb.socket.io (215 documents)
2019-08-16T15:17:09.854+0300 done dumping nodebb.objects (1488 documents)

لاحظ أن النسخة الاحتياطية هي في الواقع مجلد وليس مجرد ملف.

:bulb: يمكنك التحقق من حجم قاعدة البيانات الخاصة بك بتشغيل use myDatabase ثم db.stats().dataSize; داخل واجهة سطر أوامر mongo. ستكون القيمة المعادة بالبايت.

قم بنسخ أصول المنتدى احتياطيًا:

$ cd /path_to_nodebb_root_directory/
$ tar -czf ./uploads.tar.gz ./public/uploads

بعد الحصول على قاعدة البيانات وأصول المنتدى، يجب نسخها إلى خادم Discourse.

استيراد قاعدة البيانات

الآن بعد أن أصبح لدينا قاعدة البيانات، يمكننا استيرادها في مثيل MongoDB المحلي لدينا:

$ mongorestore ~/path_of_my_backup_directory/

لمزيد من الخيارات، راجع الدليل الرسمي.

بعد ذلك، تحتاج إلى استخراج ملف uploads.tar.gz حتى يتمكن المستورد من استيراد الأصول:

$ tar xvzf uploads.tar.gz

تشغيل سكريبت المستورد

الآن بعد أن أصبح لدينا قاعدة البيانات والأصول في مكانها الصحيح، نحن مستعدون لتشغيل سكريبت المستورد. قبل ذلك، نحتاج إلى تحرير سكريبت مستورد NodeBB ليتناسب مع احتياجاتنا.

هذا هو مسار مجلد التحميلات (uploads) الخاص بـ NodeBB:

ATTACHMENT_DIR = '/absolute_path/uploads'

بعد ذلك، نحتاج إلى إخبار المستورد باستخدام مُكيّف mongo بدلاً من مُكيّف redis:

adapter = NodeBB::Mongo
@client = adapter.new('mongodb://127.0.0.1:27017/nodebb')

# adapter = NodeBB::Redis
# @client = adapter.new(
# host: "localhost",
# port: "6379",
# db: 14
# )

شغّل المستورد مع دعم Discourse النظيف ومكتبة mongo:

$ cd ~/discourse
$ echo "gem 'mongo'" >> Gemfile
$ bundle install
$ bundle exec rake db:drop db:create db:migrate
$ bundle exec ruby script/import_scripts/nodebb/nodebb.rb

سيتم توصيل المستورد بمثيل MongoDB وينقل كل شيء إلى Discourse.

بعد انتهاء المستورد، قم بتشغيل Discourse:

$ bundle exec rails server

قم بتشغيل Sidekiq لمعالجة البيانات المنقولة:

$ bundle exec sidekiq

يمكنك مراقبة التقدم في http://localhost:3000/sidekiq/queues.

قم بنسخ احتياطي لـ Discourse وقم برفعه إلى خادم Discourse الإنتاجي الخاص بك باتباع هذا الدليل.

:tada:

إذا كان لديك أي أسئلة حول العملية، فسأكون سعيدًا للمساعدة.

نقل سعيد :grinning:

10 إعجابات

Great work really. While I was trying, I am getting below error. Any idea what I might be doing wrong here?

importing groups
   10 / 10 (100.0%)  [474765 items/min]    
importing top level categories...
    8 / 8 (100.0%)  [437896 items/min]    
importing child categories...
   68 / 68 (100.0%)  [774048 items/min]  
importing users
 5534 / 5534 (100.0%)  [355520 items/min]    
adding users to groups...

importing topics...
Traceback (most recent call last):
	12: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
	11: from /home/fsuzer/discourse/script/import_scripts/base.rb:47:in `perform'
	10: from script/import_scripts/nodebb/nodebb.rb:52:in `execute'
	 9: from script/import_scripts/nodebb/nodebb.rb:336:in `import_topics'
	 8: from /home/fsuzer/discourse/script/import_scripts/base.rb:877:in `batches'
	 7: from /home/fsuzer/discourse/script/import_scripts/base.rb:877:in `loop'
	 6: from /home/fsuzer/discourse/script/import_scripts/base.rb:878:in `block in batches'
	 5: from script/import_scripts/nodebb/nodebb.rb:337:in `block in import_topics'
	 4: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `topics'
	 3: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `map'
	 2: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `block in topics'
	 1: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:79:in `topic'
/home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:100:in `post': undefined method `[]' for nil:NilClass (NoMethodError)

Some field from the topic is empty. Or perhaps the topic query returned nothing.

إعجاب واحد (1)

Can someone help me?

Error: Could not find gem ‘mongo’ in any of the gem sources listed in your Gemfile.

After $ bundle install

This will happen if you skipped this:

Make sure you Gemfile has this line:

gem 'mongo'

Then run:

$ bundle install
إعجاب واحد (1)

Gem

Thanks for your help. But this line already exists in my file. I also did the previous steps informed.

I don’t know if that helps, but when I run gem list mongo it’s not listed.

Your Gemfile sounds weird to me. Please, can you share the whole content?

Your Gemfile is just wrong. It should have another content, just like this:

In addition to:

gem 'mongo'

Hey,

Thanks for the guide! When I run the migration script, this is what I get when the script reaches the point where it imports users:

Traceback (most recent call last):
        21: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        20: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:47:in `perform'
        19: from script/import_scripts/nodebb/nodebb.rb:50:in `execute'
        18: from script/import_scripts/nodebb/nodebb.rb:130:in `import_users'
        17: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:262:in `create_users'
        16: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:262:in `each'
        15: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:274:in `block in create_users'
        14: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:391:in `create_user'
        13: from /home/odyslam/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/core_ext/object/try.rb:15:in `try'
        12: from /home/odyslam/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/core_ext/object/try.rb:15:in `public_send'
        11: from script/import_scripts/nodebb/nodebb.rb:164:in `block (2 levels) in import_users'
        10: from script/import_scripts/nodebb/nodebb.rb:211:in `import_profile_picture'
         9: from /home/odyslam/forum/discourse/lib/upload_creator.rb:45:in `create_for'
         8: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:14:in `synchronize'
         7: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         6: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         5: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:33:in `block in synchronize'
         4: from /home/odyslam/forum/discourse/lib/upload_creator.rb:66:in `block in create_for'
         3: from /home/odyslam/forum/discourse/lib/upload_creator.rb:381:in `optimize!'
         2: from /home/odyslam/forum/discourse/app/models/optimized_image.rb:178:in `ensure_safe_paths!'
         1: from /home/odyslam/forum/discourse/app/models/optimized_image.rb:178:in `each'
/home/odyslam/forum/discourse/app/models/optimized_image.rb:179:in `block in ensure_safe_paths!': Discourse::InvalidAccess (Discourse::InvalidAccess)

I managed to bypass this by commenting out the part that uploads profile pictures, that’s not a problem.

Now I get the nil error when importing topics. :frowning:

Traceback (most recent call last):
        12: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        11: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:47:in `perform'
        10: from script/import_scripts/nodebb/nodebb.rb:52:in `execute'
         9: from script/import_scripts/nodebb/nodebb.rb:336:in `import_topics'
         8: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:862:in `batches'
         7: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:862:in `loop'
         6: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:863:in `block in batches'
         5: from script/import_scripts/nodebb/nodebb.rb:337:in `block in import_topics'
         4: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `topics'
         3: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `map'
         2: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `block in topics'
         1: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:79:in `topic'
/home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:100:in `post': undefined method `[]' for nil:NilClass (NoMethodError)

في حال كان هناك اهتمام بالمستقبل، فقد تمكنت من إجراء الترحيل. إليك بعض التعليقات حول تجربتي؛ فهناك بعض النقاط التي يجب الانتباه إليها خلال العملية.

https://odyslam.com/blog/Migrating-from-Nodebb-to-Discourse/

إعجابَين (2)
21: من script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        20: from /home/nodebb/discourse/script/import_scripts/base.rb:47:in `perform'
        19: from script/import_scripts/nodebb/nodebb.rb:50:in `execute'
        18: from script/import_scripts/nodebb/nodebb.rb:130:in `import_users'
        17: from /home/nodebb/discourse/script/import_scripts/base.rb:264:in `create_users'
        16: from /home/nodebb/discourse/script/import_scripts/base.rb:264:in `each'
        15: from /home/nodebb/discourse/script/import_scripts/base.rb:276:in `block in create_users'
        14: from /home/nodebb/discourse/script/import_scripts/base.rb:393:in `create_user'
        13: from /home/nodebb/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/object/try.rb:15:in `try'
        12: from /home/nodebb/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/object/try.rb:15:in `public_send'
        11: from script/import_scripts/nodebb/nodebb.rb:164:in `block (2 levels) in import_users'
        10: from script/import_scripts/nodebb/nodebb.rb:211:in `import_profile_picture'
         9: from /home/nodebb/discourse/lib/upload_creator.rb:64:in `create_for'
         8: from /home/nodebb/discourse/lib/distributed_mutex.rb:14:in `synchronize'
         7: from /home/nodebb/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         6: from /home/nodebb/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         5: from /home/nodebb/discourse/lib/distributed_mutex.rb:33:in `block in synchronize'
         4: from /home/nodebb/discourse/lib/upload_creator.rb:83:in `block in create_for'
         3: from /home/nodebb/discourse/lib/upload_creator.rb:501:in `optimize!'
         2: from /home/nodebb/discourse/app/models/optimized_image.rb:181:in `ensure_safe_paths!'
         1: from /home/nodebb/discourse/app/models/optimized_image.rb:181:in `each'
/home/nodebb/discourse/app/models/optimized_image.rb:182:in `block in ensure_safe_paths!': Discourse::InvalidAccess (Discourse::InvalidAccess)

@enigmaty أواجه نفس الخطأ الذي واجهه @OdysLam @FreeWorLD.

مرحباً!
هل حاول أي شخص الترحيل من أحدث إصدار NodeBB 4.9.x إلى إصدار التطوير الحالي لـ Discourse؟

/home/dev/discourse/script/import_scripts/nodebb/mongo.rb:100:in 'NodeBB::Mongo#post': undefined method '[]' for nil (NoMethodError)

      post["timestamp"] = timestamp_to_date(post["timestamp"])
                                                ^^^^^^^^^^^^^
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'block in NodeBB::Mongo#posts'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'Array#map'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'NodeBB::Mongo#posts'
        from script/import_scripts/nodebb/nodebb.rb:413:in 'block in ImportScripts::NodeBB#import_posts'
        from /home/dev/discourse/script/import_scripts/base.rb:943:in 'block in ImportScripts::Base#batches'
        from <internal:kernel>:168:in 'Kernel#loop'
        from /home/dev/discourse/script/import_scripts/base.rb:942:in 'ImportScripts::Base#batches'
        from script/import_scripts/nodebb/nodebb.rb:412:in 'ImportScripts::NodeBB#import_posts'
        from script/import_scripts/nodebb/nodebb.rb:52:in 'ImportScripts::NodeBB#execute'
        from /home/dev/discourse/script/import_scripts/base.rb:47:in 'ImportScripts::Base#perform'
        from script/import_scripts/nodebb/nodebb.rb:568:in '<main>'

لقد واجهت هذا الخطأ أثناء الترحيل في بيئة التطوير.
أي أفكار؟

هل لا تزال نصوص الترحيل مدعومة من قبل المجتمع في عام 2026؟
الحقل timestamp موجود ويحتوي على بيانات صالحة

شكراً.

المشكلة هي أن post هو nil. لذا لم تقم بإعداد الأمور بشكل صحيح. هل قام باستيراد المستخدمين؟

يمكننا المحاولة، لكنها محدودة للغاية. من المحتمل أن تحتاج إلى فهم كيفية عمل الكثير من الأشياء التي قد تتجاوز ما يمكن دعمه بسهولة في منتدى.

3 إعجابات
استيراد المستخدمين
      182 / 182 (100.0%)  [437953 items/min]
إضافة المستخدمين إلى المجموعات...

استيراد المواضيع...
      132 / 132 (100.0%)  [3567036 items/min]
استيراد المشاركات...
/home/dev/discourse/script/import_scripts/nodebb/mongo.rb:100:in 'NodeBB::Mongo#post': undefined method '[]' for nil (NoMethodError)

      post["timestamp"] = timestamp_to_date(post["timestamp"])
                                                ^^^^^^^^^^^^^
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'block in NodeBB::Mongo#posts'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'Array#map'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'NodeBB::Mongo#posts'
        from script/import_scripts/nodebb/nodebb.rb:413:in 'block in ImportScripts::NodeBB#import_posts'
        from /home/dev/discourse/script/import_scripts/base.rb:943:in 'block in ImportScripts::Base#batches'
        from 内部:kernel:168:in 'Kernel#loop'
        from /home/dev/discourse/script/import_scripts/base.rb:942:in 'ImportScripts::Base#batches'
        from script/import_scripts/nodebb/nodebb.rb:412:in 'ImportScripts::NodeBB#import_posts'
        from script/import_scripts/nodebb/nodebb.rb:52:in 'ImportScripts::NodeBB#execute'
        from /home/dev/discourse/script/import_scripts/base.rb:47:in 'ImportScripts::Base#perform'
        from script/import_scripts/nodebb/nodebb.rb:568:in '<main>'


يبدو أن المستخدمين قد تم استيرادهم بشكل صحيح وفقًا لمخرجات البرنامج النصي، انظر أعلاه.

عذرًا، هل أنت شخص على قيد الحياة أم مجرد مساعد ذكاء اصطناعي ذكي بما فيه الكفاية؟

إذًا المشكلة تتعلق بعدم قراءة بيانات المنشورات.

تخميني هو أنها هنا ولسبب ما لا تجد المنشورات.

    def posts(offset = 0, page_size = 2000)
      post_keys = mongo.find(_key: "posts:pid").skip(offset).limit(page_size).pluck(:value)

      post_keys.map { |post_key| post(post_key) }
    end

أنا شخص حي كسب عيشه من دعم Discourse لمدة عقد من الزمان وكتبت مجموعة من أدوات الاستيراد وقمت باستيراد أكثر من مائة منتدى من منصات مختلفة أخرى.

إذا كنت تريد المساعدة من مساعد ذكاء اصطناعي، فانظر https://ask.discourse.com/

6 إعجابات

تمكنت بطريقة ما من جعل دالة السكريبت تعمل بشكل صحيح.

إليك التغييرات التي أجريتها (بمساعدة بسيطة من Claude Code :slight_smile:)

 =>mongo.rb
   
   def posts(offset = 0, page_size = 1000)
      post_keys = mongo.find(_key: "posts:pid").skip(offset).limit(page_size).pluck(:value)
      post_keys
          .map { |pid| post(pid) }
          .compact  # <-- يسقط أي نتائج فارغة (معرفات المنشورات اليتيمة)
      post_keys.map { |post_key| post(post_key) }
    end

    def post(id)
    post = mongo.find(_key: "post:#{id}").first
    return nil if post.nil? # <-- التحقق من القيمة الفارغة
    post["timestamp"] = timestamp_to_date(post["timestamp"])
    if post["upvoted_by"] = mongo.find(_key: "pid:#{id}:upvote").first
        post["upvoted_by"] = mongo.find(_key: "pid:#{id}:upvote").first[:members]
      else
        post["upvoted_by"] = []
      end

      post["pid"] = post["pid"].to_s
      post["deleted"] = post["deleted"].to_s

      post
    end
	
=>nodebb.rb

 create_posts(posts, total: post_count, offset: offset) do |post|
        # تخطي إذا كان المنشور فارغًا
		# تخطي إذا كان merged_post
        next if post.nil?
        next if @merged_posts_map[post["pid"]]

        # تخطي إذا كان محذوفًا
        next if post["deleted"] == "1"

        raw = post["content"]
        post_id = "p#{post["pid"]}"

        next if raw.blank?
        topic = topic_lookup_from_imported_post_id("t#{post["tid"]}")

        unless topic
          puts "Topic with id #{post["tid"]} not found, skipping"
          next
        end	

يبدو أنه يعمل بالطريقة الصحيحة الآن.

على الرغم من أنني لا أعرف مدى صحة هذا من وجهة نظر البنية الداخلية لـ Discourse، إلا أنه يبدو أنه يعمل للوهلة الأولى.

أي اقتراحات للتحسين والتحسين هي موضع ترحيب كبير.

إعجاب واحد (1)

نصيحة: لا تجعله يفشل بصمت تام - قم بتغيير هذا إلى

if post.nil?
  puts "!!! Could not find post #{id}"
  return nil
end

وإلا ستجد نفسك تتساءل لماذا نصف منشوراتك مفقودة وبعد ساعات من الإحباط تكتشف أن السبب هو هذا.

نفس الشيء لهذين السطرين

        next if post.nil?
        next if post["deleted"] == "1"
5 إعجابات

تمت إعادة كتابة الدالة posts في ملف mongo.rb

def posts(offset = 0, page_size = 1000)
post_keys = mongo.find(_key: “posts:pid”).sort(score: 1).skip(offset).limit(page_size).pluck(:value)
post_keys.map { |pid| post(pid)}.compact
end

اكتب نص الاقتباس هنا

يضمن هذا الأسلوب فرز المنشورات داخل موضوع بترتيب زمني صحيح. تصاعدي.
لاحظ استدعاء sort(score: 1).