تم إكمال الترحيل من vB3 إلى Discourse بما في ذلك الإصدار القديم vB3 شكرًا على الإعجابات

تم الانتهاء منه، إلى حد كبير (لا يزال في بيئة الاختبار، لكن الأجزاء “الصعبة” منتهية تقريبًا، وتعمل عملية الهجرة بسلاسة)، من هجرة منتدى vb3 قديم يبلغ عمره 15 عامًا، كان في الأصل (لا تصدق!) منتدى vb2 من عام 2000.

هذا المنتدى موجود منذ حوالي 20 عامًا (مع كود PHP مخصص لا يُصدق، وجداول، وإضافات، وما إلى ذلك)، ورأيت أن Discourse هو برنامج المنتدى الوحيد الذي يستحق “مشروع هجرة”، والذي بدأت فيه منذ حوالي أسبوع، من دراسة الجدوى إلى الإنجاز (تقريبًا).

أولاً وقبل كل شيء، أود أن أشكر فريق Discourse بأكمله على هذا العمل الفني الرائع لمنتدى حديث. إن Discourse رائع حقًا، في رأيي (وأنا متأكد من أن العديد من الآخرين يرون ذلك).

ثانيًا، أود أن أشكر الفريق الذي برمج سكريبت الهجرة الأصلي vbulletin.rb. كان هذا السكريبت سليمًا بنسبة 95% تقريبًا، وقمت بتعديله ليعمل بشكل صحيح مع vb3. على سبيل المثال، لا يحتوي vb3 على جدول filedata مثل vb4 (المتعلق بالمرفقات)؛ لذا قمت بتعديل vbulletin.rb لجعله يعمل. أيضًا، كانت هناك مشاكل في استيراد منتديات vb3 الفرعية كفئات، لكنني عدلت الكود لاستيراد جميع المنتديات كفئات من المستوى الأعلى، ثم كتبت بعض أوامر postgres psql لإنشاء علاقات جديدة بين الفئات الأب والبنية (أو قمت بتكوينها يدويًا). كانت هناك “مفاجآت” أخرى (قصة ليوم آخر)، لكن تلك الشياطين جعلت الأمر ممتعًا وتحديًا، لكنه لم يكن مستحيلًا.

ثالثًا، أود أن أشكر سام وكود lithium.rb الخاص به، حيث استخدمت روتين import_likes الخاص به كأساس لروتين vb3 thank you الخاص بي (من إضافة قديمة، وليست جزءًا من vB OOTB) إلى discourse likes. قمت بإجراء تغييرات طفيفة على روتين import_likes وبعد بضع ساعات من تصحيح الأخطاء، نجحت في جعله يعمل.

كما ترون من هذا المنشور لعام 2018، الذي تم هجرته اليوم:

اعتقدت أنه من المهم للمستخدمين أن يتم نقل vb thanks الخاصة بهم إلى discourse likes، وهنا الكود الذي استخدمته لنقل likes لـ أرواح منتديات vb3 المفقودة الأخرى:

أولاً، قمت بإنشاء جدول vb3 mysql يسمى user_actions وكتبت سكريبت PHP لملئه من جدول legacy vb post_thanks، مثل هذا (غير مصقول، لكنه يعمل):

ubuntu:/var/www/includes/cron# cat thanks_to_discourse.php
 <?php

/**************************************************
+-----------+------------+-----------+-----------+
| thanker   | unixtime   | id        | thanked   |
+-----------+------------+-----------+-----------+
|         1 | 1584149592 | 303045211 | 302093876 |
| 302153369 | 1584136706 | 303045214 | 302116191 |
| 302108573 | 1584128526 | 303045211 | 302093876 |
| 302153369 | 1584126659 | 303042175 | 302116191 |
| 302153369 | 1584126400 | 303045174 | 302116191 |
| 302153369 | 1584117711 | 303045184 |         1 |
|     37898 | 1584108187 | 303045175 |         1 |
| 302181242 | 1584106664 | 303045201 | 302122047 |
| 302181242 | 1584104642 | 303045074 | 302052697 |
| 302025710 | 1584103722 | 303045184 |         1 |
+-----------+------------+-----------+-----------+
 **************************************************/

$query = 'SELECT p.threadid AS threadid,t.userid AS thanker,t.date AS unixtime,t.postid AS postid,p.userid AS thanked from post_thanks as t LEFT JOIN post p ON p.postid = t.postid';

