Migración completada de vB3 a Discourse incluyendo vB3 Legacy Gracias a Likes

Acabo de completar, en su mayor parte (aún en fase de pruebas, pero las «partes difíciles» están básicamente resueltas y la migración funciona bien), la migración de un foro vb3 de 15 años, que originalmente (¡créanlo o no!) era un foro vb2 del año 2000.

Este foro lleva en funcionamiento alrededor de 20 años (con código PHP, tablas, complementos, etc., personalizados de una magnitud inimaginable), y en mi opinión, Discourse es el único software de foros «digno» de un proyecto de migración, el cual comencé hace aproximadamente una semana, pasando de la viabilidad a la conclusión (básicamente).

En primer lugar, quiero agradecer a todo el equipo de Discourse por tal obra maestra moderna de foro. Discourse es verdaderamente notable, en mi opinión (y estoy seguro de que muchos otros opinan igual).

En segundo lugar, me gustaría agradecer al equipo que codificó el script original de migración vbulletin.rb. Este script estaba aproximadamente un 95% correcto. Lo modifiqué para que funcionara adecuadamente con vb3. Por ejemplo, vb3 no tiene una tabla filedata como vb4 (relacionada con los archivos adjuntos); por lo tanto, modifiqué vbulletin.rb para que funcionara. Además, hubo problemas al importar foros vb3 secundarios como categorías, pero modifiqué el código para importar todos los foros como categorías de nivel superior y luego escribí algo de postgres psql para crear nuevas relaciones de categorías padre-hijo (o las configuré manualmente). Hubo otros «detalles» (una historia para otro día), pero esos duendes hicieron que fuera divertido y desafiante, aunque no insuperable.

En tercer lugar, me gustaría agradecer a Sam y su código lithium.rb, donde utilicé su rutina import_likes como base para mi vb3 thank you (de un complemento heredado, no parte de vB OOTB) a discourse likes. Realicé cambios menores en la rutina import_likes y, después de unas pocas horas de depuración, logré que funcionara.

Como pueden ver en esta publicación de 2018, migrada hoy:

Pensé que era importante para los usuarios que sus vb thanks se migraran a discourse likes, y por eso aquí está el código que utilicé para migrar los likes para otras almas perdidas de foros vb3:

En primer lugar, creé una tabla mysql vb3 llamada user_actions y escribí un script PHP para llenarla desde la tabla heredada post_thanks de vb, así (no pulido, pero funcional):

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

Así, esta tabla mysql user_actions se incluye en el volcado de la migración.

Aquí está la rutina import_likes modificada que utilicé para completar la migración en el lado de 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

Espero que esta publicación ayude a otros que puedan migrar de vB3 a Discourse, ya que muchas personas utilizaron el complemento heredado de «gracias» de vb3 durante muchos años.

Además, pasé mucho tiempo en la base de datos de Discourse y dentro de muchos scripts ruby de Discourse, por lo que esta migración fue complicada (no para los de corazón débil sin duda), pero ciertamente factible.

De hecho, disfruté haciéndolo. Fue la primera vez que usé ruby; así que fue divertido empezar a aprender ruby y, como persona mysql de larga data, la parte de postgres fue mayormente rutinaria.

Pronto estaremos en línea… ¡Gracias al equipo de Discourse!!!

¡Muy bien y gracias por compartir!

Gracias @codinghorror

Acabo de encontrar un pequeño gremlin, con el que trabajaré. La rutina import_likes no está transfiriendo los “me gusta” en la primera publicación del “iniciador del tema” (solo en las respuestas).

Tendré que ponerme las mangas y resolver esto, y publicar de nuevo la rutina import_likes actualizada.

Parece que fue un poco prematuramente que publiqué, ya que queda un pequeño duende más que vencer antes de poder declarar la victoria.

Actualización:

Añadí este código al script de PHP en vB3 para compensar la diferencia entre cómo vB3 y Discourse manejan la primera publicación en un tema/hilo:

    <?php
    # Código de vB3 para manejar cómo Discourse identifica la primera publicación en un tema en la tabla 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'];
    }

Ahora funciona como se esperaba y los “agradecimientos dados” en vB3 se migran a Discourse, incluyendo la primera publicación del tema; ajustando la tabla de migración de vB3 para que coincida con la tabla post_custom_fields de Discourse.

Estoy realizando la prueba final, pero parece que funcionará basándome en las pruebas preliminares.

Además, actualicé el código en mi publicación original añadiendo el fragmento de PHP anterior.

Leí que no están migrando las contraseñas porque los hashes son diferentes. Quizás quieras echar un vistazo a mi plugin de migración de contraseñas (aquí). Probablemente ya admita hashes de VB3 y, si no es así, estoy abierto a agregar esa funcionalidad.

Gracias, Michael,

No vamos a migrar las contraseñas; preferimos que los usuarios cambien sus contraseñas (y las cambien por otras más seguras y largas).

¡Gracias de todos modos por la oferta de ayuda! Lo agradecemos mucho.

Tenemos un pequeño problema con el que quizás puedas ayudarnos.

Por alguna razón, el número total de “me gusta” en los usuarios no se actualiza correctamente en sus perfiles, aunque todo lo demás parezca estar bien. He intentado rastrear el problema, pero mi búsqueda no me está dando los resultados que necesito.

