tetratto_core/model/
economy.rs

1use serde::{Serialize, Deserialize};
2use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
3use super::auth::User;
4
5#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub enum ProductFulfillmentMethod {
7    /// Automatically send a letter to the customer with the specified content.
8    AutoMail(String),
9    /// Manually send a letter to the customer with the specified content.
10    ///
11    /// This will leave the [`CoinTransfer`] pending until you send this mail.
12    ManualMail,
13    /// A CSS snippet which can be applied to user profiles.
14    ///
15    /// Only supporters can create products like this.
16    ProfileStyle,
17}
18
19#[derive(Clone, Serialize, Deserialize)]
20pub struct ProductUploads {
21    /// Promotional thumbnails shown on the product page.
22    ///
23    /// Maximum of 4 with a maximum upload size of 2 MiB.
24    #[serde(default)]
25    pub thumbnails: Vec<usize>,
26    /// Reward given to users through active configurations after they purchase the product.
27    //
28    //  Maximum upload size of 4 MiB.
29    #[serde(default)]
30    pub reward: usize,
31}
32
33impl Default for ProductUploads {
34    fn default() -> Self {
35        Self {
36            thumbnails: Vec::new(),
37            reward: 0,
38        }
39    }
40}
41
42#[derive(Clone, Serialize, Deserialize)]
43pub struct Product {
44    pub id: usize,
45    pub created: usize,
46    pub owner: usize,
47    pub title: String,
48    pub description: String,
49    /// How this product will be delivered.
50    pub method: ProductFulfillmentMethod,
51    /// If this product is actually for sale.
52    pub on_sale: bool,
53    /// The price of this product.
54    pub price: i32,
55    /// The number of times this product can be purchased.
56    ///
57    /// A negative stock means the product has unlimited stock.
58    pub stock: i32,
59    /// If this product is limited to one purchase per person.
60    #[serde(default)]
61    pub single_use: bool,
62    /// Data for this product. Only used by snippets.
63    #[serde(default)]
64    pub data: String,
65    /// Uploads for this product.
66    #[serde(default)]
67    pub uploads: ProductUploads,
68}
69
70impl Product {
71    /// Create a new [`Product`].
72    pub fn new(owner: usize, title: String, description: String) -> Self {
73        Self {
74            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
75            created: unix_epoch_timestamp(),
76            owner,
77            title,
78            description,
79            method: ProductFulfillmentMethod::ManualMail,
80            on_sale: false,
81            price: 0,
82            stock: 0,
83            single_use: true,
84            data: String::new(),
85            uploads: ProductUploads::default(),
86        }
87    }
88}
89
90#[derive(Serialize, Deserialize)]
91pub enum CoinTransferMethod {
92    Transfer,
93    /// A [`Product`] purchase with the product's ID.
94    Purchase(usize),
95}
96
97#[derive(Serialize, Deserialize, PartialEq, Eq)]
98pub enum CoinTransferSource {
99    /// An unknown source, such as a transfer request.
100    General,
101    /// A product sale.
102    Sale,
103    /// A purchase of coins through Stripe.
104    Purchase,
105    /// A refund of coins.
106    Refund,
107    /// The charge for keeping an ad running.
108    AdCharge,
109    /// Gained coins from a click on an ad on your site.
110    AdClick,
111}
112
113#[derive(Serialize, Deserialize)]
114pub struct CoinTransfer {
115    pub id: usize,
116    pub created: usize,
117    pub sender: usize,
118    pub receiver: usize,
119    pub amount: i32,
120    pub is_pending: bool,
121    pub method: CoinTransferMethod,
122    pub source: CoinTransferSource,
123}
124
125impl CoinTransfer {
126    /// Create a new [`CoinTransfer`].
127    pub fn new(
128        sender: usize,
129        receiver: usize,
130        amount: i32,
131        method: CoinTransferMethod,
132        source: CoinTransferSource,
133    ) -> Self {
134        Self {
135            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
136            created: unix_epoch_timestamp(),
137            sender,
138            receiver,
139            amount,
140            is_pending: false,
141            method,
142            source,
143        }
144    }
145
146    /// Apply the effects of this transaction onto the sender and receiver balances.
147    ///
148    /// # Returns
149    /// `(sender bankrupt, receiver bankrupt)`
150    pub fn apply(&self, sender: &mut User, receiver: &mut User) -> (bool, bool) {
151        sender.coins -= self.amount;
152        receiver.coins += self.amount;
153        (sender.coins < 0, receiver.coins < 0)
154    }
155}
156
157/// <https://en.wikipedia.org/wiki/Web_banner#Standard_sizes>
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
159pub enum UserAdSize {
160    /// 970x250
161    Billboard,
162    /// 720x90
163    Leaderboard,
164    /// 160x600
165    Skyscraper,
166    /// 300x250
167    MediumRectangle,
168    /// 320x50 - mobile only
169    MobileLeaderboard,
170}
171
172impl Default for UserAdSize {
173    fn default() -> Self {
174        Self::MediumRectangle
175    }
176}
177
178impl UserAdSize {
179    /// Get the dimensions of the size in CSS pixels.
180    pub fn dimensions(&self) -> (u16, u16) {
181        match self {
182            Self::Billboard => (970, 250),
183            Self::Leaderboard => (720, 90),
184            Self::Skyscraper => (160, 600),
185            Self::MediumRectangle => (300, 250),
186            Self::MobileLeaderboard => (320, 50),
187        }
188    }
189}
190
191#[derive(Serialize, Deserialize)]
192pub struct UserAd {
193    pub id: usize,
194    pub created: usize,
195    pub owner: usize,
196    pub upload_id: usize,
197    pub target: String,
198    /// The time that the owner was last charged for keeping this ad up.
199    ///
200    /// Ads cost 50 coins per day of running.
201    pub last_charge_time: usize,
202    pub is_running: bool,
203    pub size: UserAdSize,
204}
205
206impl UserAd {
207    /// Create a new [`UserAd`].
208    pub fn new(owner: usize, upload_id: usize, target: String, size: UserAdSize) -> Self {
209        let created = unix_epoch_timestamp();
210        Self {
211            id: 0, // will be overwritten by postgres
212            created,
213            owner,
214            upload_id,
215            target,
216            last_charge_time: 0,
217            is_running: false,
218            size,
219        }
220    }
221}