Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script para reconstruir histórico de TabCoins #652

Closed
filipedeschamps opened this issue Aug 17, 2022 · 3 comments
Closed

Script para reconstruir histórico de TabCoins #652

filipedeschamps opened this issue Aug 17, 2022 · 3 comments
Labels
back Envolve modificações no backend

Comments

@filipedeschamps
Copy link
Owner

Contexto e Execução

Nesse caso, sugiro fortemente centralizar a discussão na publicação abaixo

@filipedeschamps filipedeschamps added the back Envolve modificações no backend label Aug 17, 2022
@filipedeschamps
Copy link
Owner Author

Rodei hoje de manhã em Homologação, deu tudo certo demorou 60 segundos: https://tabnews-8b589clch-tabnews.vercel.app/filipedeschamps/rodei-o-script-de-preencher-o-historico-de-tabcoins-em-homologacao

Mas fui rodar agora em produção, e dado que a minha máquina aqui está longe da instância do banco em São Paulo, demorou muito mais (18 minutos) e nesse meio tempo, as TabCoins se mexeram e o script fez rollback 😂

    {
      name: 'UnprocessableEntityError',
      message: 'Number of added Content TabCoins does not match',
      action: 'Os dados enviados estão corretos, porém não foi possível realizar esta operação.',
      status_code: 422,
      error_id: 'b56c7726-907e-42b9-8be5-09f9bcbb8de3',
      request_id: '54cc3af3-7a91-4099-a82d-a09f05406f19',
      context: {
        total_contents_before_tabcoins: 2815,
        total_contents_tabcoins_before: '5271',
        total_contents_tabcoins_after: '8088',
        added_tab_coins: 2817
      }
    }

O legal é que o rollback funcionou certinho pelo que pude avaliar aqui 👍

@filipedeschamps
Copy link
Owner Author

Script executado em produção, durou 18 minutos:

Contents: 4537
Contents before TabCoins existence: 2815
---
Content TabCoins (before): 5286
Content TabCoins (after): 8101 (+2815)
---
User TabCoins (before): 11995
User TabCoins (after): 40145 (+28150)

Isso injetou muita TabCoin no sistema, muita mesmo, mas tudo bem, pois era o que tinhamos combinado :)

@filipedeschamps
Copy link
Owner Author

E este foi o exato script que rodei:

import nextConnect from 'next-connect';
import controller from 'models/controller.js';
import database from 'infra/database.js';
import event from 'models/event.js';
import balance from 'models/balance.js';
import { UnprocessableEntityError } from 'errors/index.js';

export default nextConnect({
  attachParams: true,
  onNoMatch: controller.onNoMatchHandler,
  onError: controller.onErrorHandler,
})
  .use(controller.injectRequestMetadata)
  .get(runTabCoinsScript);

