mostro_client/parser/
orders.rs

1use std::collections::HashMap;
2
3use crate::util::Event;
4use anyhow::Result;
5use chrono::DateTime;
6use comfy_table::presets::UTF8_FULL;
7use comfy_table::*;
8use log::{error, info};
9use mostro_core::prelude::*;
10use nostr_sdk::prelude::*;
11use uuid::Uuid;
12
13use crate::nip33::order_from_tags;
14
15pub fn parse_orders_events(
16    events: Events,
17    currency: Option<String>,
18    status: Option<Status>,
19    kind: Option<mostro_core::order::Kind>,
20) -> Vec<SmallOrder> {
21    // HashMap to store the latest order by id
22    let mut latest_by_id: HashMap<Uuid, SmallOrder> = HashMap::new();
23
24    for event in events.iter() {
25        // Get order from tags
26        let mut order = match order_from_tags(event.tags.clone()) {
27            Ok(o) => o,
28            Err(e) => {
29                error!("{e:?}");
30                continue;
31            }
32        };
33        // Get order id
34        let order_id = match order.id {
35            Some(id) => id,
36            None => {
37                info!("Order ID is none");
38                continue;
39            }
40        };
41        // Check if order kind is none
42        if order.kind.is_none() {
43            info!("Order kind is none");
44            continue;
45        }
46        // Set created at
47        order.created_at = Some(event.created_at.as_u64() as i64);
48        // Update latest order by id
49        latest_by_id
50            .entry(order_id)
51            .and_modify(|existing| {
52                let new_ts = order.created_at.unwrap_or(0);
53                let old_ts = existing.created_at.unwrap_or(0);
54                if new_ts > old_ts {
55                    *existing = order.clone();
56                }
57            })
58            .or_insert(order);
59    }
60
61    let mut requested: Vec<SmallOrder> = latest_by_id
62        .into_values()
63        .filter(|o| status.map(|s| o.status == Some(s)).unwrap_or(true))
64        .filter(|o| currency.as_ref().map(|c| o.fiat_code == *c).unwrap_or(true))
65        .filter(|o| {
66            kind.as_ref()
67                .map(|k| o.kind.as_ref() == Some(k))
68                .unwrap_or(true)
69        })
70        .collect();
71
72    requested.sort_by(|a, b| b.created_at.cmp(&a.created_at));
73    requested
74}
75
76pub fn print_order_preview(ord: Payload) -> Result<String, String> {
77    let single_order = match ord {
78        Payload::Order(o) => o,
79        _ => return Err("Error".to_string()),
80    };
81
82    let mut table = Table::new();
83
84    table
85        .load_preset(UTF8_FULL)
86        .set_content_arrangement(ContentArrangement::Dynamic)
87        .set_width(160)
88        .set_header(vec![
89            Cell::new("Buy/Sell")
90                .add_attribute(Attribute::Bold)
91                .set_alignment(CellAlignment::Center),
92            Cell::new("Sats Amount")
93                .add_attribute(Attribute::Bold)
94                .set_alignment(CellAlignment::Center),
95            Cell::new("Fiat Code")
96                .add_attribute(Attribute::Bold)
97                .set_alignment(CellAlignment::Center),
98            Cell::new("Fiat Amount")
99                .add_attribute(Attribute::Bold)
100                .set_alignment(CellAlignment::Center),
101            Cell::new("Payment method")
102                .add_attribute(Attribute::Bold)
103                .set_alignment(CellAlignment::Center),
104            Cell::new("Premium %")
105                .add_attribute(Attribute::Bold)
106                .set_alignment(CellAlignment::Center),
107        ]);
108
109    //Table rows
110    let r = Row::from(vec![
111        if let Some(k) = single_order.kind {
112            match k {
113                mostro_core::order::Kind::Buy => Cell::new(k.to_string())
114                    .fg(Color::Green)
115                    .set_alignment(CellAlignment::Center),
116                mostro_core::order::Kind::Sell => Cell::new(k.to_string())
117                    .fg(Color::Red)
118                    .set_alignment(CellAlignment::Center),
119            }
120        } else {
121            Cell::new("BUY/SELL").set_alignment(CellAlignment::Center)
122        },
123        if single_order.amount == 0 {
124            Cell::new("market price").set_alignment(CellAlignment::Center)
125        } else {
126            Cell::new(single_order.amount).set_alignment(CellAlignment::Center)
127        },
128        Cell::new(single_order.fiat_code.to_string()).set_alignment(CellAlignment::Center),
129        // No range order print row
130        if single_order.min_amount.is_none() && single_order.max_amount.is_none() {
131            Cell::new(single_order.fiat_amount.to_string()).set_alignment(CellAlignment::Center)
132        } else {
133            let range_str = match (single_order.min_amount, single_order.max_amount) {
134                (Some(min), Some(max)) => format!("{}-{}", min, max),
135                (Some(min), None) => format!("{}-?", min),
136                (None, Some(max)) => format!("?-{}", max),
137                (None, None) => "?".to_string(),
138            };
139            Cell::new(range_str).set_alignment(CellAlignment::Center)
140        },
141        Cell::new(single_order.payment_method.to_string()).set_alignment(CellAlignment::Center),
142        Cell::new(single_order.premium.to_string()).set_alignment(CellAlignment::Center),
143    ]);
144
145    table.add_row(r);
146
147    Ok(table.to_string())
148}
149
150pub fn print_orders_table(orders_table: Vec<Event>) -> Result<String> {
151    let mut table = Table::new();
152    // Convert Event to SmallOrder
153    let orders_table: Vec<SmallOrder> = orders_table
154        .into_iter()
155        .filter_map(|event| {
156            if let Event::SmallOrder(order) = event {
157                Some(order)
158            } else {
159                None
160            }
161        })
162        .collect();
163
164    //Table rows
165    let mut rows: Vec<Row> = Vec::new();
166
167    if orders_table.is_empty() {
168        table
169            .load_preset(UTF8_FULL)
170            .set_content_arrangement(ContentArrangement::Dynamic)
171            .set_width(160)
172            .set_header(vec![Cell::new("Sorry...")
173                .add_attribute(Attribute::Bold)
174                .set_alignment(CellAlignment::Center)]);
175
176        // Single row for error
177        let mut r = Row::new();
178
179        r.add_cell(
180            Cell::new("No offers found with requested parameters...")
181                .fg(Color::Red)
182                .set_alignment(CellAlignment::Center),
183        );
184
185        //Push single error row
186        rows.push(r);
187    } else {
188        table
189            .load_preset(UTF8_FULL)
190            .set_content_arrangement(ContentArrangement::Dynamic)
191            .set_width(160)
192            .set_header(vec![
193                Cell::new("Buy/Sell")
194                    .add_attribute(Attribute::Bold)
195                    .set_alignment(CellAlignment::Center),
196                Cell::new("Order Id")
197                    .add_attribute(Attribute::Bold)
198                    .set_alignment(CellAlignment::Center),
199                Cell::new("Status")
200                    .add_attribute(Attribute::Bold)
201                    .set_alignment(CellAlignment::Center),
202                Cell::new("Amount")
203                    .add_attribute(Attribute::Bold)
204                    .set_alignment(CellAlignment::Center),
205                Cell::new("Fiat Code")
206                    .add_attribute(Attribute::Bold)
207                    .set_alignment(CellAlignment::Center),
208                Cell::new("Fiat Amount")
209                    .add_attribute(Attribute::Bold)
210                    .set_alignment(CellAlignment::Center),
211                Cell::new("Payment method")
212                    .add_attribute(Attribute::Bold)
213                    .set_alignment(CellAlignment::Center),
214                Cell::new("Created")
215                    .add_attribute(Attribute::Bold)
216                    .set_alignment(CellAlignment::Center),
217            ]);
218
219        //Iterate to create table of orders
220        for single_order in orders_table.into_iter() {
221            let date = DateTime::from_timestamp(single_order.created_at.unwrap_or(0), 0);
222
223            let r = Row::from(vec![
224                if let Some(k) = single_order.kind {
225                    match k {
226                        mostro_core::order::Kind::Buy => Cell::new(k.to_string())
227                            .fg(Color::Green)
228                            .set_alignment(CellAlignment::Center),
229                        mostro_core::order::Kind::Sell => Cell::new(k.to_string())
230                            .fg(Color::Red)
231                            .set_alignment(CellAlignment::Center),
232                    }
233                } else {
234                    Cell::new("BUY/SELL").set_alignment(CellAlignment::Center)
235                },
236                Cell::new(
237                    single_order
238                        .id
239                        .map(|id| id.to_string())
240                        .unwrap_or_else(|| "N/A".to_string()),
241                )
242                .set_alignment(CellAlignment::Center),
243                Cell::new(
244                    single_order
245                        .status
246                        .unwrap_or(mostro_core::order::Status::Active)
247                        .to_string(),
248                )
249                .set_alignment(CellAlignment::Center),
250                if single_order.amount == 0 {
251                    Cell::new("market price").set_alignment(CellAlignment::Center)
252                } else {
253                    Cell::new(single_order.amount.to_string()).set_alignment(CellAlignment::Center)
254                },
255                Cell::new(single_order.fiat_code.to_string()).set_alignment(CellAlignment::Center),
256                // No range order print row
257                if single_order.min_amount.is_none() && single_order.max_amount.is_none() {
258                    Cell::new(single_order.fiat_amount.to_string())
259                        .set_alignment(CellAlignment::Center)
260                } else {
261                    let range_str = match (single_order.min_amount, single_order.max_amount) {
262                        (Some(min), Some(max)) => format!("{}-{}", min, max),
263                        (Some(min), None) => format!("{}-?", min),
264                        (None, Some(max)) => format!("?-{}", max),
265                        (None, None) => "?".to_string(),
266                    };
267                    Cell::new(range_str).set_alignment(CellAlignment::Center)
268                },
269                Cell::new(single_order.payment_method.to_string())
270                    .set_alignment(CellAlignment::Center),
271                Cell::new(
272                    date.map(|d| d.to_string())
273                        .unwrap_or_else(|| "Invalid date".to_string()),
274                ),
275            ]);
276            rows.push(r);
277        }
278    }
279
280    table.add_rows(rows);
281
282    Ok(table.to_string())
283}
284
285#[cfg(test)]
286mod tests {}