Только что завершил, по большей части (ещё в тестовой среде, но «самые сложные части» в основном сделаны, и миграция работает исправно), миграцию 15-летнего форума vb3, который изначально (поверьте или нет) был форумом 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) в discourse likes. Я внес незначительные изменения в процедуру import_likes и после нескольких часов отладки добился её работы.
Как видно из этого поста 2018 года, мигрированного сегодня:
Я считал важным, чтобы пользователи сохранили свои vb thanks в виде discourse likes, поэтому вот код, который я использовал для миграции likes для других потерянных душ форумов vb3:
Сначала я создал таблицу vb3 mysql под названием user_actions и написал PHP-скрипт для её заполнения из устаревшей таблицы 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 == 2
$target_user_id = "";
while ($action = $vbulletin->db->fetch_array($allthanks)) {
# код для обработки того, как 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'];
}
$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..."
# создана таблица mysql user_actions в vb3 с помощью PHP-скрипта и включена в дамп миграции
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"
# вручную создана временная таблица like_data, чтобы можно было проверить таблицу после окончания сессии
#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
# обратное действие
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, поскольку многие люди годами использовали устаревший плагин «спасибо» vb3.
Также я потратил много времени в базе данных discourse и внутри множества скриптов ruby для discourse, поэтому эта миграция была непростой (определённо не для слабого сердца), но вполне выполнимой.
На самом деле, мне это понравилось. Это был мой первый опыт работы с ruby, поэтому было весело начать изучать ruby, а как давний специалист по mysql, часть с postgres была в основном рутинной.
Мы скоро выйдем в онлайн… Спасибо команде Discourse!!!