$allthanks = $vbulletin->db->query_read($query);

/****************************************
mysql> describe user_actions;
+-----------------+------------------+------+-----+---------+----------------+
| Field           | Type             | Null | Key | Default | Extra          |
+-----------------+------------------+------+-----+---------+----------------+
| id              | int(11)          | NO   | PRI | NULL    | auto_increment |
| action_type     | int(11) unsigned | NO   |     | NULL    |                |
| user_id         | int(11) unsigned | NO   |     | NULL    |                |
| target_topic_id | int(11) unsigned | YES  |     | NULL    |                |
| target_post_id  | varchar(16)      | YES  |     | NULL    |                |
| target_user_id  | int(11) unsigned | YES  |     | NULL    |                |
| acting_user_id  | int(11) unsigned | YES  |     | NULL    |                |
| created_at      | timestamp        | YES  |     | NULL    |                |
| updated_at      | timestamp        | YES  |     | NULL    |                |
+-----------------+------------------+------+-----+---------+----------------+
9 rows in set (0.00 sec)
INSERT INTO table_name (column1, column2, column3, ...)
VALUES (value1, value2, value3, ...);
 *************************************/

$action_code = 2; // discourse like action == 2
$target_user_id = "";
while ($action = $vbulletin->db->fetch_array($allthanks)) {

    #code to deal with how discourse identifies the first post in a topic in the post_custom_fields table
    $query = 'SELECT firstpostid FROM thread WHERE threadid =' . $action['threadid'] . ' LIMIT 1';
    $threadinfo = $vbulletin->db->query_first($query);
    if ($threadinfo['firstpostid'] == $action['postid']) {
        $action['postid'] = 'thread-' . $action['threadid'];
    }

    $update = 'INSERT INTO user_actions (action_type, user_id, target_topic_id, target_post_id, target_user_id,acting_user_id, created_at, updated_at)' .
        ' VALUES (' . $action_code . ',' .
        '"' . $action['thanked'] . '",' .
        '"' . $action['threadid'] . '",' .
        '"' . $action['postid'] . '",' .
        '"' . $target_user_id . '",' .
        '"' . $action['thanker'] . '",' .
        'FROM_UNIXTIME(' . $action['unixtime'] . '),' .
        'FROM_UNIXTIME(' . $action['unixtime'] . '))';
    $doit = $vbulletin->db->query_write($update);
}

إذًا، يتم تضمين جدول mysql user_actions هذا مع نسخة الهجرات.

إليك روتين import_likes المعدل الذي استخدمته لإكمال الهجرة على جانب discourse:

