From b1f447c5c8f07efcf165eea570e20d13232249da Mon Sep 17 00:00:00 2001 From: Bryan Stitt Date: Wed, 26 Jul 2023 07:33:00 -0700 Subject: [PATCH] add mass_grant_credits helper script and other prep for premium (#196) * add mass_grant_credits helper script * include error in the rate limit log * allow granting 0 credits * migration to change default user_tier to premium --- migration/src/lib.rs | 2 + ...230726_072845_default_premium_user_tier.rs | 90 +++++++++++++++++++ web3_proxy/src/bin/web3_proxy_cli.rs | 10 +++ web3_proxy/src/rpcs/request.rs | 4 +- .../src/sub_commands/mass_grant_credits.rs | 81 +++++++++++++++++ web3_proxy/src/sub_commands/mod.rs | 2 + 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 migration/src/m20230726_072845_default_premium_user_tier.rs create mode 100644 web3_proxy/src/sub_commands/mass_grant_credits.rs diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 80c4fdff..33090544 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -39,6 +39,7 @@ mod m20230708_151756_rpc_accounting_free_usage_credits; mod m20230708_152131_referral_track_one_time_bonus_bonus; mod m20230713_144446_stripe_default_date_created; mod m20230713_210511_deposit_add_date_created; +mod m20230726_072845_default_premium_user_tier; pub struct Migrator; @@ -85,6 +86,7 @@ impl MigratorTrait for Migrator { Box::new(m20230708_152131_referral_track_one_time_bonus_bonus::Migration), Box::new(m20230713_144446_stripe_default_date_created::Migration), Box::new(m20230713_210511_deposit_add_date_created::Migration), + Box::new(m20230726_072845_default_premium_user_tier::Migration), ] } } diff --git a/migration/src/m20230726_072845_default_premium_user_tier.rs b/migration/src/m20230726_072845_default_premium_user_tier.rs new file mode 100644 index 00000000..38a452e9 --- /dev/null +++ b/migration/src/m20230726_072845_default_premium_user_tier.rs @@ -0,0 +1,90 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + /// change default to premium tier + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db_conn = manager.get_connection(); + let db_backend = manager.get_database_backend(); + + let select_premium_id = Query::select() + .column(UserTier::Id) + .column(UserTier::Title) + .from(UserTier::Table) + .and_having(Expr::col(UserTier::Title).eq("Premium")) + .to_owned(); + + let premium_id: u64 = db_conn + .query_one(db_backend.build(&select_premium_id)) + .await? + .expect("Premium tier should exist") + .try_get("", &UserTier::Id.to_string())?; + + manager + .alter_table( + Table::alter() + .table(User::Table) + .modify_column( + ColumnDef::new(User::UserTierId) + .big_unsigned() + .default(premium_id) + .not_null(), + ) + .to_owned(), + ) + .await?; + + Ok(()) + } + + /// change default to free tier + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db_conn = manager.get_connection(); + let db_backend = manager.get_database_backend(); + + let select_free_id = Query::select() + .column(UserTier::Id) + .column(UserTier::Title) + .from(UserTier::Table) + .and_having(Expr::col(UserTier::Title).eq("Free")) + .to_owned(); + + let free_id: u64 = db_conn + .query_one(db_backend.build(&select_free_id)) + .await? + .expect("Free tier should exist") + .try_get("", &UserTier::Id.to_string())?; + + manager + .alter_table( + Table::alter() + .table(User::Table) + .modify_column( + ColumnDef::new(User::UserTierId) + .big_unsigned() + .default(free_id) + .not_null(), + ) + .to_owned(), + ) + .await?; + + Ok(()) + } +} + +#[derive(Iden)] +enum User { + Table, + UserTierId, +} + +#[derive(Iden)] +enum UserTier { + Table, + Id, + Title, +} diff --git a/web3_proxy/src/bin/web3_proxy_cli.rs b/web3_proxy/src/bin/web3_proxy_cli.rs index 3f814afa..775c2175 100644 --- a/web3_proxy/src/bin/web3_proxy_cli.rs +++ b/web3_proxy/src/bin/web3_proxy_cli.rs @@ -68,6 +68,7 @@ enum SubCommand { CreateKey(sub_commands::CreateKeySubCommand), CreateUser(sub_commands::CreateUserSubCommand), DropMigrationLock(sub_commands::DropMigrationLockSubCommand), + MassGrantCredits(sub_commands::MassGrantCredits), MigrateStatsToV2(sub_commands::MigrateStatsToV2SubCommand), Pagerduty(sub_commands::PagerdutySubCommand), PopularityContest(sub_commands::PopularityContestSubCommand), @@ -392,6 +393,15 @@ fn main() -> anyhow::Result<()> { x.main(&db_conn).await } + SubCommand::MassGrantCredits(x) => { + let db_url = cli_config + .db_url + .expect("'--config' (with a db) or '--db-url' is required to run mass_grant_credits"); + + let db_conn = get_migrated_db(db_url, 1, 1).await?; + + x.main(&db_conn).await + } SubCommand::MigrateStatsToV2(x) => { let top_config = top_config.expect("--config is required to run the migration from stats-mysql to stats-influx"); diff --git a/web3_proxy/src/rpcs/request.rs b/web3_proxy/src/rpcs/request.rs index e1cacbfe..505d81ba 100644 --- a/web3_proxy/src/rpcs/request.rs +++ b/web3_proxy/src/rpcs/request.rs @@ -298,9 +298,9 @@ impl OpenRequestHandle { let retry_at = Instant::now() + Duration::from_secs(1); if self.rpc.backup { - debug!(?retry_at, "rate limited on {}!", self.rpc); + debug!(?retry_at, ?err, "rate limited on {}!", self.rpc); } else { - warn!(?retry_at, "rate limited on {}!", self.rpc); + warn!(?retry_at, ?err, "rate limited on {}!", self.rpc); } hard_limit_until.send_replace(retry_at); diff --git a/web3_proxy/src/sub_commands/mass_grant_credits.rs b/web3_proxy/src/sub_commands/mass_grant_credits.rs new file mode 100644 index 00000000..b53845f8 --- /dev/null +++ b/web3_proxy/src/sub_commands/mass_grant_credits.rs @@ -0,0 +1,81 @@ +// TODO: a lot of this is copy/paste of the admin frontend endpoint for granting credits. +// that's easier than refactoring right now. +// it could be cleaned up, but this is a script that runs once so isn't worth spending tons of time on. + +use anyhow::Context; +use argh::FromArgs; +use entities::{admin_increase_balance_receipt, user, user_tier}; +use futures::TryStreamExt; +use migration::sea_orm::{ + self, ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, + PaginatorTrait, QueryFilter, QueryOrder, TransactionTrait, +}; +use rust_decimal::Decimal; +use tracing::info; + +#[derive(FromArgs, PartialEq, Debug)] +/// Grant credits to all the users in a tier (and change their tier to premium). +#[argh(subcommand, name = "mass_grant_credits")] +pub struct MassGrantCredits { + #[argh(positional)] + /// the name of the user tier whose users will be upgraded to premium + tier_to_upgrade: String, + + #[argh(positional)] + /// how many credits to give. + credits: Decimal, +} + +impl MassGrantCredits { + pub async fn main(self, db_conn: &DatabaseConnection) -> anyhow::Result<()> { + let old_user_tier = user_tier::Entity::find() + .filter(user_tier::Column::Title.like(&self.tier_to_upgrade)) + .one(db_conn) + .await? + .context("no user tier found with that name")?; + + let new_user_tier = user_tier::Entity::find() + .filter(user_tier::Column::Title.like("Premium")) + .one(db_conn) + .await? + .context("no Premium user tier found")?; + + let mut user_stream = user::Entity::find() + .filter(user::Column::UserTierId.eq(old_user_tier.id)) + .order_by_asc(user::Column::Id) + .paginate(db_conn, 50) + .into_stream(); + + while let Some(users_to_upgrade) = user_stream.try_next().await? { + let txn = db_conn.begin().await?; + + for user_to_upgrade in users_to_upgrade { + if self.credits > 0.into() { + let increase_balance_receipt = admin_increase_balance_receipt::ActiveModel { + amount: sea_orm::Set(self.credits), + // TODO: allow customizing the admin id + admin_id: sea_orm::Set(1), + deposit_to_user_id: sea_orm::Set(user_to_upgrade.id), + note: sea_orm::Set("mass grant credits".into()), + ..Default::default() + }; + increase_balance_receipt.save(&txn).await?; + } + + let mut user_to_upgrade = user_to_upgrade.into_active_model(); + + user_to_upgrade.user_tier_id = sea_orm::Set(new_user_tier.id); + + user_to_upgrade.save(&txn).await?; + } + + txn.commit().await?; + + // we can't invalidate balance caches because they are in another process. they do have short ttls though + } + + info!("success"); + + Ok(()) + } +} diff --git a/web3_proxy/src/sub_commands/mod.rs b/web3_proxy/src/sub_commands/mod.rs index 5ff80938..e5c2b3bc 100644 --- a/web3_proxy/src/sub_commands/mod.rs +++ b/web3_proxy/src/sub_commands/mod.rs @@ -8,6 +8,7 @@ mod count_users; mod create_key; mod create_user; mod drop_migration_lock; +mod mass_grant_credits; mod migrate_stats_to_v2; mod pagerduty; mod popularity_contest; @@ -29,6 +30,7 @@ pub use self::count_users::CountUsersSubCommand; pub use self::create_key::CreateKeySubCommand; pub use self::create_user::CreateUserSubCommand; pub use self::drop_migration_lock::DropMigrationLockSubCommand; +pub use self::mass_grant_credits::MassGrantCredits; pub use self::migrate_stats_to_v2::MigrateStatsToV2SubCommand; pub use self::pagerduty::PagerdutySubCommand; pub use self::popularity_contest::PopularityContestSubCommand;