From c379e4b17376597fc3fa85a0c5abf48138f7b5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20Talbot?= <108630700+fetchfern@users.noreply.github.com> Date: Mon, 20 Oct 2025 21:31:20 +0100 Subject: [PATCH] admin/credit: don't credit unprovisioned subscriptions (#4594) * Remove pointless sorting * Filter subscriptions by labrinth's provisioned state --- apps/labrinth/src/routes/internal/billing.rs | 55 +++++++++++--------- apps/labrinth/src/util/archon.rs | 8 +-- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/apps/labrinth/src/routes/internal/billing.rs b/apps/labrinth/src/routes/internal/billing.rs index 3fb75a13..6e6c180e 100644 --- a/apps/labrinth/src/routes/internal/billing.rs +++ b/apps/labrinth/src/routes/internal/billing.rs @@ -1,6 +1,7 @@ use self::payments::*; use crate::auth::get_user_from_headers; use crate::database::models::charge_item::DBCharge; +use crate::database::models::ids::DBUserSubscriptionId; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::products_tax_identifier_item::product_info_by_product_price_id; use crate::database::models::users_subscriptions_credits::DBUserSubscriptionCredit; @@ -2189,10 +2190,8 @@ pub async fn stripe_webhook( Ok(HttpResponse::Ok().finish()) } -pub mod payments; - #[allow(clippy::too_many_arguments)] -async fn apply_credit_many_in_txn( +async fn apply_credit_many( transaction: &mut Transaction<'_, Postgres>, redis: &RedisPool, current_user_id: crate::database::models::ids::DBUserId, @@ -2201,20 +2200,6 @@ async fn apply_credit_many_in_txn( send_email: bool, message: String, ) -> Result<(), ApiError> { - use crate::database::models::ids::DBUserSubscriptionId; - - let mut credit_sub_ids: Vec = - Vec::with_capacity(subscription_ids.len()); - let mut credit_user_ids: Vec = - Vec::with_capacity(subscription_ids.len()); - let mut credit_creditor_ids: Vec = - Vec::with_capacity(subscription_ids.len()); - let mut credit_days: Vec = Vec::with_capacity(subscription_ids.len()); - let mut credit_prev_dues: Vec> = - Vec::with_capacity(subscription_ids.len()); - let mut credit_next_dues: Vec> = - Vec::with_capacity(subscription_ids.len()); - let subs_ids: Vec = subscription_ids .iter() .map(|id| DBUserSubscriptionId(id.0 as i64)) @@ -2225,7 +2210,28 @@ async fn apply_credit_many_in_txn( ) .await?; + let provisioned_count = subs + .iter() + .filter(|s| s.status == SubscriptionStatus::Provisioned) + .count(); + + let mut credit_sub_ids: Vec = + Vec::with_capacity(provisioned_count); + let mut credit_user_ids: Vec = + Vec::with_capacity(provisioned_count); + let mut credit_creditor_ids: Vec = + Vec::with_capacity(provisioned_count); + let mut credit_days: Vec = Vec::with_capacity(provisioned_count); + let mut credit_prev_dues: Vec> = + Vec::with_capacity(provisioned_count); + let mut credit_next_dues: Vec> = + Vec::with_capacity(provisioned_count); + for subscription in subs { + if subscription.status != SubscriptionStatus::Provisioned { + continue; + } + let mut open_charge = charge_item::DBCharge::get_open_subscription( subscription.id, &mut **transaction, @@ -2349,7 +2355,7 @@ pub async fn credit( "You must specify at least one subscription id".to_string(), )); } - apply_credit_many_in_txn( + apply_credit_many( &mut transaction, &redis, crate::database::models::ids::DBUserId(user.id.0 as i64), @@ -2370,9 +2376,8 @@ pub async fn credit( for hostname in nodes { let ids = archon_client.get_servers_by_hostname(&hostname).await?; - server_ids.extend(ids); + server_ids.extend(ids.into_iter().map(|id| id.to_string())); } - server_ids.sort(); server_ids.dedup(); let subs = user_subscription_item::DBUserSubscription::get_many_by_server_ids( &server_ids, @@ -2384,7 +2389,7 @@ pub async fn credit( "No subscriptions found for provided nodes".to_string(), )); } - apply_credit_many_in_txn( + apply_credit_many( &mut transaction, &redis, crate::database::models::ids::DBUserId(user.id.0 as i64), @@ -2396,10 +2401,10 @@ pub async fn credit( .await?; } CreditTarget::Region { region } => { - let parsed_active = + let servers = archon_client.get_active_servers_by_region(®ion).await?; let subs = user_subscription_item::DBUserSubscription::get_many_by_server_ids( - &parsed_active, + &servers.into_iter().map(|id| id.to_string()).collect::>(), &mut *transaction, ) .await?; @@ -2408,7 +2413,7 @@ pub async fn credit( "No subscriptions found for provided region".to_string(), )); } - apply_credit_many_in_txn( + apply_credit_many( &mut transaction, &redis, crate::database::models::ids::DBUserId(user.id.0 as i64), @@ -2425,3 +2430,5 @@ pub async fn credit( Ok(HttpResponse::NoContent().finish()) } + +pub mod payments; diff --git a/apps/labrinth/src/util/archon.rs b/apps/labrinth/src/util/archon.rs index 384648be..621e923f 100644 --- a/apps/labrinth/src/util/archon.rs +++ b/apps/labrinth/src/util/archon.rs @@ -76,14 +76,14 @@ impl ArchonClient { pub async fn get_servers_by_hostname( &self, hostname: &str, - ) -> Result, reqwest::Error> { + ) -> Result, reqwest::Error> { #[derive(Deserialize)] struct NodeByHostnameResponse { servers: Vec, } #[derive(Deserialize)] struct NodeServerEntry { - id: String, + id: Uuid, #[allow(dead_code)] available: Option, } @@ -106,10 +106,10 @@ impl ArchonClient { pub async fn get_active_servers_by_region( &self, region: &str, - ) -> Result, reqwest::Error> { + ) -> Result, reqwest::Error> { #[derive(Deserialize)] struct RegionResponse { - active_servers: Vec, + active_servers: Vec, } let res = self