def import_likes
    puts "\nimporting likes..."

    # created mysql user_actions table in vb3 using PHP script and included that table with migration dump
    sql = "select acting_user_id as user_id, target_post_id as post_id, created_at from user_actions"
    results = mysql_query(sql)
    puts "length() method form : #{results.count}\n\n"

    puts "skip loading unique id map"
    existing_map = {}
    PostCustomField.where(name: 'import_id').pluck(:post_id, :value).each do |post_id, import_id|
      existing_map[import_id] = post_id
      #puts "postcustomfield existing_map post_id: #{post_id} import_id #{import_id}\n"
    end

    puts "loading data into temp table"

    #manually created the temp like_data table so I could check the table after session ends
    #DB.exec("create temp table like_data(user_id integer, post_id integer, created_at timestamp without time zone)")
    
    puts "like_data temp table created"
    PostAction.transaction do
      results.each do |result|

        result["user_id"] = user_id_from_imported_user_id(result["user_id"].to_s)
        result["post_id"] = existing_map[result["post_id"].to_s]

        next unless result["user_id"] && result["post_id"]

        puts "insert like table user_id: #{result["user_id"]} post_id #{result["post_id"]}\n"

        DB.exec("INSERT INTO like_data VALUES (:user_id,:post_id,:created_at)",
          user_id: result["user_id"],
          post_id: result["post_id"],
          created_at: result["created_at"]
        )

      end
    end


    puts "creating missing post actions"
    DB.exec <<~SQL

    INSERT INTO post_actions (post_id, user_id, post_action_type_id, created_at, updated_at)
             SELECT l.post_id, l.user_id, 2, l.created_at, l.created_at FROM like_data l
             LEFT JOIN post_actions a ON a.post_id = l.post_id AND l.user_id = a.user_id AND a.post_action_type_id = 2
             WHERE a.id IS NULL
    SQL

    puts "creating missing user actions"
    DB.exec <<~SQL
    INSERT INTO user_actions (user_id, action_type, target_topic_id, target_post_id, acting_user_id, created_at, updated_at)
             SELECT pa.user_id, 1, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at
             FROM post_actions pa
             JOIN posts p ON p.id = pa.post_id
             LEFT JOIN user_actions ua ON action_type = 1 AND ua.target_post_id = pa.post_id AND ua.user_id = pa.user_id

             WHERE ua.id IS NULL AND pa.post_action_type_id = 2
    SQL

    # reverse action
    DB.exec <<~SQL
    INSERT INTO user_actions (user_id, action_type, target_topic_id, target_post_id, acting_user_id, created_at, updated_at)
             SELECT p.user_id, 2, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at
             FROM post_actions pa
             JOIN posts p ON p.id = pa.post_id
             LEFT JOIN user_actions ua ON action_type = 2 AND ua.target_post_id = pa.post_id AND
                ua.acting_user_id = pa.user_id AND ua.user_id = p.user_id

             WHERE ua.id IS NULL AND pa.post_action_type_id = 2
    SQL
    puts "updating like counts on posts"

    DB.exec <<~SQL
        UPDATE posts SET like_count = coalesce(cnt,0)
                  FROM (
        SELECT post_id, count(*) cnt
        FROM post_actions
        WHERE post_action_type_id = 2 AND deleted_at IS NULL
        GROUP BY post_id
    ) x
    WHERE posts.like_count <> x.cnt AND posts.id = x.post_id

    SQL

    puts "updating like counts on topics"

    DB.exec <<-SQL
      UPDATE topics SET like_count = coalesce(cnt,0)
      FROM (
        SELECT topic_id, sum(like_count) cnt
        FROM posts
        WHERE deleted_at IS NULL
        GROUP BY topic_id
      ) x
      WHERE topics.like_count <> x.cnt AND topics.id = x.topic_id

    SQL
    end
end

آمل أن تساعد هذه المنشور الآخرين الذين قد يهاجرون من vB3 إلى discourse، حيث استخدم العديد من الأشخاص إضافة “شكر” legacy vb3 لسنوات عديدة.

أيضًا، قضيت الكثير من الوقت في قاعدة بيانات discourse وداخل العديد من سكريبتات ruby الخاصة بـ discourse، لذا كانت هذه الهجرة صعبة (ليست لمن لديهم قلوب ضعيفة بالتأكيد)، لكنها بالتأكيد قابلة للتنفيذ.

في الواقع، استمتعت بفعل ذلك. كانت هذه أول مرة لي في ruby؛ لذا كان من الممتع البدء في تعلم ruby، وكشخص اعتاد على mysql منذ فترة طويلة، كان جزء postgres روتينيًا في الغالب.

سنبدأ العمل قريبًا… شكرًا لكم يا فريق Discourse!!!

أحسنت، وشكرًا لمشاركتك!!

شكرًا لك @codinghorror

لقد اكتشفت للتو مشكلة صغيرة سأعمل على حلها. فعادة import_likes لا تنقل الإعجابات في منشور “موضوع البداية” الأول (فقط في الردود).

سأضطر إلى التحرك بجدية وإيجاد حل، ثم سأشارك هنا الروتين المحدث لـ import_likes.

يبدو أنني كنت متسرعًا قليلاً في النشر، إذ لا يزال هناك “شيطان” صغير آخر يجب القضاء عليه قبل أن أستطيع إعلان النصر.

تحديث:

أضفت هذا الكود إلى سكريبت PHP في vB3 لتعويض الفرق في طريقة تعامل vB3 وDiscourse مع المنشور الأول في الموضوع/الخيط:

    <?php
    # كود vB3 للتعامل مع الطريقة التي يحدّ بها Discourse المنشور الأول في الموضوع في جدول post_custom_fields
    $query = 'SELECT firstpostid FROM thread WHERE threadid =' . $action['threadid'] . ' LIMIT 1';
    $threadinfo = $vbulletin->db->query_first($query);
    if ($threadinfo['firstpostid'] == $action['postid']) {
        $action['postid'] = 'thread-' . $action['threadid'];
    }

الآن، يعمل كما هو مقصود، ويتم نقل “شكرات مُقدَّمة” في vB3 إلى Discourse، بما في ذلك المنشور الأول في الموضوع؛ وذلك عن طريق تعديل جدول الهجرة من vB3 ليتوافق مع جدول Discourse post_custom_fields.

أجري الاختبار النهائي الآن، لكن يبدو أن الأمر سيعمل بناءً على الاختبارات الأولية.

كما قمت بتحديث الكود في منشوري الأصلي بإضافة مقتطف PHP المذكور أعلاه.

قرأت أنك لا تقوم بنقل كلمات المرور لأن تجزئاتها مختلفة. قد ترغب في إلقاء نظرة على إضافة نقل كلمات المرور الخاصة بي (هنا). من المرجح أنها تدعم بالفعل تجزئات VB3، وإذا لم تكن كذلك، فأنا منفتح على إضافة ذلك.

شكرًا لك يا مايكل،

لن نقوم بنقل كلمات المرور، ونفضل أن يقوم المستخدمون بتغيير كلمات المرور الخاصة بهم (والتحول إلى كلمات مرور أقوى وأطول).

شكرًا لك على عرض المساعدة، على أي حال! نقدر ذلك كثيرًا.

لدينا مشكلة صغيرة واحدة ربما يمكنك مساعدتنا فيها.

لسبب ما، لا يتم تحديث العدد الإجمالي للإعجابات التي حصل عليها المستخدمون بشكل صحيح في عروض ملفاتهم الشخصية، على الرغم من أن كل شيء آخر يبدو على ما يرام. لقد حاولت حفر المشكلة؛ لكن حفر لم يعطني النتائج التي أحتاجها.

هل تعرف طريقة جيدة لتحديث عدادات “الإعجابات” هذه في الملفات الشخصية يدويًا؟

ما هي مهمة rake التي استخدمتها؟ وما الخطأ؟ هل الأرقام جميعها بعيدة كل البعد، أم فقط لبعض المستخدمين، أم…؟

مرحباً.

للبداية، جربت أمر rake الوحيد الذي بدا لي منطقياً (في ذلك الوقت، بما أن المشكلة تتعلق بـ user_actions و post_actions)؛ لكنه لم يُثمر عن أي نتيجة (في الواقع، عند التفكير في الأمر لاحقاً، كان يجب أن أقرأ أمر rake هذا أولاً لأرى بالضبط ما يفعله، LOL):

rake user_actions:rebuild    

حتى الآن، بصراحة، لم أتمكن من تحديد ما الذي يخطئ في سكريبت الهجرة import_likes تحديداً. لو كنت أعرف بالضبط ما الذي يخطئ وأين، لكنت استطعت إصلاحه (كثيراً ما أقضي ساعات أو أياماً في العثور على مشكلة، وبعد العثور عليها، يستغرق الإصلاح دقيقة واحدة… جوهر البرمجة LOL). لقد استعدت من نسخة احتياطية عدة مرات وأعدت تشغيل روتين import_likes بعدة طرق مختلفة (إضافة جمل puts إضافية، وما إلى ذلك) ومراجعة الجداول. كانت النتائج غير حاسمة. وهذا يتفاقم بسبب مستواي المبتدئ في Ruby و Discourse بشكل عام؛ إذ لم أكن بعد طليقاً في الواجهة الخلفية. (ومع ذلك، أتحسن في استخدام begin... rescue ... end … هاها، الذي أصبح أحد أدوات تصحيح الأخطاء الجديدة لدي كـ مبتدئ في Ruby).

ما لاحظته هو أن جدول like_data المؤقت في قاعدة بيانات PostgreSQL (على جانب Discourse) سليم ويحتوي على جميع البيانات بالتنسيق الصحيح، وتطابق الإدخالات جدول الهجرة في MySQL وموقع vB.

يبدو أن العملية تتعطل عند إنشاء user_actions وربما حتى post_actions لأن اختباراتي أظهرت أن البيانات لا تنتقل بشكل صحيح إلى هذه الجداول الرئيسية.

