tetratto-core 16.0.1

The core behind Tetratto
Documentation
use serde::{Serialize, Deserialize};
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
use super::auth::User;

#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ProductFulfillmentMethod {
    /// Automatically send a letter to the customer with the specified content.
    AutoMail(String),
    /// Manually send a letter to the customer with the specified content.
    ///
    /// This will leave the [`CoinTransfer`] pending until you send this mail.
    ManualMail,
    /// A CSS snippet which can be applied to user profiles.
    ///
    /// Only supporters can create products like this.
    ProfileStyle,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct ProductUploads {
    /// Promotional thumbnails shown on the product page.
    ///
    /// Maximum of 4 with a maximum upload size of 2 MiB.
    #[serde(default)]
    pub thumbnails: Vec<usize>,
    /// Reward given to users through active configurations after they purchase the product.
    //
    //  Maximum upload size of 4 MiB.
    #[serde(default)]
    pub reward: usize,
}

impl Default for ProductUploads {
    fn default() -> Self {
        Self {
            thumbnails: Vec::new(),
            reward: 0,
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Product {
    pub id: usize,
    pub created: usize,
    pub owner: usize,
    pub title: String,
    pub description: String,
    /// How this product will be delivered.
    pub method: ProductFulfillmentMethod,
    /// If this product is actually for sale.
    pub on_sale: bool,
    /// The price of this product.
    pub price: i32,
    /// The number of times this product can be purchased.
    ///
    /// A negative stock means the product has unlimited stock.
    pub stock: i32,
    /// If this product is limited to one purchase per person.
    #[serde(default)]
    pub single_use: bool,
    /// Data for this product. Only used by snippets.
    #[serde(default)]
    pub data: String,
    /// Uploads for this product.
    #[serde(default)]
    pub uploads: ProductUploads,
}

impl Product {
    /// Create a new [`Product`].
    pub fn new(owner: usize, title: String, description: String) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp(),
            owner,
            title,
            description,
            method: ProductFulfillmentMethod::ManualMail,
            on_sale: false,
            price: 0,
            stock: 0,
            single_use: true,
            data: String::new(),
            uploads: ProductUploads::default(),
        }
    }
}

#[derive(Serialize, Deserialize)]
pub enum CoinTransferMethod {
    Transfer,
    /// A [`Product`] purchase with the product's ID.
    Purchase(usize),
}

#[derive(Serialize, Deserialize, PartialEq, Eq)]
pub enum CoinTransferSource {
    /// An unknown source, such as a transfer request.
    General,
    /// A product sale.
    Sale,
    /// A purchase of coins through Stripe.
    Purchase,
    /// A refund of coins.
    Refund,
    /// The charge for keeping an ad running.
    AdCharge,
    /// Gained coins from a click on an ad on your site.
    AdClick,
}

#[derive(Serialize, Deserialize)]
pub struct CoinTransfer {
    pub id: usize,
    pub created: usize,
    pub sender: usize,
    pub receiver: usize,
    pub amount: i32,
    pub is_pending: bool,
    pub method: CoinTransferMethod,
    pub source: CoinTransferSource,
}

impl CoinTransfer {
    /// Create a new [`CoinTransfer`].
    pub fn new(
        sender: usize,
        receiver: usize,
        amount: i32,
        method: CoinTransferMethod,
        source: CoinTransferSource,
    ) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp(),
            sender,
            receiver,
            amount,
            is_pending: false,
            method,
            source,
        }
    }

    /// Apply the effects of this transaction onto the sender and receiver balances.
    ///
    /// # Returns
    /// `(sender bankrupt, receiver bankrupt)`
    pub fn apply(&self, sender: &mut User, receiver: &mut User) -> (bool, bool) {
        sender.coins -= self.amount;
        receiver.coins += self.amount;
        (sender.coins < 0, receiver.coins < 0)
    }
}

/// <https://en.wikipedia.org/wiki/Web_banner#Standard_sizes>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum UserAdSize {
    /// 970x250
    Billboard,
    /// 720x90
    Leaderboard,
    /// 160x600
    Skyscraper,
    /// 300x250
    MediumRectangle,
    /// 320x50 - mobile only
    MobileLeaderboard,
}

impl Default for UserAdSize {
    fn default() -> Self {
        Self::MediumRectangle
    }
}

impl UserAdSize {
    /// Get the dimensions of the size in CSS pixels.
    pub fn dimensions(&self) -> (u16, u16) {
        match self {
            Self::Billboard => (970, 250),
            Self::Leaderboard => (720, 90),
            Self::Skyscraper => (160, 600),
            Self::MediumRectangle => (300, 250),
            Self::MobileLeaderboard => (320, 50),
        }
    }
}

#[derive(Serialize, Deserialize)]
pub struct UserAd {
    pub id: usize,
    pub created: usize,
    pub owner: usize,
    pub upload_id: usize,
    pub target: String,
    /// The time that the owner was last charged for keeping this ad up.
    ///
    /// Ads cost 50 coins per day of running.
    pub last_charge_time: usize,
    pub is_running: bool,
    pub size: UserAdSize,
}

impl UserAd {
    /// Create a new [`UserAd`].
    pub fn new(owner: usize, upload_id: usize, target: String, size: UserAdSize) -> Self {
        let created = unix_epoch_timestamp();
        Self {
            id: 0, // will be overwritten by postgres
            created,
            owner,
            upload_id,
            target,
            last_charge_time: 0,
            is_running: false,
            size,
        }
    }
}