تم الانتهاء منه، إلى حد كبير (لا يزال في بيئة الاختبار، لكن الأجزاء “الصعبة” منتهية تقريبًا، وتعمل عملية الهجرة بسلاسة)، من هجرة منتدى 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!!!



