Skip to main content

tap_cli/commands/
customer.rs

1use crate::error::{Error, Result};
2use crate::output::{print_success, OutputFormat};
3use crate::tap_integration::TapIntegration;
4use clap::Subcommand;
5use serde::Serialize;
6use tap_node::customer::CustomerManager;
7use tap_node::storage::models::{Customer, SchemaType};
8
9#[derive(Subcommand, Debug)]
10pub enum CustomerCommands {
11    /// List customers
12    List {
13        /// Agent DID for storage lookup
14        #[arg(long)]
15        agent_did: Option<String>,
16        /// Maximum results
17        #[arg(long, default_value = "50")]
18        limit: u32,
19        /// Offset for pagination
20        #[arg(long, default_value = "0")]
21        offset: u32,
22    },
23    /// Create a new customer record
24    Create {
25        /// Customer identifier (DID or CAIP-10 address)
26        #[arg(long)]
27        customer_id: String,
28        /// Customer profile as JSON
29        #[arg(long)]
30        profile: String,
31        /// Agent DID for storage lookup
32        #[arg(long)]
33        agent_did: Option<String>,
34    },
35    /// View customer details
36    Details {
37        /// Customer identifier
38        #[arg(long)]
39        customer_id: String,
40        /// Agent DID for storage lookup
41        #[arg(long)]
42        agent_did: Option<String>,
43    },
44    /// Update a customer profile
45    Update {
46        /// Customer identifier
47        #[arg(long)]
48        customer_id: String,
49        /// Updated profile as JSON
50        #[arg(long)]
51        profile: String,
52        /// Agent DID for storage lookup
53        #[arg(long)]
54        agent_did: Option<String>,
55    },
56    /// Generate IVMS101 data for a customer
57    Ivms101 {
58        /// Customer identifier
59        #[arg(long)]
60        customer_id: String,
61        /// Agent DID for storage lookup
62        #[arg(long)]
63        agent_did: Option<String>,
64    },
65}
66
67#[derive(Debug, Serialize)]
68struct CustomerInfo {
69    id: String,
70    display_name: Option<String>,
71    schema_type: String,
72    address_country: Option<String>,
73    created_at: String,
74    updated_at: String,
75}
76
77#[derive(Debug, Serialize)]
78struct CustomerListResponse {
79    customers: Vec<CustomerInfo>,
80    total: usize,
81}
82
83pub async fn handle(
84    cmd: &CustomerCommands,
85    format: OutputFormat,
86    default_agent_did: &str,
87    tap_integration: &TapIntegration,
88) -> Result<()> {
89    match cmd {
90        CustomerCommands::List {
91            agent_did,
92            limit,
93            offset,
94        } => {
95            let effective_did = agent_did.as_deref().unwrap_or(default_agent_did);
96            let storage = tap_integration.storage_for_agent(effective_did).await?;
97            let customers = storage
98                .list_customers(effective_did, *limit, *offset)
99                .await?;
100
101            let customer_infos: Vec<CustomerInfo> = customers
102                .iter()
103                .map(|c| CustomerInfo {
104                    id: c.id.clone(),
105                    display_name: c.display_name.clone(),
106                    schema_type: format!("{:?}", c.schema_type),
107                    address_country: c.address_country.clone(),
108                    created_at: c.created_at.clone(),
109                    updated_at: c.updated_at.clone(),
110                })
111                .collect();
112
113            let response = CustomerListResponse {
114                total: customer_infos.len(),
115                customers: customer_infos,
116            };
117            print_success(format, &response);
118            Ok(())
119        }
120        CustomerCommands::Create {
121            customer_id,
122            profile,
123            agent_did,
124        } => {
125            let effective_did = agent_did.as_deref().unwrap_or(default_agent_did);
126            let storage = tap_integration.storage_for_agent(effective_did).await?;
127
128            let profile_json: serde_json::Value = serde_json::from_str(profile)
129                .map_err(|e| Error::invalid_parameter(format!("Invalid profile JSON: {}", e)))?;
130
131            let schema_type =
132                if profile_json.get("@type").and_then(|v| v.as_str()) == Some("Organization") {
133                    SchemaType::Organization
134                } else {
135                    SchemaType::Person
136                };
137
138            let customer = Customer {
139                id: customer_id.clone(),
140                agent_did: effective_did.to_string(),
141                schema_type,
142                given_name: profile_json
143                    .get("givenName")
144                    .and_then(|v| v.as_str())
145                    .map(String::from),
146                family_name: profile_json
147                    .get("familyName")
148                    .and_then(|v| v.as_str())
149                    .map(String::from),
150                display_name: profile_json
151                    .get("name")
152                    .and_then(|v| v.as_str())
153                    .map(String::from),
154                legal_name: profile_json
155                    .get("legalName")
156                    .and_then(|v| v.as_str())
157                    .map(String::from),
158                lei_code: profile_json
159                    .get("leiCode")
160                    .and_then(|v| v.as_str())
161                    .map(String::from),
162                mcc_code: profile_json
163                    .get("mccCode")
164                    .and_then(|v| v.as_str())
165                    .map(String::from),
166                address_country: profile_json
167                    .get("addressCountry")
168                    .and_then(|v| v.as_str())
169                    .map(String::from),
170                address_locality: profile_json
171                    .get("addressLocality")
172                    .and_then(|v| v.as_str())
173                    .map(String::from),
174                postal_code: profile_json
175                    .get("postalCode")
176                    .and_then(|v| v.as_str())
177                    .map(String::from),
178                street_address: profile_json
179                    .get("streetAddress")
180                    .and_then(|v| v.as_str())
181                    .map(String::from),
182                profile: profile_json,
183                ivms101_data: None,
184                verified_at: None,
185                created_at: chrono::Utc::now().to_rfc3339(),
186                updated_at: chrono::Utc::now().to_rfc3339(),
187            };
188
189            storage
190                .upsert_customer(&customer)
191                .await
192                .map_err(|e| Error::command_failed(format!("Failed to create customer: {}", e)))?;
193
194            #[derive(Serialize)]
195            struct Created {
196                customer_id: String,
197                status: String,
198            }
199            let response = Created {
200                customer_id: customer_id.clone(),
201                status: "created".to_string(),
202            };
203            print_success(format, &response);
204            Ok(())
205        }
206        CustomerCommands::Details {
207            customer_id,
208            agent_did,
209        } => {
210            let effective_did = agent_did.as_deref().unwrap_or(default_agent_did);
211            let storage = tap_integration.storage_for_agent(effective_did).await?;
212            let customer = storage.get_customer(customer_id).await?;
213
214            match customer {
215                Some(c) => {
216                    print_success(format, &c);
217                    Ok(())
218                }
219                None => Err(Error::command_failed(format!(
220                    "Customer '{}' not found",
221                    customer_id
222                ))),
223            }
224        }
225        CustomerCommands::Update {
226            customer_id,
227            profile,
228            agent_did,
229        } => {
230            let effective_did = agent_did.as_deref().unwrap_or(default_agent_did);
231            let storage = tap_integration.storage_for_agent(effective_did).await?;
232
233            let profile_json: serde_json::Value = serde_json::from_str(profile)
234                .map_err(|e| Error::invalid_parameter(format!("Invalid profile JSON: {}", e)))?;
235
236            // Get existing customer to preserve fields
237            let existing = storage.get_customer(customer_id).await?.ok_or_else(|| {
238                Error::command_failed(format!("Customer '{}' not found", customer_id))
239            })?;
240
241            let customer = Customer {
242                profile: profile_json,
243                updated_at: chrono::Utc::now().to_rfc3339(),
244                ..existing
245            };
246
247            storage
248                .upsert_customer(&customer)
249                .await
250                .map_err(|e| Error::command_failed(format!("Failed to update customer: {}", e)))?;
251
252            #[derive(Serialize)]
253            struct Updated {
254                customer_id: String,
255                status: String,
256            }
257            let response = Updated {
258                customer_id: customer_id.clone(),
259                status: "updated".to_string(),
260            };
261            print_success(format, &response);
262            Ok(())
263        }
264        CustomerCommands::Ivms101 {
265            customer_id,
266            agent_did,
267        } => {
268            let effective_did = agent_did.as_deref().unwrap_or(default_agent_did);
269            let storage = tap_integration.storage_for_agent(effective_did).await?;
270            let customer_manager = CustomerManager::new(storage);
271
272            let ivms_data = customer_manager
273                .generate_ivms101_data(customer_id)
274                .await
275                .map_err(|e| {
276                    Error::command_failed(format!("Failed to generate IVMS101 data: {}", e))
277                })?;
278            print_success(format, &ivms_data);
279            Ok(())
280        }
281    }
282}