async function runTabCoinsScript(request, response) {
  // Para quem não conhece, o conceito de "dry run" é tentar rodar um código,
  // mas que ao final não faz nada de verdade. No caso desse script, é
  // iniciada uma transação no banco de dados, que de fato executa tudo,
  // mas ao final é feito o rollback, o que cancela tudo que foi feito.
  const DRY_RUN = false;
  const transaction = await database.transaction();

  try {
    await transaction.query('BEGIN');

    // 0) CRIAR EVENTO
    //    Nós primeiro criamos um evento, pois este evento será usado
    //    como lastro em todas as entradas de balanço que iremos criar
    //    mais para frente. A única coisa chata de "marretar" um evento
    //    assim fora do fluxo normal é que eu precisei hardcode o meu
    //    usuário e ip (localhost).
    const currentEvent = await event.create(
      {
        type: 'system:update:tabcoins',
        originatorUserId: '07ea33ea-78bd-4578-bad2-1cf5323cab07', // filipedeschamps
        originatorIp: '127.0.0.1',
        metadata: {
          description: `Backfill all TabCoins from contents created before '2022-07-03 16:48:12.457+00'.`,
        },
      },
      {
        transaction: transaction,
      }
    );

    // 1) PEGAR NÚMERO TOTAL DE CONTEÚDOS PUBLICADOS
    //    Para referência, eu pego o número total de Conteúdos publicados
    //    e isso vai dar um comparativo bom com a próxima query.
    const totalContents = await transaction.query(`
      SELECT
        count(*)
      FROM
        contents
      WHERE
        status = 'published'
    ;`);

    console.log(`Contents: ${totalContents.rows[0].count}`);

    // 2) PEGAR CONTEÚDOS CRIADOS ANTES DO DEPLOY SOBRE AS TABCOINS
    //    Estes Conteúdos hoje podem ter saldo de TabCoins, mas não
    //    importa, pois precisamos mais para frente creditar as TabCoins
    //    originais do Conteúdo e do Usuário criador do Conteúdo. Então
    //    dessa query, a gente extrai o ID do Conteúdo e o ID do Autor
    //    e não importa se o Conteúdo é "root" ou "child", precisa apenas
    //    estar com status "published" e ter sido criado antes do deploy.
    const totalContentsBeforeTabcoins = await transaction.query(`
      SELECT
       id as content_id,
       owner_id as user_id
      FROM
        contents
      WHERE
        contents.status = 'published'
        AND contents.created_at < '2022-07-03 16:48:12+00'
    ;`);

    console.log(`Contents before TabCoins existence: ${totalContentsBeforeTabcoins.rows.length}`);
    console.log(`---`);

    // 3) PEGAR TOTAL DE TABCOINS POSITIVAS DOS CONTEÚDOS (ANTES)
    //    Apenas como referência, eu somo o total de TabCoins positivas
    //    de todos os Conteúdos do sistema só para entender o que vai
    //    acontecer com esse valor depois de rodarmos o script.
    const totalContentsTabcoinsBefore = await transaction.query(`
      SELECT
        sum(amount)
      FROM
        balance_operations
      WHERE
        balance_type = 'content:tabcoin'
        and amount > 0
    ;`);

    console.log(`Content TabCoins (before): ${totalContentsTabcoinsBefore.rows[0].sum}`);

    // 4) CRIAR TABCOINS PARA OS CONTEÚDOS
    //    Basicamente é criado uma entrada na tabela de balanço para cada
    //    Conteúdo que foi criado antes do deploy e está na variável "totalContentsBeforeTabcoins"
    //    acima. Fora isso, usamos o evento também criado acima para registrar o lastro
    //    dessa movimentação.
    for await (const contentWithoutTabcoins of totalContentsBeforeTabcoins.rows) {
      const contentId = contentWithoutTabcoins.content_id;

      await balance.create(
        {
          balanceType: 'content:tabcoin',
          recipientId: contentId,
          amount: 1, // Valor padrão que o Conteúdo ganha ao ser criado
          originatorType: 'event',
          originatorId: currentEvent.id,
        },
        {
          transaction: transaction,
        }
      );
    }

    // 4) PEGAR TOTAL DE TABCOINS POSITIVAS DOS CONTEÚDOS (DEPOIS)
    //    Então agora nós conseguiremos comparar o que aconteceu com
    //    o número total de TabCoins no sistema, que deveria ter a mais
    //    o mesmo número de Conteúdo sem TabCoins, dado que é creditado
    //    1 TabCoin por criação de conteúdo.
    const totalContentsTabcoinsAfter = await transaction.query(`
      SELECT
        sum(amount)
      FROM
        balance_operations
      WHERE
        balance_type = 'content:tabcoin'
        and amount > 0
    ;`);

    console.log(
      `Content TabCoins (after): ${totalContentsTabcoinsAfter.rows[0].sum} (+${
        totalContentsTabcoinsAfter.rows[0].sum - totalContentsTabcoinsBefore.rows[0].sum
      })`
    );
    console.log(`---`);

    // 5) PEGAR TOTAL DE TABCOINS POSITIVAS DOS USUÁRIOS (ANTES)
    //    Mesma lógica usada no caso das TabCoins dos Conteúdos, mas agora
    //    para os Usuários.
    const totalUsersTabcoinsBefore = await transaction.query(`
      SELECT
        sum(amount)
      FROM
        balance_operations
      WHERE
        balance_type = 'user:tabcoin'
        and amount > 0
    ;`);

    console.log(`User TabCoins (before): ${totalUsersTabcoinsBefore.rows[0].sum}`);

    // 6) CRIAR TABCOINS PARA OS USUÁRIOS
    //    Praticamente a mesma lógica que antes, mas agora bonificando os Usuários.
    for await (const contentWithoutTabcoins of totalContentsBeforeTabcoins.rows) {
      const userId = contentWithoutTabcoins.user_id;

      await balance.create(
        {
          balanceType: 'user:tabcoin',
          recipientId: userId,
          amount: 10, // O valor bônus para quem se arriscou a criar um Conteúdo no passado
          originatorType: 'event',
          originatorId: currentEvent.id,
        },
        {
          transaction: transaction,
        }
      );
    }

    // 7) PEGAR TOTAL DE TABCOINS POSITIVAS DOS USUÁRIOS (DEPOIS)
    //    Isso deveria gerar para os usuários 10x mais TabCoins que foram
    //    geradas para os Conteúdos.
    const totalUsersTabcoinsAfter = await transaction.query(`
      SELECT
        sum(amount)
      FROM
        balance_operations
      WHERE
        balance_type = 'user:tabcoin'
        and amount > 0
    ;`);

    console.log(
      `User TabCoins (after): ${totalUsersTabcoinsAfter.rows[0].sum} (+${
        totalUsersTabcoinsAfter.rows[0].sum - totalUsersTabcoinsBefore.rows[0].sum
      })`
    );

    if (
      totalContentsBeforeTabcoins.rows.length !==
      totalContentsTabcoinsAfter.rows[0].sum - totalContentsTabcoinsBefore.rows[0].sum
    ) {
      throw new UnprocessableEntityError({
        message: 'Number of added Content TabCoins does not match',
        context: {
          totalContentsBeforeTabcoins: totalContentsBeforeTabcoins.rows.length,
          totalContentsTabcoinsBefore: totalContentsTabcoinsBefore.rows[0].sum,
          totalContentsTabcoinsAfter: totalContentsTabcoinsAfter.rows[0].sum,
          addedTabCoins: totalContentsTabcoinsAfter.rows[0].sum - totalContentsTabcoinsBefore.rows[0].sum,
        },
      });
    } else if (
      totalContentsBeforeTabcoins.rows.length !==
      (totalUsersTabcoinsAfter.rows[0].sum - totalUsersTabcoinsBefore.rows[0].sum) / 10
    ) {
      throw new UnprocessableEntityError({
        message: 'Number of added User TabCoins does not match',
        context: {
          totalContentsBeforeTabcoins: totalContentsBeforeTabcoins.rows.length,
          totalUsersTabcoinsBefore: totalUsersTabcoinsBefore.rows[0].sum,
          totalUsersTabcoinsAfter: totalUsersTabcoinsAfter.rows[0].sum,
        },
      });
    } else if (DRY_RUN) {
      throw new UnprocessableEntityError({
        message: 'Dry run',
      });
    } else {
      await transaction.query('COMMIT');
      await transaction.release();
      return response.status(200).json({});
    }
  } catch (error) {
    await transaction.query('ROLLBACK');
    await transaction.release();
    throw error;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
back Envolve modificações no backend
Projects
None yet
Development

No branches or pull requests

1 participant