koprogo_api/infrastructure/database/repositories/
organization_repository_impl.rs

1use crate::application::ports::OrganizationRepository;
2use crate::domain::entities::{Organization, SubscriptionPlan};
3use crate::infrastructure::pool::DbPool;
4use async_trait::async_trait;
5use uuid::Uuid;
6
7pub struct PostgresOrganizationRepository {
8    pool: DbPool,
9}
10
11impl PostgresOrganizationRepository {
12    pub fn new(pool: DbPool) -> Self {
13        Self { pool }
14    }
15}
16
17#[async_trait]
18impl OrganizationRepository for PostgresOrganizationRepository {
19    async fn create(&self, org: &Organization) -> Result<Organization, String> {
20        sqlx::query!(
21            r#"
22            INSERT INTO organizations (id, name, slug, contact_email, contact_phone, subscription_plan, max_buildings, max_users, is_active, created_at, updated_at)
23            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
24            "#,
25            org.id,
26            org.name,
27            org.slug,
28            org.contact_email,
29            org.contact_phone,
30            org.subscription_plan.to_string(),
31            org.max_buildings,
32            org.max_users,
33            org.is_active,
34            org.created_at,
35            org.updated_at,
36        )
37        .execute(&self.pool)
38        .await
39        .map_err(|e| format!("Failed to create organization: {}", e))?;
40
41        Ok(org.clone())
42    }
43
44    async fn find_by_id(&self, id: Uuid) -> Result<Option<Organization>, String> {
45        let result = sqlx::query!(
46            r#"
47            SELECT id, name, slug, contact_email, contact_phone, subscription_plan, max_buildings, max_users, is_active, created_at, updated_at
48            FROM organizations
49            WHERE id = $1
50            "#,
51            id
52        )
53        .fetch_optional(&self.pool)
54        .await
55        .map_err(|e| format!("Failed to find organization: {}", e))?;
56
57        match result {
58            Some(row) => {
59                let subscription_plan = row
60                    .subscription_plan
61                    .parse::<SubscriptionPlan>()
62                    .map_err(|e| format!("Invalid subscription plan: {}", e))?;
63
64                Ok(Some(Organization {
65                    id: row.id,
66                    name: row.name,
67                    slug: row.slug,
68                    contact_email: row.contact_email,
69                    contact_phone: row.contact_phone,
70                    subscription_plan,
71                    max_buildings: row.max_buildings,
72                    max_users: row.max_users,
73                    is_active: row.is_active,
74                    created_at: row.created_at,
75                    updated_at: row.updated_at,
76                }))
77            }
78            None => Ok(None),
79        }
80    }
81
82    async fn find_by_slug(&self, slug: &str) -> Result<Option<Organization>, String> {
83        let result = sqlx::query!(
84            r#"
85            SELECT id, name, slug, contact_email, contact_phone, subscription_plan, max_buildings, max_users, is_active, created_at, updated_at
86            FROM organizations
87            WHERE slug = $1
88            "#,
89            slug
90        )
91        .fetch_optional(&self.pool)
92        .await
93        .map_err(|e| format!("Failed to find organization by slug: {}", e))?;
94
95        match result {
96            Some(row) => {
97                let subscription_plan = row
98                    .subscription_plan
99                    .parse::<SubscriptionPlan>()
100                    .map_err(|e| format!("Invalid subscription plan: {}", e))?;
101
102                Ok(Some(Organization {
103                    id: row.id,
104                    name: row.name,
105                    slug: row.slug,
106                    contact_email: row.contact_email,
107                    contact_phone: row.contact_phone,
108                    subscription_plan,
109                    max_buildings: row.max_buildings,
110                    max_users: row.max_users,
111                    is_active: row.is_active,
112                    created_at: row.created_at,
113                    updated_at: row.updated_at,
114                }))
115            }
116            None => Ok(None),
117        }
118    }
119
120    async fn find_all(&self) -> Result<Vec<Organization>, String> {
121        let rows = sqlx::query!(
122            r#"
123            SELECT id, name, slug, contact_email, contact_phone, subscription_plan, max_buildings, max_users, is_active, created_at, updated_at
124            FROM organizations
125            ORDER BY created_at DESC
126            "#
127        )
128        .fetch_all(&self.pool)
129        .await
130        .map_err(|e| format!("Failed to fetch organizations: {}", e))?;
131
132        let orgs = rows
133            .into_iter()
134            .filter_map(|row| {
135                let subscription_plan = row.subscription_plan.parse::<SubscriptionPlan>().ok()?;
136                Some(Organization {
137                    id: row.id,
138                    name: row.name,
139                    slug: row.slug,
140                    contact_email: row.contact_email,
141                    contact_phone: row.contact_phone,
142                    subscription_plan,
143                    max_buildings: row.max_buildings,
144                    max_users: row.max_users,
145                    is_active: row.is_active,
146                    created_at: row.created_at,
147                    updated_at: row.updated_at,
148                })
149            })
150            .collect();
151
152        Ok(orgs)
153    }
154
155    async fn update(&self, org: &Organization) -> Result<Organization, String> {
156        sqlx::query!(
157            r#"
158            UPDATE organizations
159            SET name = $2, slug = $3, contact_email = $4, contact_phone = $5,
160                subscription_plan = $6, max_buildings = $7, max_users = $8,
161                is_active = $9, updated_at = $10
162            WHERE id = $1
163            "#,
164            org.id,
165            org.name,
166            org.slug,
167            org.contact_email,
168            org.contact_phone,
169            org.subscription_plan.to_string(),
170            org.max_buildings,
171            org.max_users,
172            org.is_active,
173            org.updated_at,
174        )
175        .execute(&self.pool)
176        .await
177        .map_err(|e| format!("Failed to update organization: {}", e))?;
178
179        Ok(org.clone())
180    }
181
182    async fn delete(&self, id: Uuid) -> Result<bool, String> {
183        let result = sqlx::query!(
184            r#"
185            DELETE FROM organizations
186            WHERE id = $1
187            "#,
188            id
189        )
190        .execute(&self.pool)
191        .await
192        .map_err(|e| format!("Failed to delete organization: {}", e))?;
193
194        Ok(result.rows_affected() > 0)
195    }
196
197    async fn count_buildings(&self, org_id: Uuid) -> Result<i64, String> {
198        let result = sqlx::query!(
199            r#"
200            SELECT COUNT(*) as count
201            FROM buildings
202            WHERE organization_id = $1
203            "#,
204            org_id
205        )
206        .fetch_one(&self.pool)
207        .await
208        .map_err(|e| format!("Failed to count buildings: {}", e))?;
209
210        Ok(result.count.unwrap_or(0))
211    }
212}