Migration vB3 vers Discourse terminée incluant Legacy vB3, merci aux Likes

Just completed, for the most part (still in test bed, but the “hard parts” are basically done and the migration works fine), migrating a 15 year old vb3 forum, which was originally (believe-it-or-not) a vb2 forum from 2000.

This forum has been around for around 20 years (with unimaginable custom PHP code, tables, plugins, etc), and Discourse is the only forum software, in my view, “worthy” of a migration project, which I started around a week ago, going from feasibility to completion (basically).

First of all, I want to thank the entire Discourse team for such a remarkable “work-of-art” modern forum. Discourse is truly remarkable, in my view (and I am sure many others).

Second, I would like to thank the team who coded the original vbulletin.rb migration script. This script was about 95% OK. and I modified that script to get it to work properly for vb3. For example, vb3 does not have a filedata table like vb4 (related to attachments); so I modified vbulletin.rb to get it to work. Also, there were issues importing child vb3 forums as categories, but I modified the code to import all forums as top level categories and then wrote some postgres psql to create new parent-child categories relationships (or manually configured). There were other “gotchas” (a story for another day) but those goblins made it fun and challenging, but not insurmountable.

Third, I would like to thank Sam and his code lithium.rb where I used his import_likes routine as the basis for my vb3 thank you (from a legacy plugin, not part of vB OOTB) to discourse likes. I made minor changes to the import_likes routine and after a few hours of debugging, got it to work.

As you can see from this 2018 post, migrated today:

I thought it was important for users that their vb thanks migrated to discourse likes and so here is the code I used to migrate the likes for other lost vb3 forum souls:

First of all I created a vb3 mysql table called user_actions and wrote a PHP script to fill it from the legacy vb post_thanks table, like this (not polished, but working):

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);
}

So, this mysql user_actions table is included with the migration dump.

Here is the modified import_likes routine I used to complete the migration on the discourse side:

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

I hope this post helps others who might migrate from vB3 to discourse, since many people used the legacy vb3 “thank you” plugin for many years.

Also, I spent a lot of time in the discourse database and inside a lot of discourse ruby scripts, so this migration was tricky (not for the faint of heart to be sure), but certainly doable.

Actually, I enjoyed doing it. This was the first time in ruby for me; so it was fun to start to learn ruby and as a long time mysql person, the postgres part was mostly routine.

We will be going live soon… Thank You Discourse Team!!!

Bravo et merci d’avoir partagé !!

Merci @codinghorror

Je viens de repérer un petit gremlin que je vais régler. La routine import_likes ne transfère pas les « likes » sur le premier message (« topic starter ») (seulement les réponses).

Je vais devoir retrousser mes manches, trouver la solution et publier ici la routine import_likes mise à jour.

Il semble que j’aie été un peu trop pressé en publiant, car il reste un petit gobelin à abattre avant de pouvoir déclarer victoire.

Mise à jour :

J’ai ajouté ce code au script PHP de vB3 pour compenser la différence entre la façon dont vB3 et Discourse gèrent le premier message d’un sujet/thread :

    <?php
    # Code vB3 pour gérer la façon dont Discourse identifie le premier message d'un sujet dans la table 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'];
    }

Maintenant, cela fonctionne comme prévu et les « remerciements donnés » dans vB3 sont migrés vers Discourse, y compris le premier message du sujet ; en ajustant la table de migration de vB3 pour qu’elle corresponde à la table post_custom_fields de Discourse.

Je réalise le test final maintenant, mais d’après les tests préliminaires, cela semble fonctionner.

J’ai également mis à jour le code dans mon message initial en ajoutant le fragment PHP ci-dessus.

J’ai lu que vous ne migriez pas les mots de passe car les hachages sont différents. Vous devriez jeter un coup d’œil à mon plugin de migration de mots de passe (ici). Il supportera probablement déjà les hachages VB3, et si ce n’est pas le cas, je suis ouvert à l’ajout de cette fonctionnalité.

Merci Michael,

Nous ne allons pas migrer les mots de passe et préférons que les utilisateurs changent leurs mots de passe (et optent pour des mots de passe plus forts et plus longs).

Merci tout de même pour votre proposition d’aide ! C’est très apprécié.

Nous avons cependant un petit problème sur lequel vous pourriez peut-être nous aider.

Pour une raison inconnue, le nombre total de « likes » reçus par les utilisateurs n’est pas correctement mis à jour dans leurs profils, même si tout le reste semble fonctionner normalement. J’ai essayé de résoudre le problème par moi-même, mais mes tentatives ne m’ont pas donné les résultats escomptés.

Connaissez-vous une bonne méthode pour mettre à jour manuellement ces compteurs de « likes » sur les profils ?

Quelle tâche rake avez-vous utilisée ? Et quel est le problème ? Tous les chiffres sont-ils complètement faux, ou seulement pour certains utilisateurs, ou… ?

Bonjour.

