RFC: Script que irá preencher histórico de TabCoins
Turma, esta publicação é importante então peço que leiam com carinho.
Nem dá para acreditar, mas faz apenas 45 dias que publicamos a feature de TabCoins aqui no TabNews... parece que sempre existiu a qualificação dos conteúdos e o algoritmo na Home, mas tudo isso é super recente.
Bom, o deploy e o anúncio desta feature aconteceu exatamente no mesmo momento e isso foi feito no dia 3 de Julho, onde na publicação de comemoração, escrevi o seguinte no Tópico bônus:
E todos os conteúdos que já foram publicados no passado, ninguém vai ganhar
TabCoinspor isso? Vão ganhar sim, e em dobro. Iremos fazer um script para historicamente creditar os autores de todas as publicações que foram feitas até hoje, onde ao invés de creditar5 TabCoins, serão creditadas10 TabCoinscomo agradecimento por terem participado do projeto num estágio tão inicial.
Então estamos aqui para preencher esse histórico 🤝 E apesar de que isto é um feito técnico e poderia ser discutido dentro de uma issue no GitHub, eu preferi trazer isso para cá, pois é aqui no TabNews que as pessoas criam os conteúdos... então nada mais justo do que deixar transpartente aqui o que será feito para todo mundo conseguir acompanhar, opinar ou descobrir alguma falha no script que iremos rodar.
O que foi feito
Então nesses últimos dias eu fiz um script que, em resumo, pega todos os conteúdos que foram criados antes do deploy da feature de TabCoins e que não creditaram TabCoins nem para o Conteúdo, nem para o Usuário, e cria esses créditos.
O script é simples, mas como não fiz nenhuma modelagem nem nada, inseri um monte de comentários para guiar qualquer pessoa que estiver lendo. Um cuidado extra é que comentários podem ser mentirosos e a única coisa real é o código, então não deixe de ler ele com total atenção.
O script está logo abaixo, mas gostaria de começar pelo resultado que ele gera.
Resultado
Basicamente o bloco logo abaixo é o log que o script gera utilizando os dados de Produção. O script rodou no modo Dry Run, ou seja, ele executa de verdade, mas não registra nada de verdade no sistema. Assim podemos rodar eles várias vezes para ir lapidando o que deve ser feito.
E nesse resultado começo informando a quantidade total de Conteúdos (independente se é root ou child) e lista também a quantidade desses Conteúdos que foram criados antes da existência da feature de TabCoins.
Depois somo a quantidade de TabCoins no sistema inteiro para os Conteúdos (antes e depois de rodar o script), e a mesma coisa para a quantidade de TabCoins dos Usuários.
Contents: 4435
Contents before TabCoins existence: 2816
---
Content TabCoins (before): 4886
Content TabCoins (after): 7702 (+2816)
---
User TabCoins (before): 11492
User TabCoins (after): 39652 (+28160)
Script
Este foi o script que rodei e qualquer furo ou dúvida não hesite em deixar um comentário:
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 = true;
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 (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;
}
}
Conclusão
Caso algo não ficou claro ou você ficou com alguma dúvida, por favor pergunte. Caso você tenha alguma sugestão, não pense duas vezes em sugerir. Vamos conversando para que a reconstrução desse histórico de TabCoins seja feita da melhor forma 🤝