From a7119f6b5bdb74e6ded35dce0533c6554745a084 Mon Sep 17 00:00:00 2001 From: Bryan Stitt Date: Mon, 10 Jul 2023 14:03:05 -0700 Subject: [PATCH] split balance queries up (#177) * wip * tests pass, referee_bonus changed to one_time_referee_bonus --------- Co-authored-by: yenicelik --- web3_proxy/src/balance.rs | 222 +++++++++++++++++------ web3_proxy/src/frontend/users/payment.rs | 10 +- web3_proxy/src/stats/mod.rs | 5 +- 3 files changed, 173 insertions(+), 64 deletions(-) diff --git a/web3_proxy/src/balance.rs b/web3_proxy/src/balance.rs index a1dcf702..fd08a67d 100644 --- a/web3_proxy/src/balance.rs +++ b/web3_proxy/src/balance.rs @@ -1,80 +1,194 @@ -use crate::errors::Web3ProxyResult; -use fstrings::{f, format_args_f}; -use migration::sea_orm; +use crate::errors::{Web3ProxyErrorContext, Web3ProxyResult}; +use crate::frontend::users::referral; +use entities::{ + admin_increase_balance_receipt, increase_on_chain_balance_receipt, referee, referrer, + rpc_accounting_v2, rpc_key, stripe_increase_balance_receipt, +}; use migration::sea_orm::prelude::Decimal; -use migration::sea_orm::{DbBackend, DbConn, FromQueryResult, Statement}; -use serde::{Deserialize, Serialize}; +use migration::sea_orm::DbConn; +use migration::sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect}; +use migration::{Func, SimpleExpr}; +use serde::ser::SerializeStruct; +use serde::Serialize; -/// Implements the balance getter -#[derive(Clone, Debug, Default, Serialize, Deserialize, FromQueryResult)] +/// Implements the balance getter which combines data from several tables +#[derive(Clone, Debug, Default)] pub struct Balance { - pub user_id: u64, - pub total_spent_paid_credits: Decimal, + pub admin_deposits: Decimal, + pub chain_deposits: Decimal, + pub referal_bonus: Decimal, + pub one_time_referee_bonus: Decimal, + pub stripe_deposits: Decimal, pub total_spent: Decimal, - pub total_deposits: Decimal, + pub total_spent_paid_credits: Decimal, + pub user_id: u64, +} + +impl Serialize for Balance { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("balance", 12)?; + + state.serialize_field("admin_deposits", &self.admin_deposits)?; + state.serialize_field("chain_deposits", &self.chain_deposits)?; + state.serialize_field("referal_bonus", &self.referal_bonus)?; + state.serialize_field("one_time_referee_bonus", &self.one_time_referee_bonus)?; + state.serialize_field("stripe_deposits", &self.stripe_deposits)?; + state.serialize_field("total_spent", &self.total_spent)?; + state.serialize_field("total_spent_paid_credits", &self.total_spent_paid_credits)?; + state.serialize_field("user_id", &self.user_id)?; + + state.serialize_field("active_premium", &self.active_premium())?; + state.serialize_field("was_ever_premium", &self.was_ever_premium())?; + state.serialize_field("balance", &self.remaining())?; + state.serialize_field("total_deposits", &self.total_deposits())?; + + state.end() + } } impl Balance { pub fn active_premium(&self) -> bool { - self.was_ever_premium() && self.total_deposits > self.total_spent_paid_credits + self.was_ever_premium() && self.total_deposits() > self.total_spent_paid_credits } pub fn was_ever_premium(&self) -> bool { - self.user_id != 0 && self.total_deposits >= Decimal::from(10) + self.user_id != 0 && self.total_deposits() >= Decimal::from(10) } pub fn remaining(&self) -> Decimal { - self.total_deposits - self.total_spent_paid_credits + self.total_deposits() - self.total_spent_paid_credits } + pub fn total_deposits(&self) -> Decimal { + self.admin_deposits + + self.chain_deposits + + self.referal_bonus + + self.one_time_referee_bonus + + self.stripe_deposits + } + + /// TODO: do this with a single db query pub async fn try_from_db(db_conn: &DbConn, user_id: u64) -> Web3ProxyResult> { // Return early if user_id == 0 if user_id == 0 { return Ok(None); } - // Injecting the variable directly, should be fine because Rust is typesafe, especially with primitives - let raw_sql = f!(r#" - SELECT - user.id AS user_id, - COALESCE(SUM(admin_receipt.amount), 0) + COALESCE(SUM(chain_receipt.amount), 0) + COALESCE(SUM(stripe_receipt.amount), 0) + COALESCE(SUM(referee.one_time_bonus_applied_for_referee), 0) + COALESCE(referrer_bonus.total_bonus, 0) AS total_deposits, - COALESCE(SUM(accounting.sum_credits_used), 0) AS total_spent_paid_credits, - COALESCE(SUM(accounting.sum_incl_free_credits_used), 0) AS total_spent - FROM - user - LEFT JOIN - admin_increase_balance_receipt AS admin_receipt ON user.id = admin_receipt.deposit_to_user_id - LEFT JOIN - increase_on_chain_balance_receipt AS chain_receipt ON user.id = chain_receipt.deposit_to_user_id - LEFT JOIN - stripe_increase_balance_receipt AS stripe_receipt ON user.id = stripe_receipt.deposit_to_user_id - LEFT JOIN - referee ON user.id = referee.user_id - LEFT JOIN - (SELECT referrer.user_id, SUM(referee.credits_applied_for_referrer) AS total_bonus - FROM referrer - JOIN referee ON referrer.id = referee.used_referral_code - GROUP BY referrer.user_id) AS referrer_bonus ON user.id = referrer_bonus.user_id - LEFT JOIN - rpc_key ON user.id = rpc_key.user_id - LEFT JOIN - rpc_accounting_v2 AS accounting ON rpc_key.id = accounting.rpc_key_id - LEFT JOIN - user_tier ON user.user_tier_id = user_tier.id - WHERE - user.id = {user_id}; - "#); + let (admin_deposits,) = admin_increase_balance_receipt::Entity::find() + .select_only() + .column_as( + SimpleExpr::from(Func::coalesce([ + admin_increase_balance_receipt::Column::Amount.sum(), + 0.into(), + ])), + "admin_deposits", + ) + .filter(admin_increase_balance_receipt::Column::DepositToUserId.eq(user_id)) + .into_tuple() + .one(db_conn) + .await + .web3_context("fetching admin deposits")? + .unwrap_or_default(); - let balance: Balance = match Self::find_by_statement(Statement::from_string( - DbBackend::MySql, - raw_sql, - // [.into()], - )) - .one(db_conn) - .await? - { - None => return Ok(None), - Some(x) => x, + let (chain_deposits,) = increase_on_chain_balance_receipt::Entity::find() + .select_only() + .column_as( + SimpleExpr::from(Func::coalesce([ + increase_on_chain_balance_receipt::Column::Amount.sum(), + 0.into(), + ])), + "chain_deposits", + ) + .filter(increase_on_chain_balance_receipt::Column::DepositToUserId.eq(user_id)) + .into_tuple() + .one(db_conn) + .await + .web3_context("fetching chain deposits")? + .unwrap_or_default(); + + let (stripe_deposits,) = stripe_increase_balance_receipt::Entity::find() + .select_only() + .column_as( + SimpleExpr::from(Func::coalesce([ + stripe_increase_balance_receipt::Column::Amount.sum(), + 0.into(), + ])), + "stripe_deposits", + ) + .filter(stripe_increase_balance_receipt::Column::DepositToUserId.eq(user_id)) + .into_tuple() + .one(db_conn) + .await + .web3_context("fetching stripe deposits")? + .unwrap_or_default(); + + let (total_spent_paid_credits, total_spent) = rpc_accounting_v2::Entity::find() + .select_only() + .column_as( + SimpleExpr::from(Func::coalesce([ + rpc_accounting_v2::Column::SumCreditsUsed.sum(), + 0.into(), + ])), + "total_spent_paid_credits", + ) + .column_as( + SimpleExpr::from(Func::coalesce([ + rpc_accounting_v2::Column::SumInclFreeCreditsUsed.sum(), + 0.into(), + ])), + "total_spent", + ) + .left_join(rpc_key::Entity) + // .filter(rpc_key::Column::Id.eq(rpc_accounting_v2::Column::RpcKeyId)) // TODO: i think the left_join function handles this + .filter(rpc_key::Column::UserId.eq(user_id)) + .into_tuple() + .one(db_conn) + .await + .web3_context("fetching rpc_accounting_v2")? + .unwrap_or_default(); + + let one_time_referee_bonus = referee::Entity::find() + .select_only() + .column_as( + referee::Column::OneTimeBonusAppliedForReferee, + "one_time_bonus_applied_for_referee", + ) + .filter(referee::Column::UserId.eq(user_id)) + .into_tuple() + .one(db_conn) + .await + .web3_context("fetching referee")? + .unwrap_or_default(); + + let referal_bonus = referee::Entity::find() + .select_only() + .column_as( + SimpleExpr::from(Func::coalesce([ + referee::Column::CreditsAppliedForReferrer.sum(), + 0.into(), + ])), + "credits_applied_for_referrer", + ) + .left_join(referrer::Entity) + .filter(referrer::Column::UserId.eq(user_id)) + .into_tuple() + .one(db_conn) + .await + .web3_context("fetching referee and referral_codes")? + .unwrap_or_default(); + + let balance = Self { + admin_deposits, + chain_deposits, + referal_bonus, + one_time_referee_bonus, + stripe_deposits, + total_spent, + total_spent_paid_credits, + user_id, }; // Return None if there is no entry diff --git a/web3_proxy/src/frontend/users/payment.rs b/web3_proxy/src/frontend/users/payment.rs index f89472a1..0e3e7c77 100644 --- a/web3_proxy/src/frontend/users/payment.rs +++ b/web3_proxy/src/frontend/users/payment.rs @@ -53,15 +53,7 @@ pub async fn user_balance_get( Some(x) => x, }; - let response = json!({ - "total_deposits": user_balance.total_deposits, - "total_spent_paid_credits": user_balance.total_spent_paid_credits, - "total_spent": user_balance.total_spent, - "balance": user_balance.remaining(), - }); - - // TODO: Gotta create a new table for the spend part - Ok(Json(response).into_response()) + Ok(Json(user_balance).into_response()) } /// `GET /user/deposits/chain` -- Use a bearer token to get the user's balance and spend. diff --git a/web3_proxy/src/stats/mod.rs b/web3_proxy/src/stats/mod.rs index 4d82f23b..98f7aa0e 100644 --- a/web3_proxy/src/stats/mod.rs +++ b/web3_proxy/src/stats/mod.rs @@ -423,6 +423,7 @@ impl BufferedRpcQueryStats { // referral_entity.credits_applied_for_referrer * (Decimal::from(10) checks (atomically using this table only), whether the user has brought in >$100 to the referer // In this case, the sender receives $100 as a bonus / gift // Apply a 10$ bonus onto the user, if the user has spent 100$ + // TODO: i think we do want a LockType::Update on this match referee::Entity::find() .filter(referee::Column::UserId.eq(sender_user_id)) .find_also_related(referrer::Entity) @@ -465,8 +466,10 @@ impl BufferedRpcQueryStats { referral_entity.one_time_bonus_applied_for_referee = sea_orm::Set(bonus_for_user); + // Update the cache - user_balance.total_deposits += bonus_for_user; + // TODO: race condition here? + user_balance.one_time_referee_bonus += bonus_for_user; } let now = Utc::now();