mostro_client/parser/
orders.rs

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