Pour commencer, j’ai essayé la seule tâche Rake qui me semblait logique (à l’époque, puisque le problème est lié à user_actions et post_actions), mais cela n’a rien donné (en fait, en y repensant, j’aurais dû lire cette tâche rake pour voir exactement ce qu’elle fait, LOL) :

rake user_actions:rebuild    

Pour l’instant, franchement, je n’ai pas encore déterminé ce qui ne va pas dans le script de migration import_likes. Si je savais exactement ce qui cloche et où, j’aurais pu le corriger (je passe souvent des heures ou des jours à trouver un problème qui, une fois trouvé, ne prend qu’une minute à corriger… l’essence de la programmation, LOL). J’ai restauré à partir d’une capture plusieurs fois et réexécuté la routine import_likes de différentes manières (ajout de déclarations puts supplémentaires, etc.) et examiné les tables. Les résultats sont restés inconclusifs. Cela est aggravé par mon amateurisme en ruby et en discourse en général ; je ne suis pas encore fluide dans le backend. (Cependant, je m’améliore avec begin... rescue ... end… haha, ce qui est devenu l’un de mes nouveaux outils de débogage en tant que débutant ruby.)

Ce que j’ai observé, c’est que la table temporaire postgres like_data (côté discourse) est correcte et contient toutes les données au bon format, et les entrées correspondent à la fois à la table de migration mysql et au site vB.

Le processus semble échouer lors de la création des tables user_actions et peut-être même post_actions, car mes tests montrent que les données ne sont pas correctement transférées vers ces tables clés.

Veuillez noter que la migration principale vB3 est correcte. Ce problème est lié à un ancien plugin vB3 « merci » que je convertis en likes Discourse. Ma prochaine étape, lorsque j’aurai du temps plus tard aujourd’hui, j’espère, sera de m’asseoir et de voir exactement où le processus échoue lors du transfert de la table like_data (qui est correcte) vers user_actions et post_actions.

Ma conclusion préliminaire, basée sur les tests d’hier, est que le rake ruby ne sera pas d’une grande aide car la rupture se situe dans le processus de mise à jour des tables user_actions ou post_actions ; je dois donc déterminer exactement où se trouve la rupture et pourquoi. Cela pourrait être aussi simple qu’un conflit de type de base de données ou un autre petit gremlin dans le code.

Notre site Discourse n’est pas encore en ligne, donc ce n’est pas une urgence et, comme beaucoup d’ici et ailleurs, des tâches comme gérer les démarches administratives ou bancaires sont devenues plus qu'un peu ennuyeuses, pour ne pas dire plus, et je dois retourner en ville aujourd’hui pour des tâches non liées à Discourse. Je préférerais explorer Discourse et chercher des gremlins de code.

J’ai à nouveau exécuté le script de migration (sans aucune modification du script), y compris le script personnalisé de conversion des balises thanks de vb3 en likes de Discourse (import_likes), et tout semble être OK maintenant.

Je vais essayer de synchroniser le gros « D » avec le dernier dump de la base de données vB et voir ce qui se passe !

Je suis très satisfait de Discourse… Merci encore pour ce forum moderne, véritable « œuvre d’art ». Je continue à tester, mais nous passerons en production bientôt.

5,6 k j’aime donnés… C’est beaucoup d’amour :heart:

Après avoir récupéré tous les vb thanks pour les transférer en discourse likes, voici un exemple de sujet migré datant de l’an 2000, il y a vingt ans, montrant les likes migrés dans le sujet :

… et je commence déjà à avoir hâte d’apprendre à écrire des plugins sympas… :slight_smile:

Très satisfait de Discourse et de l’équipe… et surtout de begin .... rescue ...puts "look at me".... end en Ruby… quel sauveur !

C’est vrai… les vieux chiens peuvent apprendre de nouveaux tours si on leur lance quelques os :slight_smile:

Merci à l’équipe Discourse ! Il y a 20 ans, nous avons lancé un forum pour les utilisateurs Unix et Linux sur vB2, et il y a environ 15 ans, nous sommes « passés » à vB3. Aujourd’hui marque un grand changement pour les utilisateurs Unix et Linux du monde entier et pour nous, car la migration vers Discourse est désormais active.

Merci beaucoup d’avoir créé ce logiciel et de le rendre disponible à tous en tant que projet open source. Votre générosité est très appréciée. À mon avis (et je suis sûr que beaucoup d’autres partagent ce point de vue), Discourse est de loin le meilleur logiciel de forum sur la planète en 2020.

Pour les autres utilisateurs de vB3 legacy qui souhaitent migrer vers Discourse, cette migration n’est pas aussi « simple » que je l’ai présentée. À moins d’être bien versé en programmation et à l’aise avec l’intégration directe à vos bases de données en ligne de commande, je vous recommande de faire appel aux services de certains des professionnels talentueux et actifs ici sur meta.discourse.org.

Encore merci, équipe Discourse !

Note : J’ajouterai quelques leçons apprises dans ce post sur le nouveau site :