Ho appena completato, per la maggior parte (ancora in fase di test, ma le parti difficili sono sostanzialmente finite e la migrazione funziona bene), la migrazione di un forum vb3 vecchio di 15 anni, che originariamente (credeteci o no) era un forum vb2 del 2000.
Questo forum esiste da circa 20 anni (con codice PHP personalizzato inimmaginabile, tabelle, plugin, ecc.), e Discourse è, a mio avviso, l’unico software per forum “degno” di un progetto di migrazione, che ho avviato circa una settimana fa, passando dalla fattibilità al completamento (sostanzialmente).
Prima di tutto, voglio ringraziare l’intero team di Discourse per un tale straordinario “capolavoro” di forum moderno. Discourse è davvero straordinario, a mio parere (e sono sicuro che lo sia anche per molti altri).
In secondo luogo, vorrei ringraziare il team che ha codificato lo script di migrazione originale vbulletin.rb. Questo script era circa al 95% corretto. L’ho modificato per farlo funzionare correttamente con vb3. Ad esempio, vb3 non ha una tabella filedata come vb4 (relativa agli allegati); quindi ho modificato vbulletin.rb per farlo funzionare. Inoltre, c’erano problemi nell’importare i forum figli vb3 come categorie, ma ho modificato il codice per importare tutti i forum come categorie di primo livello e poi ho scritto un po’ di postgres psql per creare nuove relazioni genitore-figlio tra le categorie (o configurate manualmente). C’erano altre “sorprese” (una storia per un’altra giornata), ma quei goblin hanno reso il tutto divertente e sfidante, ma non insormontabile.
In terzo luogo, vorrei ringraziare Sam e il suo codice lithium.rb, dove ho usato la sua routine import_likes come base per il mio ringraziamento vb3 (da un plugin legacy, non parte di vB OOTB) per trasformarlo in like di Discourse. Ho apportato piccole modifiche alla routine import_likes e, dopo alcune ore di debug, sono riuscito a farla funzionare.
Come potete vedere da questo post del 2018, migrato oggi:
Ho ritenuto importante che i ringraziamenti vb degli utenti venissero migrati nei like di Discourse, quindi ecco il codice che ho usato per migrare i like per altre anime perse di forum vb3:
Per prima cosa ho creato una tabella mysql vb3 chiamata user_actions e ho scritto uno script PHP per riempirla partendo dalla tabella legacy vb ```post_thanks``, in questo modo (non rifinito, ma funzionante):
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);
}
Quindi, questa tabella mysql user_actions è inclusa nel dump della migrazione.
Ecco la routine import_likes modificata che ho usato per completare la migrazione sul lato 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
Spero che questo post sia utile ad altri che potrebbero migrare da vB3 a Discourse, dato che molte persone hanno usato per anni il plugin legacy vb3 “ringraziamento”.
Inoltre, ho trascorso molto tempo nel database di Discourse e all’interno di molti script ruby di Discourse, quindi questa migrazione è stata delicata (non certo per i deboli di cuore), ma certamente fattibile.
In realtà, mi è piaciuto farlo. Questa è stata la prima volta per me in ruby; quindi è stato divertente iniziare a imparare ruby e, come persona abituata a mysql da molto tempo, la parte postgres è stata per lo più di routine.
Andremo online presto… Grazie al team di Discourse!!!