¿Conoces alguna buena manera de actualizar manualmente estos contadores de “me gusta” en los perfiles?

¿Qué tarea de rake utilizaste? ¿Y qué está mal? ¿Todos los números están muy equivocados, o solo para algunos usuarios, o…?

Hola.

Para empezar, intenté el único comando rake que tenía sentido para mí (en ese momento, ya que el problema está relacionado con user_actions y post_actions); pero no dio ningún resultado (de hecho, en retrospectiva, debería haber leído ese rake primero para ver exactamente qué hacía, LOL):

rake user_actions:rebuild

Hasta ahora, francamente, no he determinado qué falla exactamente en el script de migración import_likes. Si supiera exactamente qué salió mal y dónde, podría haberlo solucionado (a menudo paso horas o días buscando un problema que, una vez encontrado, se soluciona en un minuto… la esencia de la programación, LOL). He restaurado desde una instantánea varias veces y he vuelto a ejecutar la rutina import_likes de varias maneras (agregando declaraciones puts adicionales, etc.) y revisando las tablas. Los resultados han sido inconcluyentes. Esto se complica por mi novatada en ruby y en discourse en general; aún no soy fluido en el backend. (Sin embargo, estoy mejorando con begin... rescue ... end… jaja, que se ha convertido en una de mis nuevas herramientas de depuración como rubynube).

Lo que he observado es que la tabla temporal like_data de postgres (en el lado de discourse) está bien y contiene todos los datos en el formato correcto, y las entradas coinciden tanto con la tabla de migración de mysql como con el sitio vB.

El proceso parece fallar al crear user_actions y quizás incluso post_actions, porque mis pruebas muestran que los datos no se transfieren correctamente a esas tablas clave.

Por favor, ten en cuenta que la migración principal de vB3 está bien. Este problema está relacionado con un plugin heredado de vB3 de “gracias” que estoy convirtiendo a los “me gusta” de Discourse. Mi siguiente paso, cuando tenga tiempo más tarde hoy, espero, será sentarme e intentar ver exactamente dónde falla el proceso al transferir desde la tabla like_data, que está bien, a user_actions y post_actions.

Mi conclusión preliminar, basada en las pruebas de ayer, es que ruby raking no ayudará porque la falla está en el proceso de actualización de las tablas user_actions o post_actions; por lo tanto, necesito determinar exactamente dónde está la falla y por qué. Podría ser tan simple como una incompatibilidad de tipos de base de datos o algún otro pequeño gremlin en el código.

Nuestro sitio de Discourse aún no está en línea, así que no es una emergencia y, como muchas personas aquí y en otros lugares, realizar tareas como lidiar con trámites de inmigración o bancos se ha vuelto más que un poco problemático, por decir lo menos, y necesito ir a la ciudad hoy nuevamente para algunas tareas ajenas a Discourse. Preferiría estar explorando discourse y buscando gremlins del código.

Ejecuté el script de migración nuevamente (sin cambios en el script) incluyendo el script personalizado de vb3 thanks a likes de Discourse (import_likes) y parece que todo está bien ahora.

¡Voy a intentar sincronizar la gran “D” con el volcado más reciente de la base de datos de vB y ver qué pasa!

Muy satisfecho con Discourse… Gracias de nuevo por este foro moderno, una verdadera “obra de arte”. Todavía estoy probando, pero lo pondré en línea en breve.

5.6k likes dados… Eso es mucho amor :heart:

Después de recibir todos los vb gracias para transferirlos a discourse likes, aquí tienes un ejemplo de la publicación del tema migrado desde el año 2000, hace veinte años, mostrando los “likes” migrados en el tema:

… y ya empiezo a esperar con ilusión aprender a escribir complementos geniales… :slight_smile:

Muy contento con Discourse y el equipo… y especialmente con begin .... rescue ...puts "mírame".... end en Ruby… ¡qué salvavidas!

Es cierto… los perros viejos pueden aprender trucos nuevos si les tiras algunos huesos :slight_smile:

¡Gracias, EQUIPO Discourse! Hace 20 años, creamos un foro para usuarios de Unix y Linux en vB2, y hace unos 15 años, “actualizamos” a vB3. Hoy representa un gran cambio para los usuarios de Unix y Linux de todo el mundo y para nosotros, ya que la migración a Discourse ya está en vivo.

Muchas gracias por crear este software y ponerlo a disposición de todos como un proyecto de código abierto. Su generosidad es muy apreciada. En mi opinión (y estoy seguro de que la de innumerables otros), Discourse es, con mucho, el mejor software de foros del planeta en 2020.

En cuanto a otros usuarios de vB3 heredado que deseen migrar a Discourse, esta migración no es tan “fácil” como podría parecer. A menos que tenga conocimientos sólidos de programación y se sienta cómodo integrándose directamente con sus bases de datos desde la línea de comandos, le recomiendo que considere los servicios de algunos de los profesionales talentosos y activos aquí en meta.discourse.org.

¡Gracias nuevamente, Equipo Discourse!

Nota: Agregaré algunas lecciones aprendidas en este post en el nuevo sitio: