1
0

Move charges to DB + fix subscription recurring payments (#971)

* Move charges to DB + fix subscription recurring payments

* Finish most + pyro integration

* Finish billing

* Run prepare

* Fix intervals

* Fix clippy

* Remove unused test
This commit is contained in:
Geometrically
2024-10-09 21:11:30 -07:00
committed by GitHub
parent 28b6bf8603
commit c88bfbb5f0
33 changed files with 1692 additions and 816 deletions

View File

@@ -0,0 +1,181 @@
use crate::database::models::{
ChargeId, DatabaseError, ProductPriceId, UserId, UserSubscriptionId,
};
use crate::models::billing::{ChargeStatus, ChargeType, PriceDuration};
use chrono::{DateTime, Utc};
use std::convert::{TryFrom, TryInto};
pub struct ChargeItem {
pub id: ChargeId,
pub user_id: UserId,
pub price_id: ProductPriceId,
pub amount: i64,
pub currency_code: String,
pub status: ChargeStatus,
pub due: DateTime<Utc>,
pub last_attempt: Option<DateTime<Utc>>,
pub type_: ChargeType,
pub subscription_id: Option<UserSubscriptionId>,
pub subscription_interval: Option<PriceDuration>,
}
struct ChargeResult {
id: i64,
user_id: i64,
price_id: i64,
amount: i64,
currency_code: String,
status: String,
due: DateTime<Utc>,
last_attempt: Option<DateTime<Utc>>,
charge_type: String,
subscription_id: Option<i64>,
subscription_interval: Option<String>,
}
impl TryFrom<ChargeResult> for ChargeItem {
type Error = serde_json::Error;
fn try_from(r: ChargeResult) -> Result<Self, Self::Error> {
Ok(ChargeItem {
id: ChargeId(r.id),
user_id: UserId(r.user_id),
price_id: ProductPriceId(r.price_id),
amount: r.amount,
currency_code: r.currency_code,
status: ChargeStatus::from_string(&r.status),
due: r.due,
last_attempt: r.last_attempt,
type_: ChargeType::from_string(&r.charge_type),
subscription_id: r.subscription_id.map(UserSubscriptionId),
subscription_interval: r
.subscription_interval
.map(|x| PriceDuration::from_string(&x)),
})
}
}
macro_rules! select_charges_with_predicate {
($predicate:tt, $param:ident) => {
sqlx::query_as!(
ChargeResult,
r#"
SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval
FROM charges
"#
+ $predicate,
$param
)
};
}
impl ChargeItem {
pub async fn upsert(
&self,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<ChargeId, DatabaseError> {
sqlx::query!(
r#"
INSERT INTO charges (id, user_id, price_id, amount, currency_code, charge_type, status, due, last_attempt, subscription_id, subscription_interval)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
ON CONFLICT (id)
DO UPDATE
SET status = EXCLUDED.status,
last_attempt = EXCLUDED.last_attempt,
due = EXCLUDED.due,
subscription_id = EXCLUDED.subscription_id,
subscription_interval = EXCLUDED.subscription_interval
"#,
self.id.0,
self.user_id.0,
self.price_id.0,
self.amount,
self.currency_code,
self.type_.as_str(),
self.status.as_str(),
self.due,
self.last_attempt,
self.subscription_id.map(|x| x.0),
self.subscription_interval.map(|x| x.as_str()),
)
.execute(&mut **transaction)
.await?;
Ok(self.id)
}
pub async fn get(
id: ChargeId,
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Option<ChargeItem>, DatabaseError> {
let id = id.0;
let res = select_charges_with_predicate!("WHERE id = $1", id)
.fetch_optional(exec)
.await?;
Ok(res.and_then(|r| r.try_into().ok()))
}
pub async fn get_from_user(
user_id: UserId,
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Vec<ChargeItem>, DatabaseError> {
let user_id = user_id.0;
let res = select_charges_with_predicate!("WHERE user_id = $1 ORDER BY due DESC", user_id)
.fetch_all(exec)
.await?;
Ok(res
.into_iter()
.map(|r| r.try_into())
.collect::<Result<Vec<_>, serde_json::Error>>()?)
}
pub async fn get_open_subscription(
user_subscription_id: UserSubscriptionId,
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Option<ChargeItem>, DatabaseError> {
let user_subscription_id = user_subscription_id.0;
let res = select_charges_with_predicate!(
"WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')",
user_subscription_id
)
.fetch_optional(exec)
.await?;
Ok(res.and_then(|r| r.try_into().ok()))
}
pub async fn get_chargeable(
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Vec<ChargeItem>, DatabaseError> {
let now = Utc::now();
let res = select_charges_with_predicate!("WHERE (status = 'open' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')", now)
.fetch_all(exec)
.await?;
Ok(res
.into_iter()
.map(|r| r.try_into())
.collect::<Result<Vec<_>, serde_json::Error>>()?)
}
pub async fn remove(
id: ChargeId,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), DatabaseError> {
sqlx::query!(
"
DELETE FROM charges
WHERE id = $1
",
id.0 as i64
)
.execute(&mut **transaction)
.await?;
Ok(())
}
}

View File

@@ -256,6 +256,14 @@ generate_ids!(
UserSubscriptionId
);
generate_ids!(
pub generate_charge_id,
ChargeId,
8,
"SELECT EXISTS(SELECT 1 FROM charges WHERE id=$1)",
ChargeId
);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Type, Hash, Serialize, Deserialize)]
#[sqlx(transparent)]
pub struct UserId(pub i64);
@@ -386,6 +394,10 @@ pub struct ProductPriceId(pub i64);
#[sqlx(transparent)]
pub struct UserSubscriptionId(pub i64);
#[derive(Copy, Clone, Debug, Type, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[sqlx(transparent)]
pub struct ChargeId(pub i64);
use crate::models::ids;
impl From<ids::ProjectId> for ProjectId {
@@ -571,3 +583,14 @@ impl From<UserSubscriptionId> for ids::UserSubscriptionId {
ids::UserSubscriptionId(id.0 as u64)
}
}
impl From<ids::ChargeId> for ChargeId {
fn from(id: ids::ChargeId) -> Self {
ChargeId(id.0 as i64)
}
}
impl From<ChargeId> for ids::ChargeId {
fn from(id: ChargeId) -> Self {
ids::ChargeId(id.0 as u64)
}
}

View File

@@ -1,6 +1,7 @@
use thiserror::Error;
pub mod categories;
pub mod charge_item;
pub mod collection_item;
pub mod flow_item;
pub mod ids;

View File

@@ -1,7 +1,8 @@
use crate::database::models::{DatabaseError, ProductPriceId, UserId, UserSubscriptionId};
use crate::models::billing::{PriceDuration, SubscriptionStatus};
use crate::models::billing::{PriceDuration, SubscriptionMetadata, SubscriptionStatus};
use chrono::{DateTime, Utc};
use itertools::Itertools;
use std::convert::{TryFrom, TryInto};
pub struct UserSubscriptionItem {
pub id: UserSubscriptionId,
@@ -9,9 +10,8 @@ pub struct UserSubscriptionItem {
pub price_id: ProductPriceId,
pub interval: PriceDuration,
pub created: DateTime<Utc>,
pub expires: DateTime<Utc>,
pub last_charge: Option<DateTime<Utc>>,
pub status: SubscriptionStatus,
pub metadata: Option<SubscriptionMetadata>,
}
struct UserSubscriptionResult {
@@ -20,9 +20,8 @@ struct UserSubscriptionResult {
price_id: i64,
interval: String,
pub created: DateTime<Utc>,
pub expires: DateTime<Utc>,
pub last_charge: Option<DateTime<Utc>>,
pub status: String,
pub metadata: serde_json::Value,
}
macro_rules! select_user_subscriptions_with_predicate {
@@ -31,8 +30,8 @@ macro_rules! select_user_subscriptions_with_predicate {
UserSubscriptionResult,
r#"
SELECT
id, user_id, price_id, interval, created, expires, last_charge, status
FROM users_subscriptions
us.id, us.user_id, us.price_id, us.interval, us.created, us.status, us.metadata
FROM users_subscriptions us
"#
+ $predicate,
$param
@@ -40,18 +39,19 @@ macro_rules! select_user_subscriptions_with_predicate {
};
}
impl From<UserSubscriptionResult> for UserSubscriptionItem {
fn from(r: UserSubscriptionResult) -> Self {
UserSubscriptionItem {
impl TryFrom<UserSubscriptionResult> for UserSubscriptionItem {
type Error = serde_json::Error;
fn try_from(r: UserSubscriptionResult) -> Result<Self, Self::Error> {
Ok(UserSubscriptionItem {
id: UserSubscriptionId(r.id),
user_id: UserId(r.user_id),
price_id: ProductPriceId(r.price_id),
interval: PriceDuration::from_string(&r.interval),
created: r.created,
expires: r.expires,
last_charge: r.last_charge,
status: SubscriptionStatus::from_string(&r.status),
}
metadata: serde_json::from_value(r.metadata)?,
})
}
}
@@ -70,11 +70,14 @@ impl UserSubscriptionItem {
let ids = ids.iter().map(|id| id.0).collect_vec();
let ids_ref: &[i64] = &ids;
let results =
select_user_subscriptions_with_predicate!("WHERE id = ANY($1::bigint[])", ids_ref)
select_user_subscriptions_with_predicate!("WHERE us.id = ANY($1::bigint[])", ids_ref)
.fetch_all(exec)
.await?;
Ok(results.into_iter().map(|r| r.into()).collect())
Ok(results
.into_iter()
.map(|r| r.try_into())
.collect::<Result<Vec<_>, serde_json::Error>>()?)
}
pub async fn get_all_user(
@@ -82,22 +85,38 @@ impl UserSubscriptionItem {
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Vec<UserSubscriptionItem>, DatabaseError> {
let user_id = user_id.0;
let results = select_user_subscriptions_with_predicate!("WHERE user_id = $1", user_id)
let results = select_user_subscriptions_with_predicate!("WHERE us.user_id = $1", user_id)
.fetch_all(exec)
.await?;
Ok(results.into_iter().map(|r| r.into()).collect())
Ok(results
.into_iter()
.map(|r| r.try_into())
.collect::<Result<Vec<_>, serde_json::Error>>()?)
}
pub async fn get_all_expired(
pub async fn get_all_unprovision(
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Vec<UserSubscriptionItem>, DatabaseError> {
let now = Utc::now();
let results = select_user_subscriptions_with_predicate!("WHERE expires < $1", now)
.fetch_all(exec)
.await?;
let results = select_user_subscriptions_with_predicate!(
"
INNER JOIN charges c
ON c.subscription_id = us.id
AND (
(c.status = 'cancelled' AND c.due < $1) OR
(c.status = 'failed' AND c.last_attempt < $1 - INTERVAL '2 days')
)
",
now
)
.fetch_all(exec)
.await?;
Ok(results.into_iter().map(|r| r.into()).collect())
Ok(results
.into_iter()
.map(|r| r.try_into())
.collect::<Result<Vec<_>, serde_json::Error>>()?)
}
pub async fn upsert(
@@ -107,44 +126,25 @@ impl UserSubscriptionItem {
sqlx::query!(
"
INSERT INTO users_subscriptions (
id, user_id, price_id, interval, created, expires, last_charge, status
id, user_id, price_id, interval, created, status, metadata
)
VALUES (
$1, $2, $3, $4, $5, $6, $7, $8
$1, $2, $3, $4, $5, $6, $7
)
ON CONFLICT (id)
DO UPDATE
SET interval = EXCLUDED.interval,
expires = EXCLUDED.expires,
last_charge = EXCLUDED.last_charge,
status = EXCLUDED.status,
price_id = EXCLUDED.price_id
price_id = EXCLUDED.price_id,
metadata = EXCLUDED.metadata
",
self.id.0,
self.user_id.0,
self.price_id.0,
self.interval.as_str(),
self.created,
self.expires,
self.last_charge,
self.status.as_str(),
)
.execute(&mut **transaction)
.await?;
Ok(())
}
pub async fn remove(
id: UserSubscriptionId,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), DatabaseError> {
sqlx::query!(
"
DELETE FROM users_subscriptions
WHERE id = $1
",
id.0 as i64
serde_json::to_value(&self.metadata)?,
)
.execute(&mut **transaction)
.await?;

View File

@@ -15,7 +15,7 @@ pub async fn delete_file_version(
file_name: &str,
) -> Result<DeleteFileData, FileHostingError> {
let response = reqwest::Client::new()
.post(&format!(
.post(format!(
"{}/b2api/v2/b2_delete_file_version",
authorization_data.api_url
))

View File

@@ -259,15 +259,24 @@ pub fn app_setup(
}
let stripe_client = stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
// {
// let pool_ref = pool.clone();
// let redis_ref = redis_pool.clone();
// let stripe_client_ref = stripe_client.clone();
//
// actix_rt::spawn(async move {
// routes::internal::billing::task(stripe_client_ref, pool_ref, redis_ref).await;
// });
// }
{
let pool_ref = pool.clone();
let redis_ref = redis_pool.clone();
let stripe_client_ref = stripe_client.clone();
actix_rt::spawn(async move {
routes::internal::billing::task(stripe_client_ref, pool_ref, redis_ref).await;
});
}
{
let pool_ref = pool.clone();
let redis_ref = redis_pool.clone();
actix_rt::spawn(async move {
routes::internal::billing::subscription_task(pool_ref, redis_ref).await;
});
}
let ip_salt = Pepper {
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
@@ -456,5 +465,7 @@ pub fn check_env_vars() -> bool {
failed |= check_var::<u64>("ADITUDE_API_KEY");
failed |= check_var::<String>("PYRO_API_KEY");
failed
}

View File

@@ -21,6 +21,7 @@ pub struct Product {
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ProductMetadata {
Midas,
Pyro { ram: u32 },
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
@@ -55,6 +56,13 @@ pub enum PriceDuration {
}
impl PriceDuration {
pub fn duration(&self) -> chrono::Duration {
match self {
PriceDuration::Monthly => chrono::Duration::days(30),
PriceDuration::Yearly => chrono::Duration::days(365),
}
}
pub fn from_string(string: &str) -> PriceDuration {
match string {
"monthly" => PriceDuration::Monthly,
@@ -84,8 +92,7 @@ pub struct UserSubscription {
pub interval: PriceDuration,
pub status: SubscriptionStatus,
pub created: DateTime<Utc>,
pub expires: DateTime<Utc>,
pub last_charge: Option<DateTime<Utc>>,
pub metadata: Option<SubscriptionMetadata>,
}
impl From<crate::database::models::user_subscription_item::UserSubscriptionItem>
@@ -99,38 +106,119 @@ impl From<crate::database::models::user_subscription_item::UserSubscriptionItem>
interval: x.interval,
status: x.status,
created: x.created,
expires: x.expires,
last_charge: x.last_charge,
metadata: x.metadata,
}
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum SubscriptionStatus {
Active,
PaymentProcessing,
PaymentFailed,
Cancelled,
Provisioned,
Unprovisioned,
}
impl SubscriptionStatus {
pub fn from_string(string: &str) -> SubscriptionStatus {
match string {
"active" => SubscriptionStatus::Active,
"payment-processing" => SubscriptionStatus::PaymentProcessing,
"payment-failed" => SubscriptionStatus::PaymentFailed,
"cancelled" => SubscriptionStatus::Cancelled,
_ => SubscriptionStatus::Cancelled,
"provisioned" => SubscriptionStatus::Provisioned,
"unprovisioned" => SubscriptionStatus::Unprovisioned,
_ => SubscriptionStatus::Provisioned,
}
}
pub fn as_str(&self) -> &'static str {
match self {
SubscriptionStatus::Active => "active",
SubscriptionStatus::PaymentProcessing => "payment-processing",
SubscriptionStatus::PaymentFailed => "payment-failed",
SubscriptionStatus::Cancelled => "cancelled",
SubscriptionStatus::Provisioned => "provisioned",
SubscriptionStatus::Unprovisioned => "unprovisioned",
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum SubscriptionMetadata {
Pyro { id: String },
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(from = "Base62Id")]
#[serde(into = "Base62Id")]
pub struct ChargeId(pub u64);
#[derive(Serialize, Deserialize)]
pub struct Charge {
pub id: ChargeId,
pub user_id: UserId,
pub price_id: ProductPriceId,
pub amount: i64,
pub currency_code: String,
pub status: ChargeStatus,
pub due: DateTime<Utc>,
pub last_attempt: Option<DateTime<Utc>>,
#[serde(flatten)]
pub type_: ChargeType,
pub subscription_id: Option<UserSubscriptionId>,
pub subscription_interval: Option<PriceDuration>,
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ChargeType {
OneTime,
Subscription,
Proration,
}
impl ChargeType {
pub fn as_str(&self) -> &'static str {
match self {
ChargeType::OneTime => "one-time",
ChargeType::Subscription { .. } => "subscription",
ChargeType::Proration { .. } => "proration",
}
}
pub fn from_string(string: &str) -> ChargeType {
match string {
"one-time" => ChargeType::OneTime,
"subscription" => ChargeType::Subscription,
"proration" => ChargeType::Proration,
_ => ChargeType::OneTime,
}
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum ChargeStatus {
// Open charges are for the next billing interval
Open,
Processing,
Succeeded,
Failed,
Cancelled,
}
impl ChargeStatus {
pub fn from_string(string: &str) -> ChargeStatus {
match string {
"processing" => ChargeStatus::Processing,
"succeeded" => ChargeStatus::Succeeded,
"failed" => ChargeStatus::Failed,
"open" => ChargeStatus::Open,
"cancelled" => ChargeStatus::Cancelled,
_ => ChargeStatus::Failed,
}
}
pub fn as_str(&self) -> &'static str {
match self {
ChargeStatus::Processing => "processing",
ChargeStatus::Succeeded => "succeeded",
ChargeStatus::Failed => "failed",
ChargeStatus::Open => "open",
ChargeStatus::Cancelled => "cancelled",
}
}
}

View File

@@ -13,8 +13,7 @@ pub use super::teams::TeamId;
pub use super::threads::ThreadId;
pub use super::threads::ThreadMessageId;
pub use super::users::UserId;
pub use crate::models::billing::UserSubscriptionId;
pub use crate::models::v3::billing::{ProductId, ProductPriceId};
pub use crate::models::billing::{ChargeId, ProductId, ProductPriceId, UserSubscriptionId};
use thiserror::Error;
/// Generates a random 64 bit integer that is exactly `n` characters
@@ -137,6 +136,7 @@ base62_id_impl!(PayoutId, PayoutId);
base62_id_impl!(ProductId, ProductId);
base62_id_impl!(ProductPriceId, ProductPriceId);
base62_id_impl!(UserSubscriptionId, UserSubscriptionId);
base62_id_impl!(ChargeId, ChargeId);
pub mod base62_impl {
use serde::de::{self, Deserializer, Visitor};

View File

@@ -74,7 +74,7 @@ impl PayoutsQueue {
}
let credential: PaypalCredential = client
.post(&format!("{}oauth2/token", dotenvy::var("PAYPAL_API_URL")?))
.post(format!("{}oauth2/token", dotenvy::var("PAYPAL_API_URL")?))
.header("Accept", "application/json")
.header("Accept-Language", "en_US")
.header("Authorization", formatted_key)

File diff suppressed because it is too large Load Diff

View File

@@ -510,7 +510,7 @@ impl AuthProvider {
map.insert("grant_type", "authorization_code");
let token: AccessToken = reqwest::Client::new()
.post(&format!("{api_url}oauth2/token"))
.post(format!("{api_url}oauth2/token"))
.header(reqwest::header::ACCEPT, "application/json")
.header(
AUTHORIZATION,
@@ -766,7 +766,7 @@ impl AuthProvider {
let api_url = dotenvy::var("PAYPAL_API_URL")?;
let paypal_user: PayPalUser = reqwest::Client::new()
.get(&format!(
.get(format!(
"{api_url}identity/openidconnect/userinfo?schema=openid"
))
.header(reqwest::header::USER_AGENT, "Modrinth")
@@ -1393,7 +1393,7 @@ pub async fn sign_up_beehiiv(email: &str) -> Result<(), AuthenticationError> {
let client = reqwest::Client::new();
client
.post(&format!(
.post(format!(
"https://api.beehiiv.com/v2/publications/{id}/subscriptions"
))
.header(AUTHORIZATION, format!("Bearer {}", api_key))

View File

@@ -21,7 +21,7 @@ pub async fn export(
&req,
&**pool,
&redis,
&*session_queue,
&session_queue,
Some(&[Scopes::SESSION_ACCESS]),
)
.await?
@@ -34,19 +34,19 @@ pub async fn export(
crate::database::models::Collection::get_many(&collection_ids, &**pool, &redis)
.await?
.into_iter()
.map(|x| crate::models::collections::Collection::from(x))
.map(crate::models::collections::Collection::from)
.collect::<Vec<_>>();
let follows = crate::database::models::User::get_follows(user_id, &**pool)
.await?
.into_iter()
.map(|x| crate::models::ids::ProjectId::from(x))
.map(crate::models::ids::ProjectId::from)
.collect::<Vec<_>>();
let projects = crate::database::models::User::get_projects(user_id, &**pool, &redis)
.await?
.into_iter()
.map(|x| crate::models::ids::ProjectId::from(x))
.map(crate::models::ids::ProjectId::from)
.collect::<Vec<_>>();
let org_ids = crate::database::models::User::get_organizations(user_id, &**pool).await?;
@@ -64,7 +64,7 @@ pub async fn export(
)
.await?
.into_iter()
.map(|x| crate::models::notifications::Notification::from(x))
.map(crate::models::notifications::Notification::from)
.collect::<Vec<_>>();
let oauth_clients =
@@ -73,7 +73,7 @@ pub async fn export(
)
.await?
.into_iter()
.map(|x| crate::models::oauth_clients::OAuthClient::from(x))
.map(crate::models::oauth_clients::OAuthClient::from)
.collect::<Vec<_>>();
let oauth_authorizations = crate::database::models::oauth_client_authorization_item::OAuthClientAuthorization::get_all_for_user(
@@ -81,7 +81,7 @@ pub async fn export(
)
.await?
.into_iter()
.map(|x| crate::models::oauth_clients::OAuthClientAuthorization::from(x))
.map(crate::models::oauth_clients::OAuthClientAuthorization::from)
.collect::<Vec<_>>();
let pat_ids = crate::database::models::pat_item::PersonalAccessToken::get_user_pats(
@@ -102,7 +102,7 @@ pub async fn export(
let payouts = crate::database::models::payout_item::Payout::get_many(&payout_ids, &**pool)
.await?
.into_iter()
.map(|x| crate::models::payouts::Payout::from(x))
.map(crate::models::payouts::Payout::from)
.collect::<Vec<_>>();
let report_ids =
@@ -110,7 +110,7 @@ pub async fn export(
let reports = crate::database::models::report_item::Report::get_many(&report_ids, &**pool)
.await?
.into_iter()
.map(|x| crate::models::reports::Report::from(x))
.map(crate::models::reports::Report::from)
.collect::<Vec<_>>();
let message_ids = sqlx::query!(
@@ -146,7 +146,7 @@ pub async fn export(
crate::database::models::image_item::Image::get_many(&uploaded_images_ids, &**pool, &redis)
.await?
.into_iter()
.map(|x| crate::models::images::Image::from(x))
.map(crate::models::images::Image::from)
.collect::<Vec<_>>();
let subscriptions =
@@ -155,7 +155,7 @@ pub async fn export(
)
.await?
.into_iter()
.map(|x| crate::models::billing::UserSubscription::from(x))
.map(crate::models::billing::UserSubscription::from)
.collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(serde_json::json!({

View File

@@ -106,14 +106,11 @@ pub async fn version_create(
// Get all possible side-types for loaders given- we will use these to check if we need to convert/apply singleplayer, etc.
let loaders = match v3::tags::loader_list(client.clone(), redis.clone()).await {
Ok(loader_response) => match v2_reroute::extract_ok_json::<
Vec<v3::tags::LoaderData>,
>(loader_response)
.await
{
Ok(loaders) => loaders,
Err(_) => vec![],
},
Ok(loader_response) => {
(v2_reroute::extract_ok_json::<Vec<v3::tags::LoaderData>>(loader_response)
.await)
.unwrap_or_default()
}
Err(_) => vec![],
};

View File

@@ -352,7 +352,7 @@ pub async fn create_payout(
.fetch_optional(&mut *transaction)
.await?;
let balance = get_user_balance(user.id.into(), &**pool).await?;
let balance = get_user_balance(user.id, &pool).await?;
if balance.available < body.amount || body.amount < Decimal::ZERO {
return Err(ApiError::InvalidInput(
"You do not have enough funds to make this payout!".to_string(),
@@ -734,7 +734,7 @@ pub async fn get_balance(
.await?
.1;
let balance = get_user_balance(user.id.into(), &**pool).await?;
let balance = get_user_balance(user.id.into(), &pool).await?;
Ok(HttpResponse::Ok().json(balance))
}