2024-12-24 21:13:47 -03:00
|
|
|
use gb_core::{
|
|
|
|
Result, Error,
|
|
|
|
models::{Customer, CustomerStatus, SubscriptionTier},
|
|
|
|
};
|
2024-12-25 16:25:09 -03:00
|
|
|
use sqlx::{PgPool, Row, postgres::PgRow};
|
2024-12-24 13:05:54 -03:00
|
|
|
use std::sync::Arc;
|
2024-12-22 20:56:52 -03:00
|
|
|
use uuid::Uuid;
|
2024-12-25 16:25:09 -03:00
|
|
|
use chrono::{DateTime, Utc};
|
2024-12-22 20:56:52 -03:00
|
|
|
|
2024-12-25 16:25:09 -03:00
|
|
|
#[async_trait::async_trait]
|
|
|
|
pub trait CustomerRepository: Send + Sync {
|
|
|
|
async fn create(&self, customer: Customer) -> Result<Customer>;
|
|
|
|
async fn get_customer_by_id(&self, id: &str) -> Result<Option<Customer>>;
|
|
|
|
async fn update(&self, customer: Customer) -> Result<Customer>;
|
|
|
|
async fn delete(&self, id: &str) -> Result<()>;
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
|
|
|
|
2024-12-25 16:25:09 -03:00
|
|
|
trait ToDbString {
|
|
|
|
fn to_db_string(&self) -> String;
|
|
|
|
}
|
|
|
|
|
|
|
|
trait FromDbString: Sized {
|
|
|
|
fn from_db_string(s: &str) -> Result<Self>;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToDbString for SubscriptionTier {
|
|
|
|
fn to_db_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
SubscriptionTier::Free => "free".to_string(),
|
|
|
|
SubscriptionTier::Pro => "pro".to_string(),
|
|
|
|
SubscriptionTier::Enterprise => "enterprise".to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToDbString for CustomerStatus {
|
|
|
|
fn to_db_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
CustomerStatus::Active => "active".to_string(),
|
|
|
|
CustomerStatus::Inactive => "inactive".to_string(),
|
|
|
|
CustomerStatus::Suspended => "suspended".to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromDbString for SubscriptionTier {
|
|
|
|
fn from_db_string(s: &str) -> Result<Self> {
|
|
|
|
match s {
|
|
|
|
"free" => Ok(SubscriptionTier::Free),
|
|
|
|
"pro" => Ok(SubscriptionTier::Pro),
|
|
|
|
"enterprise" => Ok(SubscriptionTier::Enterprise),
|
|
|
|
_ => Err(Error::internal(format!("Invalid subscription tier: {}", s))),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromDbString for CustomerStatus {
|
|
|
|
fn from_db_string(s: &str) -> Result<Self> {
|
|
|
|
match s {
|
|
|
|
"active" => Ok(CustomerStatus::Active),
|
|
|
|
"inactive" => Ok(CustomerStatus::Inactive),
|
|
|
|
"suspended" => Ok(CustomerStatus::Suspended),
|
|
|
|
_ => Err(Error::internal(format!("Invalid customer status: {}", s))),
|
|
|
|
}
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
2024-12-25 16:25:09 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct PostgresCustomerRepository {
|
|
|
|
pool: Arc<PgPool>,
|
|
|
|
}
|
2024-12-22 20:56:52 -03:00
|
|
|
|
2024-12-25 16:25:09 -03:00
|
|
|
#[async_trait::async_trait]
|
|
|
|
impl CustomerRepository for PostgresCustomerRepository {
|
|
|
|
async fn create(&self, customer: Customer) -> Result<Customer> {
|
|
|
|
let subscription_tier = customer.subscription_tier.to_db_string();
|
|
|
|
let status = customer.status.to_db_string();
|
2024-12-24 21:13:47 -03:00
|
|
|
|
2024-12-25 16:25:09 -03:00
|
|
|
let row = sqlx::query(
|
2024-12-22 20:56:52 -03:00
|
|
|
r#"
|
2024-12-24 21:13:47 -03:00
|
|
|
INSERT INTO customers (
|
2024-12-25 16:25:09 -03:00
|
|
|
id, name, email,
|
|
|
|
subscription_tier, status,
|
|
|
|
created_at, updated_at,
|
|
|
|
max_instances
|
|
|
|
)
|
2024-12-24 21:13:47 -03:00
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
2024-12-22 20:56:52 -03:00
|
|
|
RETURNING *
|
2024-12-25 16:25:09 -03:00
|
|
|
"#
|
2024-12-22 20:56:52 -03:00
|
|
|
)
|
2024-12-25 16:25:09 -03:00
|
|
|
.bind(&customer.id)
|
|
|
|
.bind(&customer.name)
|
|
|
|
.bind(&customer.email)
|
|
|
|
.bind(&subscription_tier)
|
|
|
|
.bind(&status)
|
|
|
|
.bind(&customer.created_at)
|
|
|
|
.bind(&customer.updated_at)
|
|
|
|
.bind(customer.max_instances as i32)
|
2024-12-24 13:05:54 -03:00
|
|
|
.fetch_one(&*self.pool)
|
2024-12-22 20:56:52 -03:00
|
|
|
.await
|
2024-12-24 13:05:54 -03:00
|
|
|
.map_err(|e| Error::internal(format!("Database error: {}", e)))?;
|
2024-12-22 20:56:52 -03:00
|
|
|
|
2024-12-25 16:25:09 -03:00
|
|
|
Self::row_to_customer(&row).await
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
|
|
|
|
2024-12-25 16:25:09 -03:00
|
|
|
async fn get_customer_by_id(&self, id: &str) -> Result<Option<Customer>> {
|
|
|
|
let maybe_row = sqlx::query(
|
|
|
|
"SELECT * FROM customers WHERE id = $1"
|
|
|
|
)
|
|
|
|
.bind(id)
|
|
|
|
.fetch_optional(&*self.pool)
|
|
|
|
.await
|
|
|
|
.map_err(|e| Error::internal(format!("Database error: {}", e)))?;
|
|
|
|
|
|
|
|
if let Some(row) = maybe_row {
|
|
|
|
Ok(Some(Self::row_to_customer(&row).await?))
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn update(&self, customer: Customer) -> Result<Customer> {
|
|
|
|
let subscription_tier = customer.subscription_tier.to_db_string();
|
|
|
|
let status = customer.status.to_db_string();
|
|
|
|
|
|
|
|
let row = sqlx::query(
|
2024-12-22 20:56:52 -03:00
|
|
|
r#"
|
2024-12-25 16:25:09 -03:00
|
|
|
UPDATE customers
|
|
|
|
SET name = $2,
|
|
|
|
email = $3,
|
|
|
|
subscription_tier = $4,
|
|
|
|
status = $5,
|
|
|
|
updated_at = $6,
|
|
|
|
max_instances = $7
|
2024-12-24 13:05:54 -03:00
|
|
|
WHERE id = $1
|
2024-12-25 16:25:09 -03:00
|
|
|
RETURNING *
|
|
|
|
"#
|
2024-12-22 20:56:52 -03:00
|
|
|
)
|
2024-12-25 16:25:09 -03:00
|
|
|
.bind(&customer.id)
|
|
|
|
.bind(&customer.name)
|
|
|
|
.bind(&customer.email)
|
|
|
|
.bind(&subscription_tier)
|
|
|
|
.bind(&status)
|
|
|
|
.bind(Utc::now())
|
|
|
|
.bind(customer.max_instances as i32)
|
|
|
|
.fetch_one(&*self.pool)
|
2024-12-22 20:56:52 -03:00
|
|
|
.await
|
2024-12-24 13:05:54 -03:00
|
|
|
.map_err(|e| Error::internal(format!("Database error: {}", e)))?;
|
2024-12-22 20:56:52 -03:00
|
|
|
|
2024-12-25 16:25:09 -03:00
|
|
|
Self::row_to_customer(&row).await
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn delete(&self, id: &str) -> Result<()> {
|
|
|
|
sqlx::query("DELETE FROM customers WHERE id = $1")
|
|
|
|
.bind(id)
|
|
|
|
.execute(&*self.pool)
|
|
|
|
.await
|
|
|
|
.map_err(|e| Error::internal(format!("Database error: {}", e)))?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PostgresCustomerRepository {
|
|
|
|
pub fn new(pool: Arc<PgPool>) -> Self {
|
|
|
|
Self { pool }
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn row_to_customer(row: &PgRow) -> Result<Customer> {
|
|
|
|
Ok(Customer {
|
|
|
|
id: row.try_get("id").map_err(|e| Error::internal(e.to_string()))?,
|
|
|
|
name: row.try_get("name").map_err(|e| Error::internal(e.to_string()))?,
|
|
|
|
email: row.try_get("email").map_err(|e| Error::internal(e.to_string()))?,
|
|
|
|
subscription_tier: SubscriptionTier::from_db_string(
|
|
|
|
row.try_get("subscription_tier").map_err(|e| Error::internal(e.to_string()))?
|
|
|
|
)?,
|
|
|
|
status: CustomerStatus::from_db_string(
|
|
|
|
row.try_get("status").map_err(|e| Error::internal(e.to_string()))?
|
|
|
|
)?,
|
|
|
|
created_at: row.try_get("created_at").map_err(|e| Error::internal(e.to_string()))?,
|
|
|
|
updated_at: row.try_get("updated_at").map_err(|e| Error::internal(e.to_string()))?,
|
|
|
|
max_instances: {
|
|
|
|
let value: i32 = row.try_get("max_instances")
|
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
|
|
|
if value < 0 {
|
|
|
|
return Err(Error::internal("max_instances cannot be negative"));
|
|
|
|
}
|
|
|
|
value as u32
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use chrono::Utc;
|
|
|
|
|
|
|
|
fn create_test_customer() -> Customer {
|
|
|
|
Customer {
|
|
|
|
id: Uuid::new_v4(),
|
|
|
|
name: "Test Customer".to_string(),
|
|
|
|
email: "test@example.com".to_string(),
|
|
|
|
subscription_tier: SubscriptionTier::Free,
|
|
|
|
status: CustomerStatus::Active,
|
|
|
|
created_at: Utc::now(),
|
|
|
|
updated_at: Utc::now(),
|
|
|
|
max_instances: 1,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add your tests here
|
|
|
|
// Example:
|
|
|
|
/*
|
|
|
|
#[sqlx::test]
|
|
|
|
async fn test_create_customer() {
|
|
|
|
let pool = setup_test_db().await;
|
|
|
|
let repo = PostgresCustomerRepository::new(Arc::new(pool));
|
|
|
|
|
|
|
|
let customer = create_test_customer();
|
|
|
|
let created = repo.create(customer.clone()).await.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(created.id, customer.id);
|
|
|
|
assert_eq!(created.name, customer.name);
|
|
|
|
// ... more assertions
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
2024-12-25 16:25:09 -03:00
|
|
|
*/
|
2024-12-24 13:05:54 -03:00
|
|
|
}
|