Skip to main content

ore_types/
response.rs

1use chrono::NaiveDateTime;
2use serde::{Deserialize, Serialize};
3use solana_sdk::pubkey::Pubkey;
4
5#[cfg(feature = "redis")]
6use redis_derive::{FromRedisValue, ToRedisArgs};
7
8/// Response for a successful login, containing the JWT.
9#[derive(Serialize, Deserialize, Debug, Clone)]
10pub struct AuthResponse {
11    pub token: String,
12}
13
14/// Response for a successful supply endpoint.
15#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
16pub struct SupplyResponse {
17    pub result: String,
18}
19
20/// Response after successfully sending a chat message.
21#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
22pub struct ChatSendMessageResponse {
23    pub status: String,
24    pub message: String,
25}
26
27#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
28pub struct User {
29    pub authority: String,
30    pub username: String,
31    pub profile_photo_url: Option<String>,
32    pub discord_user: Option<DiscordUser>,
33    pub updated_at: NaiveDateTime,
34    pub risk_score: i64,
35    pub is_banned: bool,
36    pub role: Option<String>,
37}
38
39// Notifications
40#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
41#[cfg_attr(feature = "redis", derive(FromRedisValue, ToRedisArgs))]
42pub struct ChatNotification {
43    pub authority: String,
44    pub username: String,
45    pub text: String,
46    pub id: u64,
47    pub ts: i64,
48    pub profile_photo_url: Option<String>,
49    pub role: Option<String>,
50    pub discord_user_id: Option<String>,
51    // Reply fields (optional - null if not a reply)
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub reply_to_id: Option<u64>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub reply_to_text: Option<String>,
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub reply_to_username: Option<String>,
58    // Reaction counts (optional - for backwards compatibility)
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub reactions: Option<ChatReactions>,
61}
62
63/// Reaction counts for a chat message.
64/// Uses named fields instead of emoji keys for type safety and Redis compatibility.
65#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
66#[cfg_attr(feature = "redis", derive(FromRedisValue, ToRedisArgs))]
67pub struct ChatReactions {
68    #[serde(default, skip_serializing_if = "is_zero")]
69    pub thumbs_up: u32, // 👍
70    #[serde(default, skip_serializing_if = "is_zero")]
71    pub heart: u32, // ❤️
72    #[serde(default, skip_serializing_if = "is_zero")]
73    pub laughing: u32, // 😂
74    #[serde(default, skip_serializing_if = "is_zero")]
75    pub surprised: u32, // 😮
76    #[serde(default, skip_serializing_if = "is_zero")]
77    pub sad: u32, // 😢
78    #[serde(default, skip_serializing_if = "is_zero")]
79    pub fire: u32, // 🔥
80}
81
82fn is_zero(n: &u32) -> bool {
83    *n == 0
84}
85
86impl ChatReactions {
87    /// Check if all reaction counts are zero.
88    pub fn is_empty(&self) -> bool {
89        self.thumbs_up == 0
90            && self.heart == 0
91            && self.laughing == 0
92            && self.surprised == 0
93            && self.sad == 0
94            && self.fire == 0
95    }
96
97    /// Get the count for a specific emoji.
98    pub fn get(&self, emoji: &str) -> u32 {
99        match emoji {
100            "👍" => self.thumbs_up,
101            "❤️" | "❤" => self.heart,
102            "😂" => self.laughing,
103            "😮" => self.surprised,
104            "😢" => self.sad,
105            "🔥" => self.fire,
106            _ => 0,
107        }
108    }
109
110    /// Set the count for a specific emoji.
111    pub fn set(&mut self, emoji: &str, count: u32) {
112        match emoji {
113            "👍" => self.thumbs_up = count,
114            "❤️" | "❤" => self.heart = count,
115            "😂" => self.laughing = count,
116            "😮" => self.surprised = count,
117            "😢" => self.sad = count,
118            "🔥" => self.fire = count,
119            _ => {}
120        }
121    }
122}
123
124/// Allowed reaction emoji.
125pub const ALLOWED_REACTION_EMOJI: [&str; 6] = ["👍", "❤️", "😂", "😮", "😢", "🔥"];
126
127/// Check if an emoji is in the allowed reaction set.
128pub fn is_valid_reaction_emoji(emoji: &str) -> bool {
129    // Handle heart emoji variant (with and without variation selector)
130    if emoji == "❤" {
131        return true;
132    }
133    ALLOWED_REACTION_EMOJI.contains(&emoji)
134}
135
136#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
137pub struct ResetNotification {
138    pub block_id: u64,
139}
140
141/// Notification for a deploy event, containing all relevant data for clients.
142#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
143#[cfg_attr(feature = "redis", derive(FromRedisValue, ToRedisArgs))]
144pub struct DeployNotification {
145    /// The authority (miner) who deployed.
146    pub authority: String,
147    /// The amount of SOL deployed (in lamports).
148    pub amount: u64,
149    /// The mask (bitmask of selected squares).
150    pub mask: u64,
151    /// The round ID this deployment is for.
152    pub round_id: u64,
153    /// The signer of the transaction.
154    pub signer: String,
155    /// The strategy used for deployment.
156    pub strategy: u64,
157    /// Total squares selected.
158    pub total_squares: u64,
159    /// Timestamp of the deployment.
160    pub ts: i64,
161    /// Transaction signature.
162    pub sig: String,
163}
164
165/// Notification for a reaction being added or removed from a chat message.
166#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
167pub struct ReactionNotification {
168    pub message_id: u64,
169    pub emoji: String,
170    pub count: u32,
171    pub action: String, // "added" or "removed"
172}
173
174/// A user currently typing in chat.
175#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
176pub struct TypingUser {
177    pub authority: String,
178    pub username: String,
179}
180
181/// Notification for typing indicator updates.
182#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
183pub struct TypingNotification {
184    pub users: Vec<TypingUser>,
185}
186
187#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
188pub enum Notification {
189    Chat(ChatNotification),
190    ChatBatch(Vec<ChatNotification>),
191    Reset(ResetNotification),
192    Deploy(DeployNotification),
193    Reaction(ReactionNotification),
194    Typing(TypingNotification),
195}
196
197impl Notification {
198    pub fn id(&self) -> String {
199        match self {
200            Notification::Chat(chat) => chat.id.to_string(),
201            Notification::ChatBatch(_) => "chat_batch".to_string(),
202            Notification::Reset(reset) => reset.block_id.to_string(),
203            Notification::Deploy(deploy) => deploy.sig.clone(),
204            Notification::Reaction(reaction) => {
205                format!("{}:{}", reaction.message_id, reaction.emoji)
206            }
207            Notification::Typing(_) => "typing".to_string(),
208        }
209    }
210}
211
212/// Response after adding/removing a reaction.
213#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
214pub struct ChatReactResponse {
215    pub status: String,
216    pub action: String,     // "added" or "removed"
217    pub message_id: u64,
218    pub emoji: String,
219    pub count: u32,         // new count for this emoji on this message
220}
221
222/// Response for typing indicator endpoint.
223#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
224pub struct ChatTypingResponse {
225    pub status: String,
226}
227
228/// Response for chat history endpoint with cursor-based pagination.
229#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
230pub struct ChatHistoryResponse {
231    pub messages: Vec<ChatNotification>,
232    pub has_more: bool,
233    pub oldest_id: Option<u64>,
234}
235
236#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
237pub struct DailyRevenue {
238    pub day: String,
239    pub revenue: i64,
240}
241
242// Username validation
243
244#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
245pub struct UsernameValidationResponse {
246    pub valid: bool,
247    pub error: Option<String>,
248}
249
250impl UsernameValidationResponse {
251    pub fn valid() -> Self {
252        Self {
253            valid: true,
254            error: None,
255        }
256    }
257
258    pub fn invalid(error: String) -> Self {
259        Self {
260            valid: false,
261            error: Some(error),
262        }
263    }
264}
265
266/// Response for a successful Discord authentication.
267#[derive(Serialize, Deserialize, Debug, Clone)]
268pub struct DiscordAuthResponse {
269    pub access_token: String,
270}
271
272/// Google user information returned after authentication.
273#[derive(Serialize, Deserialize, Debug, Clone)]
274pub struct GoogleUser {
275    pub email: String,
276    pub name: String,
277    pub picture: Option<String>,
278}
279
280/// Response for a successful Google authentication.
281#[derive(Serialize, Deserialize, Debug, Clone)]
282pub struct GoogleAuthResponse {
283    pub jwt: String,
284    pub user: GoogleUser,
285}
286
287#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
288pub struct DiscordUser {
289    pub id: String,
290    pub username: String,
291    pub discriminator: String,
292    #[serde(default, skip_serializing_if = "Option::is_none")]
293    pub global_name: Option<String>,
294    #[serde(default, skip_serializing_if = "Option::is_none")]
295    pub avatar: Option<String>,
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub verified: Option<bool>,
298    #[serde(default, skip_serializing_if = "Option::is_none")]
299    pub email: Option<String>,
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
303pub struct OreBalance {
304    pub wallet: u64,
305    pub staked: u64,
306    pub unrefined: u64,
307    pub refined: u64,
308    pub lifetime_deployed_sol: u64,
309}
310
311#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
312pub struct LeaderboardEntry {
313    pub authority: String,
314    pub amount: u64,
315    pub username: Option<String>,
316    pub profile_picture_url: Option<String>,
317}
318
319/// Response type for reset events with enriched top miner user info.
320/// Maintains field order matching ore_api::event::ResetEvent for backwards compatibility.
321#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
322pub struct ResetEventResponse {
323    pub disc: u8,
324    pub round_id: u64,
325    pub start_slot: u64,
326    pub end_slot: u64,
327    pub winning_square: u64,
328    pub top_miner: Pubkey,
329    pub num_winners: u64,
330    pub motherlode: u64,
331    pub total_deployed: u64,
332    pub total_vaulted: u64,
333    pub total_winnings: u64,
334    pub total_minted: u64,
335    pub ts: i64,
336    pub rng: u64,
337    pub deployed_winning_square: u64,
338    pub top_miner_username: Option<String>,
339    pub top_miner_profile_photo: Option<String>,
340}
341
342/// Response type for a user's deploy history event.
343#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
344pub struct DeployHistoryEvent {
345    pub sig: String,
346    pub authority: String,
347    pub signer: String,
348    pub amount: u64,
349    pub mask: i64,
350    pub round_id: i64,
351    pub total_squares: i64,
352    pub ts: i64,
353    pub winning_square: i64,
354    pub top_miner: String,
355    pub rewards_sol: u64,
356    pub rewards_ore: u64,
357    pub total_winnings_sol: u64,
358    pub deployed_winning_square: u64,
359    pub motherlode: u64,
360}
361
362/// Response type for a winner in a round, with user data and betting pattern.
363#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
364pub struct RoundWinner {
365    /// The miner's public key.
366    pub authority: String,
367    /// User's display name (if set).
368    pub username: Option<String>,
369    /// User's profile photo URL (if set).
370    pub profile_photo_url: Option<String>,
371    /// Amount deployed on the winning square (in lamports).
372    pub deployed_on_winning: u64,
373    /// Combined mask of all squares the user deployed to (bitmask).
374    pub combined_mask: u64,
375}
376
377/// Response type for listing winners in a round.
378#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
379pub struct RoundWinnersResponse {
380    /// The round ID.
381    pub round_id: u64,
382    /// The winning square (0-24).
383    pub winning_square: u64,
384    /// Total amount deployed on the winning square by all miners.
385    pub total_on_winning: u64,
386    /// Total winnings for the round (in lamports).
387    pub total_winnings: u64,
388    /// List of winners sorted by deployed_on_winning descending.
389    pub winners: Vec<RoundWinner>,
390}