koprogo_api/infrastructure/database/repositories/
organization_repository_impl.rs1use 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}