ほぼ完了しました(まだテスト環境にありますが、「難しい部分」は基本的に終了し、移行も正常に動作しています)。2000 年発の vb2 フォーラム(信じられないかもしれませんが)が元となった、15 年前の vb3 フォーラムの移行を完了させました。
このフォーラムは約 20 年間存続しており(想像を絶するカスタム PHP コード、テーブル、プラグインなどがあります)、私の見解では Discourse だけが「移行プロジェクト」に値するフォーラムソフトウェアです。私は約 1 週間前に着手し、実現可能性の検討から完了(ほぼ)に至りました。
第一に、このような傑作ともいえる現代的なフォーラムを提供してくださったDiscourse チーム全体に感謝申し上げます。Discourse は、私の見解では(そして多くの他の方々もそうお感じだと確信しています)、本当に素晴らしいものです。
第二に、元々の vbulletin.rb 移行スクリプトをコーディングしたチームに感謝します。このスクリプトは約 95% 問題ありませんでした。私はそのスクリプトを修正して vb3 で正しく動作するようにしました。例えば、vb3 は vb4 のような添付ファイルに関連する filedata テーブルを持っていません。そのため、vb3 で動作するように vbulletin.rb を修正しました。また、子 vb3 フォーラムをカテゴリとしてインポートする際に問題がありましたが、すべてのフォーラムをトップレベルのカテゴリとしてインポートするようにコードを修正し、その後 postgres psql を実行して新しい親子カテゴリ関係を構築しました(または手動で設定しました)。他にもいくつかの「落とし穴」がありましたが(これはまた別の日の話題に)、それらの小悪魔たちが楽しさと挑戦をもたらしましたが、乗り越えられないものではありませんでした。
第三に、私の vb3 のお礼(レガシープラグインからのもので、vB の標準機能には含まれていません)を Discourse のいいね に変換する際、Sam のコード lithium.rb にある import_likes ルーチンを基盤として使用したことにSam に感謝します。私は import_likes ルーチンをわずかに変更し、数時間のデバッグの末、動作するようにしました。
以下は、今日移行された 2018 年の投稿のスクリーンショットです。
ユーザーにとって、vb の「お礼」が Discourse の「いいね」に移行することは重要だと考えました。そのため、他の「失われた vb3 フォーラムの魂」の「いいね」を移行するために使用したコードを以下に示します。
まず、vb3 mysql テーブル を user_actions という名前で作成し、レガシー vb の post_thanks テーブルから PHP スクリプトを使用してデータを埋めました。以下がそのコードです(洗練されてはいませんが、動作します)。
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 テーブルは移行ダンプに含まれています。
以下は、Discourse 側で移行を完了させるために使用した修正済みの import_likes ルーチンです。
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 の「お礼」プラグインを使用してきた多くの人々が、vB3 から Discourse へ移行する際に役立つことを願っています。
また、私は Discourse データベース や多数の Discourse Ruby スクリプト の内部で多くの時間を費やしました。そのため、この移行は(心臓の弱い方には確かに)難易度が高いものでしたが、決して不可能ではありませんでした。
実際、私はそれを楽しむことができました。これは私にとって Ruby での初めての経験でした。Ruby を学び始めるのは楽しかったですし、長年の MySQL 担当者 として、Postgres 部分 は主にルーチンワークでした。
まもなく公開予定です… Discourse チームに感謝します!!!



