koprogo_api/infrastructure/database/repositories/
module_registry_impl.rs1use async_trait::async_trait;
16use sqlx::PgPool;
17use uuid::Uuid;
18
19use crate::application::error::AppError;
20use crate::application::ports::ModuleRegistry;
21use crate::domain::entities::Module;
22
23pub struct PostgresModuleRegistry {
24 pool: PgPool,
25}
26
27impl PostgresModuleRegistry {
28 pub fn new(pool: PgPool) -> Self {
29 Self { pool }
30 }
31}
32
33fn erreur_sql(e: sqlx::Error) -> AppError {
34 AppError::Database(e.to_string())
35}
36
37#[async_trait]
38impl ModuleRegistry for PostgresModuleRegistry {
39 async fn list_enabled(&self, acp_id: Uuid) -> Result<Vec<Module>, AppError> {
40 let noms: Vec<String> = sqlx::query_scalar(
41 "SELECT module FROM acp_enabled_modules \
42 WHERE acp_id = $1 AND archived_at IS NULL \
43 ORDER BY module",
44 )
45 .bind(acp_id)
46 .fetch_all(&self.pool)
47 .await
48 .map_err(erreur_sql)?;
49
50 noms.into_iter()
56 .map(|nom| {
57 Module::depuis_nom(&nom).ok_or_else(|| {
58 AppError::Database(format!(
59 "Module « {nom} » présent en base mais inconnu de l'enum Rust : \
60 la contrainte SQL et le domaine ont divergé"
61 ))
62 })
63 })
64 .collect()
65 }
66
67 async fn enable(&self, acp_id: Uuid, module: Module) -> Result<(), AppError> {
68 sqlx::query(
72 "INSERT INTO acp_enabled_modules (acp_id, module) VALUES ($1, $2) \
73 ON CONFLICT (acp_id, module) \
74 DO UPDATE SET archived_at = NULL, enabled_at = NOW()",
75 )
76 .bind(acp_id)
77 .bind(module.as_str())
78 .execute(&self.pool)
79 .await
80 .map_err(erreur_sql)?;
81 Ok(())
82 }
83
84 async fn disable(&self, acp_id: Uuid, module: Module) -> Result<(), AppError> {
85 sqlx::query(
89 "UPDATE acp_enabled_modules SET archived_at = NOW() \
90 WHERE acp_id = $1 AND module = $2 AND archived_at IS NULL",
91 )
92 .bind(acp_id)
93 .bind(module.as_str())
94 .execute(&self.pool)
95 .await
96 .map_err(erreur_sql)?;
97 Ok(())
98 }
99
100 async fn is_enabled(&self, acp_id: Uuid, module: Module) -> Result<bool, AppError> {
101 let actif: Option<bool> = sqlx::query_scalar(
102 "SELECT TRUE FROM acp_enabled_modules \
103 WHERE acp_id = $1 AND module = $2 AND archived_at IS NULL",
104 )
105 .bind(acp_id)
106 .bind(module.as_str())
107 .fetch_optional(&self.pool)
108 .await
109 .map_err(erreur_sql)?;
110 Ok(actif.unwrap_or(false))
111 }
112}