1use super::{
4 build_in_clause, json1_available, map_db_error, params_refs, parse_datetime_row, parse_enum,
5 parse_enum_row, parse_json_row, parse_uuid_opt_row, parse_uuid_row, uuid_params,
6 with_immediate_transaction,
7};
8use chrono::Utc;
9use r2d2::Pool;
10use r2d2_sqlite::SqliteConnectionManager;
11use rusqlite::OptionalExtension;
12use stateset_core::{
13 AddressType, BatchResult, CommerceError, CreateCustomer, CreateCustomerAddress, Customer,
14 CustomerAddress, CustomerFilter, CustomerId, CustomerRepository, CustomerStatus, Result,
15 UpdateCustomer, validate_batch_size, validate_email, validate_phone, validate_postal_code,
16 validate_required_text, validate_required_uuid,
17};
18use uuid::Uuid;
19
20#[derive(Debug)]
22pub struct SqliteCustomerRepository {
23 pool: Pool<SqliteConnectionManager>,
24}
25
26impl SqliteCustomerRepository {
27 #[must_use]
28 pub const fn new(pool: Pool<SqliteConnectionManager>) -> Self {
29 Self { pool }
30 }
31
32 fn conn(&self) -> Result<r2d2::PooledConnection<SqliteConnectionManager>> {
33 self.pool.get().map_err(|e| CommerceError::DatabaseError(e.to_string()))
34 }
35
36 fn row_to_customer(row: &rusqlite::Row<'_>) -> rusqlite::Result<Customer> {
37 let tags_json: String = row.get("tags")?;
38 let metadata_json: Option<String> = row.get("metadata")?;
39
40 Ok(Customer {
41 id: CustomerId::from(parse_uuid_row(&row.get::<_, String>("id")?, "customer", "id")?),
42 email: row.get("email")?,
43 first_name: row.get("first_name")?,
44 last_name: row.get("last_name")?,
45 phone: row.get("phone")?,
46 status: parse_enum_row(&row.get::<_, String>("status")?, "customer", "status")?,
47 accepts_marketing: row.get::<_, i32>("accepts_marketing")? != 0,
48 email_verified: row.get::<_, i32>("email_verified")? != 0,
49 tags: parse_json_row(&tags_json, "customer", "tags")?,
50 metadata: metadata_json
51 .map(|s| parse_json_row(&s, "customer", "metadata"))
52 .transpose()?,
53 default_shipping_address_id: parse_uuid_opt_row(
54 row.get::<_, Option<String>>("default_shipping_address_id")?,
55 "customer",
56 "default_shipping_address_id",
57 )?,
58 default_billing_address_id: parse_uuid_opt_row(
59 row.get::<_, Option<String>>("default_billing_address_id")?,
60 "customer",
61 "default_billing_address_id",
62 )?,
63 created_at: parse_datetime_row(
64 &row.get::<_, String>("created_at")?,
65 "customer",
66 "created_at",
67 )?,
68 updated_at: parse_datetime_row(
69 &row.get::<_, String>("updated_at")?,
70 "customer",
71 "updated_at",
72 )?,
73 })
74 }
75
76 fn row_to_address(row: &rusqlite::Row<'_>) -> rusqlite::Result<CustomerAddress> {
77 Ok(CustomerAddress {
78 id: parse_uuid_row(&row.get::<_, String>("id")?, "customer_address", "id")?,
79 customer_id: CustomerId::from(parse_uuid_row(
80 &row.get::<_, String>("customer_id")?,
81 "customer_address",
82 "customer_id",
83 )?),
84 address_type: parse_enum_row(
85 &row.get::<_, String>("address_type")?,
86 "customer_address",
87 "address_type",
88 )?,
89 first_name: row.get("first_name")?,
90 last_name: row.get("last_name")?,
91 company: row.get("company")?,
92 line1: row.get("line1")?,
93 line2: row.get("line2")?,
94 city: row.get("city")?,
95 state: row.get("state")?,
96 postal_code: row.get("postal_code")?,
97 country: row.get("country")?,
98 phone: row.get("phone")?,
99 is_default: row.get::<_, i32>("is_default")? != 0,
100 created_at: parse_datetime_row(
101 &row.get::<_, String>("created_at")?,
102 "customer_address",
103 "created_at",
104 )?,
105 updated_at: parse_datetime_row(
106 &row.get::<_, String>("updated_at")?,
107 "customer_address",
108 "updated_at",
109 )?,
110 })
111 }
112
113 fn validate_address_input(input: &CreateCustomerAddress) -> Result<()> {
114 validate_required_uuid("customer_address.customer_id", input.customer_id.into_uuid())?;
115 validate_required_text("customer_address.first_name", &input.first_name, 100)?;
116 validate_required_text("customer_address.last_name", &input.last_name, 100)?;
117 validate_required_text("customer_address.line1", &input.line1, 255)?;
118 validate_required_text("customer_address.city", &input.city, 255)?;
119 validate_postal_code(&input.postal_code)?;
120 validate_required_text("customer_address.country", &input.country, 64)?;
121
122 if let Some(line2) = &input.line2 {
123 validate_required_text("customer_address.line2", line2, 255)?;
124 }
125 if let Some(state) = &input.state {
126 validate_required_text("customer_address.state", state, 64)?;
127 }
128 if let Some(phone) = &input.phone {
129 validate_phone(phone)?;
130 }
131
132 Ok(())
133 }
134}
135
136impl CustomerRepository for SqliteCustomerRepository {
137 fn create(&self, input: CreateCustomer) -> Result<Customer> {
138 validate_email(&input.email)?;
140 validate_required_text("customer.first_name", &input.first_name, 100)?;
141 validate_required_text("customer.last_name", &input.last_name, 100)?;
142 if let Some(phone) = &input.phone {
143 validate_phone(phone)?;
144 }
145
146 let id = CustomerId::new();
147 let tags = input.tags.clone().unwrap_or_default();
148 let metadata = input.metadata.clone();
149 let email = input.email.clone();
150 let first_name = input.first_name.clone();
151 let last_name = input.last_name.clone();
152 let phone = input.phone.clone();
153 let accepts_marketing = input.accepts_marketing.unwrap_or(false);
154
155 with_immediate_transaction(&self.pool, |tx| {
156 let now = Utc::now();
157
158 let exists: bool = tx.query_row(
160 "SELECT EXISTS(SELECT 1 FROM customers WHERE email = ? LIMIT 1)",
161 [&email],
162 |row| row.get(0),
163 )?;
164
165 if exists {
166 return Err(rusqlite::Error::ToSqlConversionFailure(Box::new(
167 CommerceError::EmailAlreadyExists(email.clone()),
168 )));
169 }
170
171 let tags_json = serde_json::to_string(&tags).unwrap_or_default();
172 let metadata_json =
173 metadata.as_ref().map(|m| serde_json::to_string(m).unwrap_or_default());
174 let id_str = id.to_string();
175 let now_str = now.to_rfc3339();
176
177 tx.prepare_cached(
178 "INSERT INTO customers (id, email, first_name, last_name, phone, status,
179 accepts_marketing, email_verified, tags, metadata,
180 created_at, updated_at)
181 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
182 )?
183 .execute(rusqlite::params![
184 &id_str,
185 &email,
186 &first_name,
187 &last_name,
188 &phone,
189 "active",
190 i32::from(accepts_marketing),
191 0,
192 tags_json,
193 metadata_json,
194 &now_str,
195 &now_str,
196 ])?;
197
198 Ok(Customer {
200 id,
201 email: email.clone(),
202 first_name: first_name.clone(),
203 last_name: last_name.clone(),
204 phone: phone.clone(),
205 status: CustomerStatus::Active,
206 accepts_marketing,
207 email_verified: false,
208 tags: tags.clone(),
209 metadata: metadata.clone(),
210 default_shipping_address_id: None,
211 default_billing_address_id: None,
212 created_at: now,
213 updated_at: now,
214 })
215 })
216 }
217
218 fn get(&self, id: CustomerId) -> Result<Option<Customer>> {
219 let conn = self.conn()?;
220 let result = conn.query_row(
221 "SELECT * FROM customers WHERE id = ?",
222 [id.to_string()],
223 Self::row_to_customer,
224 );
225
226 match result {
227 Ok(customer) => Ok(Some(customer)),
228 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
229 Err(e) => Err(map_db_error(e)),
230 }
231 }
232
233 fn get_by_email(&self, email: &str) -> Result<Option<Customer>> {
234 let conn = self.conn()?;
235 let result = conn.query_row(
236 "SELECT * FROM customers WHERE email = ?",
237 [email],
238 Self::row_to_customer,
239 );
240
241 match result {
242 Ok(customer) => Ok(Some(customer)),
243 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
244 Err(e) => Err(map_db_error(e)),
245 }
246 }
247
248 fn update(&self, id: CustomerId, input: UpdateCustomer) -> Result<Customer> {
249 let conn = self.conn()?;
250 let now = Utc::now();
251 let current_version: i32 = conn
252 .query_row("SELECT version FROM customers WHERE id = ?", [id.to_string()], |row| {
253 row.get(0)
254 })
255 .map_err(|e| match e {
256 rusqlite::Error::QueryReturnedNoRows => {
257 CommerceError::CustomerNotFound(id.into_uuid())
258 }
259 e => map_db_error(e),
260 })?;
261
262 let mut updates = vec!["updated_at = ?"];
263 let mut params: Vec<Box<dyn rusqlite::ToSql>> = vec![Box::new(now.to_rfc3339())];
264
265 if let Some(email) = &input.email {
266 validate_email(email)?;
267 let existing_id: Option<String> = conn
268 .query_row("SELECT id FROM customers WHERE email = ?", [email], |row| row.get(0))
269 .optional()
270 .map_err(map_db_error)?;
271 if let Some(existing_id) = existing_id {
272 if existing_id != id.to_string() {
273 return Err(CommerceError::EmailAlreadyExists(email.clone()));
274 }
275 }
276 updates.push("email = ?");
277 params.push(Box::new(email.clone()));
278 }
279 if let Some(first_name) = &input.first_name {
280 validate_required_text("customer.first_name", first_name, 100)?;
281 updates.push("first_name = ?");
282 params.push(Box::new(first_name.clone()));
283 }
284 if let Some(last_name) = &input.last_name {
285 validate_required_text("customer.last_name", last_name, 100)?;
286 updates.push("last_name = ?");
287 params.push(Box::new(last_name.clone()));
288 }
289 if let Some(phone) = &input.phone {
290 validate_phone(phone)?;
291 updates.push("phone = ?");
292 params.push(Box::new(phone.clone()));
293 }
294 if let Some(status) = &input.status {
295 updates.push("status = ?");
296 params.push(Box::new(status.to_string()));
297 }
298 if let Some(accepts_marketing) = &input.accepts_marketing {
299 updates.push("accepts_marketing = ?");
300 params.push(Box::new(i32::from(*accepts_marketing)));
301 }
302 if let Some(tags) = &input.tags {
303 updates.push("tags = ?");
304 params.push(Box::new(serde_json::to_string(tags).unwrap_or_default()));
305 }
306 if let Some(metadata) = &input.metadata {
307 updates.push("metadata = ?");
308 params.push(Box::new(serde_json::to_string(metadata).unwrap_or_default()));
309 }
310
311 updates.push("version = version + 1");
312 params.push(Box::new(id.to_string()));
313 params.push(Box::new(current_version));
314
315 let sql =
316 format!("UPDATE customers SET {} WHERE id = ? AND version = ?", updates.join(", "));
317 let params_refs: Vec<&dyn rusqlite::ToSql> =
318 params.iter().map(std::convert::AsRef::as_ref).collect();
319
320 let rows_affected = conn.execute(&sql, params_refs.as_slice()).map_err(map_db_error)?;
321 if rows_affected == 0 {
322 return Err(CommerceError::VersionConflict {
323 entity: "customer".to_string(),
324 id: id.to_string(),
325 expected_version: current_version,
326 });
327 }
328
329 self.get(id)?.ok_or(CommerceError::CustomerNotFound(id.into_uuid()))
330 }
331
332 fn list(&self, filter: CustomerFilter) -> Result<Vec<Customer>> {
333 let conn = self.conn()?;
334 let use_json = json1_available(&conn);
335 let mut sql = "SELECT * FROM customers WHERE 1=1".to_string();
336 let mut params: Vec<Box<dyn rusqlite::ToSql>> = vec![];
337
338 if let Some(email) = &filter.email {
339 sql.push_str(" AND email LIKE ?");
340 params.push(Box::new(format!("%{email}%")));
341 }
342 if let Some(status) = &filter.status {
343 sql.push_str(" AND status = ?");
344 params.push(Box::new(status.to_string()));
345 } else {
346 sql.push_str(" AND status != 'deleted'");
347 }
348 if let Some(tag) = &filter.tag {
349 if use_json {
350 sql.push_str(" AND EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)");
351 params.push(Box::new(tag.clone()));
352 } else {
353 sql.push_str(" AND tags LIKE ?");
354 params.push(Box::new(format!("%\"{tag}\"%")));
355 }
356 }
357 if let Some(accepts_marketing) = &filter.accepts_marketing {
358 sql.push_str(" AND accepts_marketing = ?");
359 params.push(Box::new(i32::from(*accepts_marketing)));
360 }
361
362 if let Some((cursor_date, cursor_id)) = &filter.after_cursor {
364 sql.push_str(" AND (created_at < ? OR (created_at = ? AND id < ?))");
365 params.push(Box::new(cursor_date.clone()));
366 params.push(Box::new(cursor_date.clone()));
367 params.push(Box::new(cursor_id.clone()));
368 }
369
370 sql.push_str(" ORDER BY created_at DESC, id DESC");
371
372 let offset = if filter.after_cursor.is_none() { filter.offset } else { None };
376 crate::sqlite::append_limit_offset(&mut sql, filter.limit, offset);
377
378 let params_refs: Vec<&dyn rusqlite::ToSql> =
379 params.iter().map(std::convert::AsRef::as_ref).collect();
380 let mut stmt = conn.prepare(&sql).map_err(map_db_error)?;
381
382 let customers = stmt
383 .query_map(params_refs.as_slice(), Self::row_to_customer)
384 .map_err(map_db_error)?
385 .collect::<rusqlite::Result<Vec<_>>>()
386 .map_err(map_db_error)?;
387
388 Ok(customers)
389 }
390
391 fn delete(&self, id: CustomerId) -> Result<()> {
392 let conn = self.conn()?;
393 conn.execute(
394 "UPDATE customers SET status = ?, updated_at = ? WHERE id = ?",
395 rusqlite::params!["deleted", Utc::now().to_rfc3339(), id.to_string()],
396 )
397 .map_err(map_db_error)?;
398 Ok(())
399 }
400
401 fn add_address(&self, input: CreateCustomerAddress) -> Result<CustomerAddress> {
402 Self::validate_address_input(&input)?;
403
404 let mut conn = self.conn()?;
405 let id = Uuid::new_v4();
406 let now = Utc::now();
407 let address_type = input.address_type.unwrap_or_default();
408 let is_default = input.is_default.unwrap_or(false);
409
410 let tx = super::begin_immediate(&mut conn).map_err(map_db_error)?;
411
412 tx.execute(
413 "INSERT INTO customer_addresses (id, customer_id, address_type, first_name, last_name,
414 company, line1, line2, city, state, postal_code,
415 country, phone, is_default, created_at, updated_at)
416 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
417 rusqlite::params![
418 id.to_string(),
419 input.customer_id.to_string(),
420 address_type.to_string(),
421 input.first_name,
422 input.last_name,
423 input.company,
424 input.line1,
425 input.line2,
426 input.city,
427 input.state,
428 input.postal_code,
429 input.country,
430 input.phone,
431 i32::from(is_default),
432 now.to_rfc3339(),
433 now.to_rfc3339(),
434 ],
435 )
436 .map_err(map_db_error)?;
437
438 if is_default {
439 let clear_sql = match address_type {
440 AddressType::Shipping => {
441 "UPDATE customer_addresses SET is_default = 0 WHERE customer_id = ? AND (address_type = 'shipping' OR address_type = 'both')"
442 }
443 AddressType::Billing => {
444 "UPDATE customer_addresses SET is_default = 0 WHERE customer_id = ? AND (address_type = 'billing' OR address_type = 'both')"
445 }
446 AddressType::Both => {
447 "UPDATE customer_addresses SET is_default = 0 WHERE customer_id = ?"
448 }
449 _ => "UPDATE customer_addresses SET is_default = 0 WHERE customer_id = ?",
450 };
451
452 tx.execute(clear_sql, [input.customer_id.to_string()]).map_err(map_db_error)?;
454
455 tx.execute(
457 "UPDATE customer_addresses SET is_default = 1 WHERE id = ?",
458 [id.to_string()],
459 )
460 .map_err(map_db_error)?;
461
462 match address_type {
463 AddressType::Shipping => {
464 tx.execute(
465 "UPDATE customers SET default_shipping_address_id = ?, updated_at = ? WHERE id = ?",
466 rusqlite::params![id.to_string(), now.to_rfc3339(), input.customer_id.to_string()],
467 )
468 .map_err(map_db_error)?;
469 }
470 AddressType::Billing => {
471 tx.execute(
472 "UPDATE customers SET default_billing_address_id = ?, updated_at = ? WHERE id = ?",
473 rusqlite::params![id.to_string(), now.to_rfc3339(), input.customer_id.to_string()],
474 )
475 .map_err(map_db_error)?;
476 }
477 AddressType::Both | _ => {
478 tx.execute(
479 "UPDATE customers SET default_shipping_address_id = ?, default_billing_address_id = ?, updated_at = ? WHERE id = ?",
480 rusqlite::params![id.to_string(), id.to_string(), now.to_rfc3339(), input.customer_id.to_string()],
481 )
482 .map_err(map_db_error)?;
483 }
484 }
485 }
486
487 let addr = tx
488 .query_row(
489 "SELECT * FROM customer_addresses WHERE id = ?",
490 [id.to_string()],
491 Self::row_to_address,
492 )
493 .map_err(map_db_error)?;
494
495 tx.commit().map_err(map_db_error)?;
496 Ok(addr)
497 }
498
499 fn get_addresses(&self, customer_id: CustomerId) -> Result<Vec<CustomerAddress>> {
500 let conn = self.conn()?;
501 let mut stmt = conn
502 .prepare("SELECT * FROM customer_addresses WHERE customer_id = ?")
503 .map_err(map_db_error)?;
504
505 let addresses = stmt
506 .query_map([customer_id.to_string()], Self::row_to_address)
507 .map_err(map_db_error)?
508 .collect::<rusqlite::Result<Vec<_>>>()
509 .map_err(map_db_error)?;
510
511 Ok(addresses)
512 }
513
514 fn update_address(
515 &self,
516 address_id: Uuid,
517 input: CreateCustomerAddress,
518 ) -> Result<CustomerAddress> {
519 Self::validate_address_input(&input)?;
520
521 let conn = self.conn()?;
522 let now = Utc::now();
523
524 let exists: i32 = conn
525 .query_row(
526 "SELECT COUNT(*) FROM customer_addresses WHERE id = ? AND customer_id = ?",
527 rusqlite::params![address_id.to_string(), input.customer_id.to_string()],
528 |row| row.get(0),
529 )
530 .map_err(map_db_error)?;
531 if exists == 0 {
532 return Err(CommerceError::ValidationError(
533 "Address does not belong to customer".into(),
534 ));
535 }
536
537 conn.execute(
538 "UPDATE customer_addresses SET first_name = ?, last_name = ?, company = ?,
539 line1 = ?, line2 = ?, city = ?, state = ?, postal_code = ?,
540 country = ?, phone = ?, updated_at = ? WHERE id = ?",
541 rusqlite::params![
542 input.first_name,
543 input.last_name,
544 input.company,
545 input.line1,
546 input.line2,
547 input.city,
548 input.state,
549 input.postal_code,
550 input.country,
551 input.phone,
552 now.to_rfc3339(),
553 address_id.to_string(),
554 ],
555 )
556 .map_err(map_db_error)?;
557
558 let addr = conn
559 .query_row(
560 "SELECT * FROM customer_addresses WHERE id = ?",
561 [address_id.to_string()],
562 Self::row_to_address,
563 )
564 .map_err(map_db_error)?;
565
566 Ok(addr)
567 }
568
569 fn delete_address(&self, address_id: Uuid) -> Result<()> {
570 let mut conn = self.conn()?;
571 let tx = super::begin_immediate(&mut conn).map_err(map_db_error)?;
572 let now = Utc::now();
573
574 let row: Option<(String, String, i32)> = tx
575 .query_row(
576 "SELECT customer_id, address_type, is_default FROM customer_addresses WHERE id = ?",
577 [address_id.to_string()],
578 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
579 )
580 .optional()
581 .map_err(map_db_error)?;
582
583 let (customer_id, address_type, is_default) = match row {
584 Some(values) => values,
585 None => return Err(CommerceError::NotFound),
586 };
587
588 if is_default != 0 {
589 let parsed_type: AddressType =
590 parse_enum(&address_type, "customer_address", "address_type")?;
591 match parsed_type {
592 AddressType::Shipping => {
593 tx.execute(
594 "UPDATE customers SET default_shipping_address_id = NULL, updated_at = ? WHERE id = ?",
595 rusqlite::params![now.to_rfc3339(), customer_id],
596 )
597 .map_err(map_db_error)?;
598 }
599 AddressType::Billing => {
600 tx.execute(
601 "UPDATE customers SET default_billing_address_id = NULL, updated_at = ? WHERE id = ?",
602 rusqlite::params![now.to_rfc3339(), customer_id],
603 )
604 .map_err(map_db_error)?;
605 }
606 AddressType::Both | _ => {
607 tx.execute(
608 "UPDATE customers SET default_shipping_address_id = NULL, default_billing_address_id = NULL, updated_at = ? WHERE id = ?",
609 rusqlite::params![now.to_rfc3339(), customer_id],
610 )
611 .map_err(map_db_error)?;
612 }
613 }
614 }
615
616 tx.execute("DELETE FROM customer_addresses WHERE id = ?", [address_id.to_string()])
617 .map_err(map_db_error)?;
618 tx.commit().map_err(map_db_error)?;
619 Ok(())
620 }
621
622 fn set_default_address(
623 &self,
624 customer_id: CustomerId,
625 address_id: Uuid,
626 address_type: AddressType,
627 ) -> Result<()> {
628 let mut conn = self.conn()?;
629 let tx = super::begin_immediate(&mut conn).map_err(map_db_error)?;
630 let now = Utc::now();
631
632 let owner: Option<String> = tx
633 .query_row(
634 "SELECT customer_id FROM customer_addresses WHERE id = ?",
635 [address_id.to_string()],
636 |row| row.get(0),
637 )
638 .optional()
639 .map_err(map_db_error)?;
640 match owner {
641 Some(owner_id) if owner_id != customer_id.to_string() => {
642 return Err(CommerceError::ValidationError(
643 "Address does not belong to customer".into(),
644 ));
645 }
646 None => {
647 return Err(CommerceError::NotFound);
648 }
649 _ => {}
650 }
651
652 let clear_sql = match address_type {
654 AddressType::Shipping => {
655 "UPDATE customer_addresses SET is_default = 0 WHERE customer_id = ? AND (address_type = 'shipping' OR address_type = 'both')"
656 }
657 AddressType::Billing => {
658 "UPDATE customer_addresses SET is_default = 0 WHERE customer_id = ? AND (address_type = 'billing' OR address_type = 'both')"
659 }
660 AddressType::Both | _ => {
661 "UPDATE customer_addresses SET is_default = 0 WHERE customer_id = ?"
662 }
663 };
664 tx.execute(clear_sql, [customer_id.to_string()]).map_err(map_db_error)?;
665
666 tx.execute(
668 "UPDATE customer_addresses SET is_default = 1 WHERE id = ?",
669 [address_id.to_string()],
670 )
671 .map_err(map_db_error)?;
672
673 match address_type {
675 AddressType::Shipping => {
676 tx.execute(
677 "UPDATE customers SET default_shipping_address_id = ?, updated_at = ? WHERE id = ?",
678 rusqlite::params![address_id.to_string(), now.to_rfc3339(), customer_id.to_string()],
679 )
680 .map_err(map_db_error)?;
681 }
682 AddressType::Billing => {
683 tx.execute(
684 "UPDATE customers SET default_billing_address_id = ?, updated_at = ? WHERE id = ?",
685 rusqlite::params![address_id.to_string(), now.to_rfc3339(), customer_id.to_string()],
686 )
687 .map_err(map_db_error)?;
688 }
689 AddressType::Both | _ => {
690 tx.execute(
691 "UPDATE customers SET default_shipping_address_id = ?, default_billing_address_id = ?, updated_at = ? WHERE id = ?",
692 rusqlite::params![address_id.to_string(), address_id.to_string(), now.to_rfc3339(), customer_id.to_string()],
693 )
694 .map_err(map_db_error)?;
695 }
696 }
697
698 tx.commit().map_err(map_db_error)?;
699
700 Ok(())
701 }
702
703 fn count(&self, filter: CustomerFilter) -> Result<u64> {
704 let conn = self.conn()?;
705 let use_json = json1_available(&conn);
706 let mut sql = "SELECT COUNT(*) FROM customers WHERE 1=1".to_string();
707 let mut params: Vec<Box<dyn rusqlite::ToSql>> = vec![];
708
709 if let Some(email) = &filter.email {
710 sql.push_str(" AND email LIKE ?");
711 params.push(Box::new(format!("%{email}%")));
712 }
713 if let Some(status) = &filter.status {
714 sql.push_str(" AND status = ?");
715 params.push(Box::new(status.to_string()));
716 } else {
717 sql.push_str(" AND status != 'deleted'");
718 }
719 if let Some(tag) = &filter.tag {
720 if use_json {
721 sql.push_str(" AND EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)");
722 params.push(Box::new(tag.clone()));
723 } else {
724 sql.push_str(" AND tags LIKE ?");
725 params.push(Box::new(format!("%\"{tag}\"%")));
726 }
727 }
728 if let Some(accepts_marketing) = &filter.accepts_marketing {
729 sql.push_str(" AND accepts_marketing = ?");
730 params.push(Box::new(i32::from(*accepts_marketing)));
731 }
732
733 let params_refs: Vec<&dyn rusqlite::ToSql> =
734 params.iter().map(std::convert::AsRef::as_ref).collect();
735 let count: i64 =
736 conn.query_row(&sql, params_refs.as_slice(), |row| row.get(0)).map_err(map_db_error)?;
737
738 Ok(count as u64)
739 }
740
741 fn create_batch(&self, inputs: Vec<CreateCustomer>) -> Result<BatchResult<Customer>> {
744 validate_batch_size(&inputs)?;
745 let mut result = BatchResult::with_capacity(inputs.len());
746
747 for (index, input) in inputs.into_iter().enumerate() {
748 match self.create(input) {
749 Ok(customer) => result.record_success(customer),
750 Err(e) => result.record_failure(index, None, &e),
751 }
752 }
753
754 Ok(result)
755 }
756
757 fn create_batch_atomic(&self, inputs: Vec<CreateCustomer>) -> Result<Vec<Customer>> {
758 validate_batch_size(&inputs)?;
759 if inputs.is_empty() {
760 return Ok(vec![]);
761 }
762
763 let mut conn = self.conn()?;
764 let tx = super::begin_immediate(&mut conn).map_err(map_db_error)?;
765 let mut results = Vec::with_capacity(inputs.len());
766
767 for input in inputs {
768 let id = CustomerId::new();
769 let now = Utc::now();
770 let tags = input.tags.clone().unwrap_or_default();
771 let metadata = input.metadata.clone();
772 let email = input.email.clone();
773 let first_name = input.first_name.clone();
774 let last_name = input.last_name.clone();
775 let phone = input.phone.clone();
776 let accepts_marketing = input.accepts_marketing.unwrap_or(false);
777
778 let exists: i32 = tx
780 .query_row(
781 "SELECT COUNT(*) FROM customers WHERE email = ?",
782 [&input.email],
783 |row| row.get(0),
784 )
785 .map_err(map_db_error)?;
786
787 if exists > 0 {
788 return Err(CommerceError::EmailAlreadyExists(input.email));
789 }
790
791 let tags_json = serde_json::to_string(&tags).unwrap_or_default();
792 let metadata_json =
793 metadata.as_ref().map(|m| serde_json::to_string(m).unwrap_or_default());
794
795 tx.execute(
796 "INSERT INTO customers (id, email, first_name, last_name, phone, status,
797 accepts_marketing, email_verified, tags, metadata,
798 created_at, updated_at)
799 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
800 rusqlite::params![
801 id.to_string(),
802 &email,
803 &first_name,
804 &last_name,
805 &phone,
806 "active",
807 i32::from(accepts_marketing),
808 0,
809 tags_json,
810 metadata_json,
811 now.to_rfc3339(),
812 now.to_rfc3339(),
813 ],
814 )
815 .map_err(map_db_error)?;
816
817 results.push(Customer {
818 id,
819 email,
820 first_name,
821 last_name,
822 phone,
823 status: CustomerStatus::Active,
824 accepts_marketing,
825 email_verified: false,
826 tags,
827 metadata,
828 default_shipping_address_id: None,
829 default_billing_address_id: None,
830 created_at: now,
831 updated_at: now,
832 });
833 }
834
835 tx.commit().map_err(map_db_error)?;
836 Ok(results)
837 }
838
839 fn update_batch(
840 &self,
841 updates: Vec<(CustomerId, UpdateCustomer)>,
842 ) -> Result<BatchResult<Customer>> {
843 validate_batch_size(&updates)?;
844 let mut result = BatchResult::with_capacity(updates.len());
845
846 for (index, (id, input)) in updates.into_iter().enumerate() {
847 match self.update(id, input) {
848 Ok(customer) => result.record_success(customer),
849 Err(e) => result.record_failure(index, Some(id.to_string()), &e),
850 }
851 }
852
853 Ok(result)
854 }
855
856 fn update_batch_atomic(
857 &self,
858 updates: Vec<(CustomerId, UpdateCustomer)>,
859 ) -> Result<Vec<Customer>> {
860 validate_batch_size(&updates)?;
861 if updates.is_empty() {
862 return Ok(vec![]);
863 }
864
865 let mut conn = self.conn()?;
866 let tx = super::begin_immediate(&mut conn).map_err(map_db_error)?;
867 let mut results = Vec::with_capacity(updates.len());
868
869 for (id, input) in updates {
870 let now = Utc::now();
871 let current_version: i32 = tx
872 .query_row("SELECT version FROM customers WHERE id = ?", [id.to_string()], |row| {
873 row.get(0)
874 })
875 .map_err(|e| match e {
876 rusqlite::Error::QueryReturnedNoRows => {
877 CommerceError::CustomerNotFound(id.into_uuid())
878 }
879 e => map_db_error(e),
880 })?;
881
882 let mut update_parts = vec!["updated_at = ?"];
883 let mut params: Vec<Box<dyn rusqlite::ToSql>> = vec![Box::new(now.to_rfc3339())];
884
885 if let Some(email) = &input.email {
886 validate_email(email)?;
887 let existing_id: Option<String> = tx
888 .query_row("SELECT id FROM customers WHERE email = ?", [email], |row| {
889 row.get(0)
890 })
891 .optional()
892 .map_err(map_db_error)?;
893 if let Some(existing_id) = existing_id {
894 if existing_id != id.to_string() {
895 return Err(CommerceError::EmailAlreadyExists(email.clone()));
896 }
897 }
898 update_parts.push("email = ?");
899 params.push(Box::new(email.clone()));
900 }
901 if let Some(first_name) = &input.first_name {
902 validate_required_text("customer.first_name", first_name, 100)?;
903 update_parts.push("first_name = ?");
904 params.push(Box::new(first_name.clone()));
905 }
906 if let Some(last_name) = &input.last_name {
907 validate_required_text("customer.last_name", last_name, 100)?;
908 update_parts.push("last_name = ?");
909 params.push(Box::new(last_name.clone()));
910 }
911 if let Some(phone) = &input.phone {
912 validate_phone(phone)?;
913 update_parts.push("phone = ?");
914 params.push(Box::new(phone.clone()));
915 }
916 if let Some(status) = &input.status {
917 update_parts.push("status = ?");
918 params.push(Box::new(status.to_string()));
919 }
920 if let Some(accepts_marketing) = &input.accepts_marketing {
921 update_parts.push("accepts_marketing = ?");
922 params.push(Box::new(i32::from(*accepts_marketing)));
923 }
924 if let Some(tags) = &input.tags {
925 update_parts.push("tags = ?");
926 params.push(Box::new(serde_json::to_string(tags).unwrap_or_default()));
927 }
928 if let Some(metadata) = &input.metadata {
929 update_parts.push("metadata = ?");
930 params.push(Box::new(serde_json::to_string(metadata).unwrap_or_default()));
931 }
932
933 update_parts.push("version = version + 1");
934 params.push(Box::new(id.to_string()));
935 params.push(Box::new(current_version));
936
937 let sql = format!(
938 "UPDATE customers SET {} WHERE id = ? AND version = ?",
939 update_parts.join(", ")
940 );
941
942 let params_refs: Vec<&dyn rusqlite::ToSql> =
943 params.iter().map(std::convert::AsRef::as_ref).collect();
944 let rows_affected = tx.execute(&sql, params_refs.as_slice()).map_err(map_db_error)?;
945 if rows_affected == 0 {
946 return Err(CommerceError::VersionConflict {
947 entity: "customer".to_string(),
948 id: id.to_string(),
949 expected_version: current_version,
950 });
951 }
952
953 let customer = tx
954 .query_row(
955 "SELECT * FROM customers WHERE id = ?",
956 [id.to_string()],
957 Self::row_to_customer,
958 )
959 .map_err(map_db_error)?;
960
961 results.push(customer);
962 }
963
964 tx.commit().map_err(map_db_error)?;
965 Ok(results)
966 }
967
968 fn delete_batch(&self, ids: Vec<CustomerId>) -> Result<BatchResult<CustomerId>> {
969 validate_batch_size(&ids)?;
970 let mut result = BatchResult::with_capacity(ids.len());
971
972 for (index, id) in ids.into_iter().enumerate() {
973 match self.delete(id) {
974 Ok(()) => result.record_success(id),
975 Err(e) => result.record_failure(index, Some(id.to_string()), &e),
976 }
977 }
978
979 Ok(result)
980 }
981
982 fn delete_batch_atomic(&self, ids: Vec<CustomerId>) -> Result<()> {
983 validate_batch_size(&ids)?;
984 if ids.is_empty() {
985 return Ok(());
986 }
987
988 let mut conn = self.conn()?;
989 let tx = super::begin_immediate(&mut conn).map_err(map_db_error)?;
990
991 let placeholders = build_in_clause(ids.len());
992
993 let now = Utc::now();
995 let sql = format!(
996 "UPDATE customers SET status = 'deleted', updated_at = ? WHERE id IN ({placeholders})"
997 );
998
999 let mut all_params: Vec<Box<dyn rusqlite::ToSql>> = vec![Box::new(now.to_rfc3339())];
1000 for id in &ids {
1001 all_params.push(Box::new(id.to_string()));
1002 }
1003 let all_params_refs: Vec<&dyn rusqlite::ToSql> =
1004 all_params.iter().map(std::convert::AsRef::as_ref).collect();
1005
1006 tx.execute(&sql, all_params_refs.as_slice()).map_err(map_db_error)?;
1007
1008 tx.commit().map_err(map_db_error)?;
1009 Ok(())
1010 }
1011
1012 fn get_batch(&self, ids: Vec<CustomerId>) -> Result<Vec<Customer>> {
1013 validate_batch_size(&ids)?;
1014 if ids.is_empty() {
1015 return Ok(vec![]);
1016 }
1017
1018 let conn = self.conn()?;
1019 let placeholders = build_in_clause(ids.len());
1020 let sql = format!("SELECT * FROM customers WHERE id IN ({placeholders})");
1021
1022 let raw_ids: Vec<Uuid> = ids.iter().map(|id| id.into_uuid()).collect();
1023 let params = uuid_params(&raw_ids);
1024 let params_refs = params_refs(¶ms);
1025
1026 let mut stmt = conn.prepare(&sql).map_err(map_db_error)?;
1027 let customers = stmt
1028 .query_map(params_refs.as_slice(), Self::row_to_customer)
1029 .map_err(map_db_error)?
1030 .collect::<rusqlite::Result<Vec<_>>>()
1031 .map_err(map_db_error)?;
1032
1033 Ok(customers)
1034 }
1035}