Auto tier change (#184)
* lint * change user to premium on admin credits todo: change on other deposits * set tier more places * BadRequest instead of 500 * insert existing users too * add the premium file
This commit is contained in:
parent
b6cbf02ae7
commit
5d207fb2c6
@ -4,7 +4,7 @@ use crate::serialization;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "user")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
|
@ -62,6 +62,8 @@ impl Balance {
|
||||
}
|
||||
|
||||
pub fn was_ever_premium(&self) -> bool {
|
||||
// TODO: technically we should also check that user_tier.downgrade_tier_id.is_some()
|
||||
// but now we set premium automatically on deposit, so its fine for now
|
||||
self.user_id != 0 && self.total_deposits() >= Decimal::from(10)
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ use crate::frontend::authorization::{AuthorizationChecks, RpcSecretKey};
|
||||
use derive_more::From;
|
||||
use entities::rpc_key;
|
||||
use migration::sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
|
||||
use moka::future::{Cache, ConcurrentCacheExt};
|
||||
use moka::future::Cache;
|
||||
use std::fmt;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
|
@ -6,6 +6,7 @@ use crate::app::Web3ProxyApp;
|
||||
use crate::errors::Web3ProxyResponse;
|
||||
use crate::errors::{Web3ProxyError, Web3ProxyErrorContext};
|
||||
use crate::frontend::users::authentication::PostLogin;
|
||||
use crate::premium::{get_user_and_tier_from_address, grant_premium_tier};
|
||||
use crate::user_token::UserBearerToken;
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
@ -65,14 +66,16 @@ pub async fn admin_increase_balance(
|
||||
.await?
|
||||
.ok_or_else(|| Web3ProxyError::AccessDenied("not an admin".into()))?;
|
||||
|
||||
let user_entry: user::Model = user::Entity::find()
|
||||
.filter(user::Column::Address.eq(payload.user_address.as_bytes()))
|
||||
.one(&txn)
|
||||
let (user_entry, user_tier_entry) = get_user_and_tier_from_address(&payload.user_address, &txn)
|
||||
.await?
|
||||
.ok_or(Web3ProxyError::BadRequest(
|
||||
format!("No user found with {:?}", payload.user_address).into(),
|
||||
))?;
|
||||
|
||||
grant_premium_tier(&user_entry, user_tier_entry.as_ref(), &txn)
|
||||
.await
|
||||
.web3_context("granting premium tier")?;
|
||||
|
||||
let increase_balance_receipt = admin_increase_balance_receipt::ActiveModel {
|
||||
amount: sea_orm::Set(payload.amount),
|
||||
admin_id: sea_orm::Set(admin_entry.id),
|
||||
@ -81,6 +84,7 @@ pub async fn admin_increase_balance(
|
||||
..Default::default()
|
||||
};
|
||||
increase_balance_receipt.save(&txn).await?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
// Invalidate the user_balance_cache for this user:
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::app::Web3ProxyApp;
|
||||
use crate::balance::Balance;
|
||||
use crate::errors::{Web3ProxyError, Web3ProxyResponse, Web3ProxyResult};
|
||||
use crate::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResponse, Web3ProxyResult};
|
||||
use crate::frontend::authorization::{
|
||||
login_is_authorized, Authorization as Web3ProxyAuthorization,
|
||||
};
|
||||
use crate::frontend::users::authentication::register_new_user;
|
||||
use crate::premium::grant_premium_tier;
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
extract::Path,
|
||||
@ -16,7 +17,7 @@ use axum_client_ip::InsecureClientIp;
|
||||
use axum_macros::debug_handler;
|
||||
use entities::{
|
||||
admin_increase_balance_receipt, increase_on_chain_balance_receipt,
|
||||
stripe_increase_balance_receipt, user,
|
||||
stripe_increase_balance_receipt, user, user_tier,
|
||||
};
|
||||
use ethers::abi::AbiEncode;
|
||||
use ethers::types::{Address, Block, TransactionReceipt, TxHash, H256};
|
||||
@ -302,6 +303,8 @@ pub async fn user_balance_post(
|
||||
|
||||
// TODO: check bloom filters
|
||||
|
||||
let mut user_ids_need_premium = HashSet::new();
|
||||
|
||||
// the transaction might contain multiple relevant logs. collect them all
|
||||
let mut response_data = vec![];
|
||||
let mut user_ids_to_invalidate = HashSet::new();
|
||||
@ -405,9 +408,21 @@ pub async fn user_balance_post(
|
||||
debug!("deposit data: {:#?}", x);
|
||||
|
||||
response_data.push(x);
|
||||
|
||||
user_ids_need_premium.insert(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
for user in user_ids_need_premium.into_iter() {
|
||||
let user_tier = user_tier::Entity::find_by_id(user.user_tier_id)
|
||||
.one(&txn)
|
||||
.await?;
|
||||
|
||||
grant_premium_tier(&user, user_tier.as_ref(), &txn)
|
||||
.await
|
||||
.web3_context("granting premium tier")?;
|
||||
}
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
for user_id in user_ids_to_invalidate.into_iter() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::app::Web3ProxyApp;
|
||||
use crate::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResponse};
|
||||
use crate::premium::grant_premium_tier;
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
@ -7,7 +8,7 @@ use axum::{
|
||||
Extension, Json, TypedHeader,
|
||||
};
|
||||
use axum_macros::debug_handler;
|
||||
use entities::{stripe_increase_balance_receipt, user};
|
||||
use entities::{stripe_increase_balance_receipt, user, user_tier};
|
||||
use ethers::types::Address;
|
||||
use http::HeaderMap;
|
||||
use migration::sea_orm::prelude::Decimal;
|
||||
@ -163,6 +164,14 @@ pub async fn user_balance_stripe_post(
|
||||
Some(recipient) => {
|
||||
let _ = insert_receipt_model.save(&txn).await;
|
||||
|
||||
let user_tier = user_tier::Entity::find_by_id(recipient.user_tier_id)
|
||||
.one(&txn)
|
||||
.await?;
|
||||
|
||||
grant_premium_tier(&recipient, user_tier.as_ref(), &txn)
|
||||
.await
|
||||
.web3_context("granting premium tier")?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
// Finally invalidate the cache as well
|
||||
|
@ -14,6 +14,7 @@ pub mod frontend;
|
||||
pub mod http_params;
|
||||
pub mod jsonrpc;
|
||||
pub mod pagerduty;
|
||||
pub mod premium;
|
||||
pub mod prometheus;
|
||||
pub mod referral_code;
|
||||
pub mod relational_db;
|
||||
|
64
web3_proxy/src/premium.rs
Normal file
64
web3_proxy/src/premium.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::errors::Web3ProxyResult;
|
||||
use anyhow::Context;
|
||||
use entities::{user, user_tier};
|
||||
use ethers::prelude::Address;
|
||||
use migration::sea_orm::{
|
||||
self, ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel,
|
||||
QueryFilter,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
pub async fn get_user_and_tier_from_address(
|
||||
user_address: &Address,
|
||||
txn: &DatabaseTransaction,
|
||||
) -> Web3ProxyResult<Option<(user::Model, Option<user_tier::Model>)>> {
|
||||
let x = user::Entity::find()
|
||||
.filter(user::Column::Address.eq(user_address.as_bytes()))
|
||||
.find_also_related(user_tier::Entity)
|
||||
.one(txn)
|
||||
.await?;
|
||||
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
pub async fn get_user_and_tier_from_id(
|
||||
user_id: u64,
|
||||
txn: &DatabaseTransaction,
|
||||
) -> Web3ProxyResult<Option<(user::Model, Option<user_tier::Model>)>> {
|
||||
let x = user::Entity::find_by_id(user_id)
|
||||
.find_also_related(user_tier::Entity)
|
||||
.one(txn)
|
||||
.await?;
|
||||
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
/// TODO: improve this so that funding an account that has an "unlimited" key is left alone
|
||||
pub async fn grant_premium_tier(
|
||||
user: &user::Model,
|
||||
user_tier: Option<&user_tier::Model>,
|
||||
txn: &DatabaseTransaction,
|
||||
) -> Web3ProxyResult<()> {
|
||||
if user_tier.is_none() || user_tier.and_then(|x| x.downgrade_tier_id).is_none() {
|
||||
if user_tier.map(|x| x.title.as_str()) == Some("Premium") {
|
||||
// user is already premium
|
||||
} else {
|
||||
info!("upgrading {} to Premium", user.id);
|
||||
|
||||
// switch the user to the premium tier
|
||||
let new_user_tier = user_tier::Entity::find()
|
||||
.filter(user_tier::Column::Title.like("Premium"))
|
||||
.one(txn)
|
||||
.await?
|
||||
.context("premium tier not found")?;
|
||||
|
||||
let mut user = user.clone().into_active_model();
|
||||
|
||||
user.user_tier_id = sea_orm::Set(new_user_tier.id);
|
||||
|
||||
user.save(txn).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -5,7 +5,7 @@ use std::time::Duration;
|
||||
|
||||
use crate::common::admin_increases_balance::admin_increase_balance;
|
||||
use crate::common::create_admin::create_user_as_admin;
|
||||
use crate::common::create_user::{create_user, set_user_tier};
|
||||
use crate::common::create_user::create_user;
|
||||
use crate::common::user_balance::user_get_balance;
|
||||
use crate::common::TestApp;
|
||||
use migration::sea_orm::prelude::Decimal;
|
||||
@ -39,8 +39,6 @@ async fn test_admin_grant_credits() {
|
||||
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
|
||||
info!(?admin_login_response);
|
||||
|
||||
set_user_tier(&x, user_login_response.user.clone(), "Premium").await.unwrap();
|
||||
|
||||
let increase_balance_response = admin_increase_balance(
|
||||
&x,
|
||||
&r,
|
||||
|
@ -1,12 +1,8 @@
|
||||
mod common;
|
||||
|
||||
use crate::common::{
|
||||
admin_increases_balance::admin_increase_balance,
|
||||
create_admin::create_user_as_admin,
|
||||
create_user::{create_user, set_user_tier},
|
||||
rpc_key::user_get_provider,
|
||||
user_balance::user_get_balance,
|
||||
TestApp,
|
||||
admin_increases_balance::admin_increase_balance, create_admin::create_user_as_admin,
|
||||
create_user::create_user, rpc_key::user_get_provider, user_balance::user_get_balance, TestApp,
|
||||
};
|
||||
use ethers::prelude::U64;
|
||||
use migration::sea_orm::prelude::Decimal;
|
||||
@ -33,12 +29,6 @@ async fn test_sum_credits_used() {
|
||||
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
|
||||
let user_login_response = create_user(&x, &r, &user_wallet, None).await;
|
||||
|
||||
set_user_tier(&x, user_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// TODO: set the user's user_id to the "Premium" tier
|
||||
|
||||
info!("starting balance");
|
||||
let balance: Balance = user_get_balance(&x, &r, &user_login_response).await;
|
||||
assert_eq!(
|
||||
|
@ -3,7 +3,7 @@ mod common;
|
||||
use crate::common::admin_deposits::get_admin_deposits;
|
||||
use crate::common::admin_increases_balance::admin_increase_balance;
|
||||
use crate::common::create_admin::create_user_as_admin;
|
||||
use crate::common::create_user::{create_user, set_user_tier};
|
||||
use crate::common::create_user::create_user;
|
||||
use crate::common::referral::{
|
||||
get_referral_code, get_shared_referral_codes, get_used_referral_codes, UserSharedReferralInfo,
|
||||
UserUsedReferralInfo,
|
||||
@ -105,10 +105,6 @@ async fn test_admin_balance_increase() {
|
||||
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
|
||||
let user_login_response = create_user(&x, &r, &user_wallet, None).await;
|
||||
|
||||
set_user_tier(&x, user_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Bump both user's wallet to $20
|
||||
admin_increase_balance(
|
||||
&x,
|
||||
@ -156,10 +152,6 @@ async fn test_user_balance_decreases() {
|
||||
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
|
||||
let user_login_response = create_user(&x, &r, &user_wallet, None).await;
|
||||
|
||||
set_user_tier(&x, user_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Get the rpc keys for this user
|
||||
let rpc_keys: RpcKey = user_get_first_rpc_key(&x, &r, &user_login_response).await;
|
||||
let proxy_endpoint = format!("{}rpc/{}", x.proxy_provider.url(), rpc_keys.secret_key);
|
||||
@ -267,14 +259,7 @@ async fn test_referral_bonus_non_concurrent() {
|
||||
|
||||
let user_login_response = create_user(&x, &r, &user_wallet, Some(referral_link.clone())).await;
|
||||
|
||||
set_user_tier(&x, referrer_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
set_user_tier(&x, user_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Bump both user's wallet to $20
|
||||
// Bump both user's wallet to $20 (which will give them the Premium user tier)
|
||||
admin_increase_balance(
|
||||
&x,
|
||||
&r,
|
||||
@ -417,13 +402,6 @@ async fn test_referral_bonus_concurrent_referrer_only() {
|
||||
|
||||
let user_login_response = create_user(&x, &r, &user_wallet, Some(referral_link.clone())).await;
|
||||
|
||||
set_user_tier(&x, referrer_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
set_user_tier(&x, user_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Bump both user's wallet to $20
|
||||
admin_increase_balance(
|
||||
&x,
|
||||
@ -578,13 +556,6 @@ async fn test_referral_bonus_concurrent_referrer_and_user() {
|
||||
|
||||
let user_login_response = create_user(&x, &r, &user_wallet, Some(referral_link.clone())).await;
|
||||
|
||||
set_user_tier(&x, referrer_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
set_user_tier(&x, user_login_response.user.clone(), "Premium")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Bump both user's wallet to $20
|
||||
admin_increase_balance(
|
||||
&x,
|
||||
|
Loading…
Reference in New Issue
Block a user