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
TabCoins
por 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 TabCoins
como 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 🤝