يرجى ملاحظة أن عملية هجرة vB3 الأساسية سليمة. هذه المشكلة تتعلق بإضافة “شكراً لك” قديمة من vB3 أقوم بتحويلها إلى إعجابات في Discourse. خطوتي التالية، عندما أجد وقتاً لاحقاً اليوم كما آمل، هي الجلوس ومحاولة معرفة بالضبط أين تتعطل العملية عند نقل البيانات من جدول like_data السليم إلى user_actions و post_actions.

استنتاجي الأولي، بناءً على اختبارات الأمس، هو أن أوامر rake في Ruby لن تساعد لأن التعطل يحدث في عملية تحديث جدول user_actions أو post_actions؛ لذا أحتاج إلى تحديد مكان التعطل بالضبط ولماذا. قد يكون الأمر بسيطاً مثل عدم تطابق أنواع البيانات في قاعدة البيانات، أو وجود “شيطان” صغير آخر في الكود.

موقع Discourse الخاص بنا غير نشط بعد، لذا ليست هناك حالة طارئة، وكما هو الحال مع العديد من الأشخاص هنا وفي أماكن أخرى، أصبحت المهام الروتينية مثل التعامل مع الهجرة أو البنوك أكثر من مجرد إزعاج بسيط، وأحتاج إلى الذهاب إلى المدينة اليوم لبعض المهام غير المتعلقة بـ Discourse. سأفضل أن أكون مستكشفاً لـ Discourse وأبحث عن شياطين الكود.

تم تشغيل سكريبت الهجرة مرة أخرى (بدون أي تغييرات في السكريبت) بما في ذلك السكريبت المخصص لتحويل thanks من vBulletin 3 إلى likes في Discourse (import_likes)، ويبدو أن الأمر على ما يرام الآن.

سأحاول مزامنة قاعدة بيانات vB الأحدث (التصدير الكبير “D”) لأرى ما سيحدث!

أنا سعيد جدًا بـ Discourse… شكرًا مرة أخرى على هذا المنتدى الحديث الذي يُعد تحفة فنية. ما زلت أقوم بالاختبارات، لكنني سأطلقه قريبًا جدًا.

5.6 ألف إعجاب مُقدَّم… هذا قدر كبير من الحب :heart:

بعد الحصول على جميع vb thanks لنقلها إلى discourse likes, إليك مثال على منشور موضوع تم ترحيله من عام 2000، أي منذ عشرين عامًا، يُظهر الإعجابات المرحّلة في الموضوع:

… وأنا بدأت أتطلع إلى تعلّم كتابة إضافات رائعة… :slight_smile:

سعيد جدًا بـ Discourse والفريق… وخصوصًا begin .... rescue ...puts "look at me".... end في لغة Ruby… ما هي منقذ للحياة!

الحقيقة هي… الكلاب القديمة يمكنها تعلّم حيل جديدة إذا ألقيت عليها بعض العظام :slight_smile:

شكرًا لكم يا فريق Discourse! قبل 20 عامًا، أنشأنا منتدى لمستخدمي Unix و Linux على vB2، وقبل حوالي 15 عامًا، قمنا بـ “ترقية” إلى vB3. اليوم يمثل تغييرًا كبيرًا لمستخدمي Unix و Linux في جميع أنحاء العالم، ولنا نحن أيضًا، حيث أصبح الترحيل إلى Discourse الآن نشطًا.

شكرًا جزيلاً لكم على إنشاء هذا البرنامج وجعله متاحًا للجميع كمشروع مفتوح المصدر. كرمكم يُقدّر كثيرًا. ومن وجهة نظري (وأنا متأكد من أن عددًا لا يحصى من الآخرين يشاركونني هذا الرأي)، فإن Discourse هو على الإطلاق أفضل برنامج منتديات على كوكب الأرض في عام 2020.

أما بالنسبة للآخرين الذين هم مستخدمو vB3 القدامى ويرغبون في الترحيل إلى Discourse، فإن هذه العملية ليست “سهلة” كما قد تبدو. ما لم تكن ملمًا بالبرمجة ومريحًا في التكامل المباشر مع قواعد البيانات الخاصة بك عبر سطر الأوامر، فإنني أنصحك بالنظر في خدمات بعض المهنيين الموهوبين والنشطين هنا في meta.discourse.org.

شكرًا لكم مرة أخرى، يا فريق Discourse!

ملاحظة: سأضيف بعض الدروس المستفادة المتنوعة في هذا المنشور على الموقع الجديد: