Skip to main content

bindizr_db/repository/
mod.rs

1pub mod mysql;
2pub mod postgres;
3pub mod sqlite;
4
5use async_trait::async_trait;
6use sqlx::{MySql, Postgres, Sqlite};
7
8use super::model::{
9    api_token::ApiToken,
10    catalog_zone_state::CatalogZoneState,
11    record::{Record, RecordType, RecordWithZone},
12    zone::Zone,
13    zone_change::ZoneChange,
14    zone_snapshot::ZoneSnapshot,
15};
16use crate::{DatabasePool, error::DatabaseError, get_pool};
17
18#[derive(Clone, Debug, Default)]
19pub struct ZoneFilter {
20    pub name: Option<String>,
21    pub id: Option<i32>,
22    pub primary_ns: Option<String>,
23    pub admin_email: Option<String>,
24    pub ttl: Option<i32>,
25    pub min_ttl: Option<i32>,
26    pub max_ttl: Option<i32>,
27    pub serial: Option<i32>,
28    pub search: Option<String>,
29    pub limit: Option<u32>,
30    pub offset: Option<u64>,
31}
32
33#[derive(Clone, Debug, Default)]
34pub struct RecordFilter {
35    pub zone_name: Option<String>,
36    pub name: Option<String>,
37    pub record_type: Option<String>,
38    pub value: Option<String>,
39    pub ttl: Option<i32>,
40    pub min_ttl: Option<i32>,
41    pub max_ttl: Option<i32>,
42    pub priority: Option<i32>,
43    pub min_priority: Option<i32>,
44    pub max_priority: Option<i32>,
45    pub search: Option<String>,
46    pub limit: Option<u32>,
47    pub offset: Option<u64>,
48}
49
50pub struct RepositoryTx<'a>(RepositoryTxKind<'a>);
51
52enum RepositoryTxKind<'a> {
53    MySQL(sqlx::Transaction<'a, MySql>),
54    PostgreSQL(sqlx::Transaction<'a, Postgres>),
55    SQLite(sqlx::Transaction<'a, Sqlite>),
56}
57
58pub async fn begin_transaction() -> Result<RepositoryTx<'static>, DatabaseError> {
59    match get_pool() {
60        DatabasePool::MySQL(pool) => pool
61            .begin()
62            .await
63            .map(|tx| RepositoryTx(RepositoryTxKind::MySQL(tx)))
64            .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
65        DatabasePool::PostgreSQL(pool) => pool
66            .begin()
67            .await
68            .map(|tx| RepositoryTx(RepositoryTxKind::PostgreSQL(tx)))
69            .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
70        DatabasePool::SQLite(pool) => pool
71            .begin_with("BEGIN IMMEDIATE")
72            .await
73            .map(|tx| RepositoryTx(RepositoryTxKind::SQLite(tx)))
74            .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
75    }
76}
77
78impl<'a> RepositoryTx<'a> {
79    pub async fn commit(self) -> Result<(), DatabaseError> {
80        match self.0 {
81            RepositoryTxKind::MySQL(tx) => tx
82                .commit()
83                .await
84                .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
85            RepositoryTxKind::PostgreSQL(tx) => tx
86                .commit()
87                .await
88                .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
89            RepositoryTxKind::SQLite(tx) => tx
90                .commit()
91                .await
92                .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
93        }
94    }
95
96    pub async fn rollback(self) -> Result<(), DatabaseError> {
97        match self.0 {
98            RepositoryTxKind::MySQL(tx) => tx
99                .rollback()
100                .await
101                .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
102            RepositoryTxKind::PostgreSQL(tx) => tx
103                .rollback()
104                .await
105                .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
106            RepositoryTxKind::SQLite(tx) => tx
107                .rollback()
108                .await
109                .map_err(|e| DatabaseError::TransactionFailed(e.to_string())),
110        }
111    }
112}
113
114// Zone Repository Trait
115#[allow(dead_code)]
116#[async_trait]
117pub trait ZoneRepository: Send + Sync {
118    async fn create(&self, zone: Zone) -> Result<Zone, DatabaseError>;
119    async fn create_tx(&self, tx: &mut RepositoryTx<'_>, zone: Zone)
120    -> Result<Zone, DatabaseError>;
121    async fn get_by_id(&self, id: i32) -> Result<Option<Zone>, DatabaseError>;
122    async fn get_by_id_tx(
123        &self,
124        tx: &mut RepositoryTx<'_>,
125        id: i32,
126    ) -> Result<Option<Zone>, DatabaseError>;
127    async fn get_by_name(&self, name: &str) -> Result<Option<Zone>, DatabaseError>;
128    async fn get_by_name_tx(
129        &self,
130        tx: &mut RepositoryTx<'_>,
131        name: &str,
132    ) -> Result<Option<Zone>, DatabaseError>;
133    async fn get_all(&self) -> Result<Vec<Zone>, DatabaseError>;
134    async fn get_by_filter(&self, filter: ZoneFilter) -> Result<Vec<Zone>, DatabaseError>;
135    async fn count_by_filter(&self, filter: ZoneFilter) -> Result<u64, DatabaseError>;
136    async fn update(&self, zone: Zone) -> Result<Zone, DatabaseError>;
137    async fn update_tx(&self, tx: &mut RepositoryTx<'_>, zone: Zone)
138    -> Result<Zone, DatabaseError>;
139    async fn delete(&self, id: i32) -> Result<(), DatabaseError>;
140    async fn delete_tx(&self, tx: &mut RepositoryTx<'_>, id: i32) -> Result<(), DatabaseError>;
141}
142
143// Record Repository Trait
144#[allow(dead_code)]
145#[async_trait]
146pub trait RecordRepository: Send + Sync {
147    async fn create(&self, record: Record) -> Result<Record, DatabaseError>;
148    async fn create_tx(
149        &self,
150        tx: &mut RepositoryTx<'_>,
151        record: Record,
152    ) -> Result<Record, DatabaseError>;
153    async fn get_by_id(&self, id: i32) -> Result<Option<Record>, DatabaseError>;
154    async fn get_by_id_with_zone(&self, id: i32) -> Result<Option<RecordWithZone>, DatabaseError>;
155    async fn get_by_id_tx(
156        &self,
157        tx: &mut RepositoryTx<'_>,
158        id: i32,
159    ) -> Result<Option<Record>, DatabaseError>;
160    async fn get_by_zone_id(&self, zone_id: i32) -> Result<Vec<Record>, DatabaseError>;
161    async fn get_by_zone_id_with_zone(
162        &self,
163        zone_id: i32,
164    ) -> Result<Vec<RecordWithZone>, DatabaseError>;
165    async fn get_by_zone_id_tx(
166        &self,
167        tx: &mut RepositoryTx<'_>,
168        zone_id: i32,
169    ) -> Result<Vec<Record>, DatabaseError>;
170    async fn get(
171        &self,
172        zone_id: Option<i32>,
173        name: &str,
174        record_type: &RecordType,
175        value: Option<&str>,
176        priority: Option<i32>,
177        match_priority: bool,
178    ) -> Result<Option<Record>, DatabaseError>;
179    async fn get_tx(
180        &self,
181        tx: &mut RepositoryTx<'_>,
182        zone_id: Option<i32>,
183        name: &str,
184        record_type: &RecordType,
185        value: Option<&str>,
186        priority: Option<i32>,
187        match_priority: bool,
188    ) -> Result<Option<Record>, DatabaseError>;
189    async fn get_all(&self) -> Result<Vec<Record>, DatabaseError>;
190    async fn get_all_with_zone(&self) -> Result<Vec<RecordWithZone>, DatabaseError>;
191    async fn get_by_filter_with_zone(
192        &self,
193        filter: RecordFilter,
194    ) -> Result<Vec<RecordWithZone>, DatabaseError>;
195    async fn count_by_filter(&self, filter: RecordFilter) -> Result<u64, DatabaseError>;
196    async fn update(&self, record: Record) -> Result<Record, DatabaseError>;
197    async fn update_tx(
198        &self,
199        tx: &mut RepositoryTx<'_>,
200        record: Record,
201    ) -> Result<Record, DatabaseError>;
202    async fn delete(&self, id: i32) -> Result<(), DatabaseError>;
203    async fn delete_tx(&self, tx: &mut RepositoryTx<'_>, id: i32) -> Result<(), DatabaseError>;
204}
205
206// Zone Change Repository Trait
207#[allow(dead_code)]
208#[async_trait]
209pub trait ZoneChangeRepository: Send + Sync {
210    async fn create(&self, zone_change: ZoneChange) -> Result<ZoneChange, DatabaseError>;
211    async fn create_tx(
212        &self,
213        tx: &mut RepositoryTx<'_>,
214        zone_change: ZoneChange,
215    ) -> Result<ZoneChange, DatabaseError>;
216    async fn get_changes_between_serials(
217        &self,
218        zone_id: i32,
219        from_serial: i32,
220        to_serial: i32,
221    ) -> Result<Vec<ZoneChange>, DatabaseError>;
222}
223
224#[allow(dead_code)]
225#[async_trait]
226pub trait ZoneSnapshotRepository: Send + Sync {
227    async fn upsert(&self, snapshot: ZoneSnapshot) -> Result<ZoneSnapshot, DatabaseError>;
228    async fn upsert_tx(
229        &self,
230        tx: &mut RepositoryTx<'_>,
231        snapshot: ZoneSnapshot,
232    ) -> Result<ZoneSnapshot, DatabaseError>;
233    async fn get_by_zone_id_and_serial(
234        &self,
235        zone_id: i32,
236        serial: i32,
237    ) -> Result<Option<ZoneSnapshot>, DatabaseError>;
238}
239
240// API Token Repository Trait
241#[async_trait]
242pub trait ApiTokenRepository: Send + Sync {
243    async fn create(&self, token: ApiToken) -> Result<ApiToken, DatabaseError>;
244    async fn get_by_id(&self, id: i32) -> Result<Option<ApiToken>, DatabaseError>;
245    async fn get_by_token(&self, token: &str) -> Result<Option<ApiToken>, DatabaseError>;
246    async fn get_all(&self) -> Result<Vec<ApiToken>, DatabaseError>;
247    async fn update(&self, token: ApiToken) -> Result<ApiToken, DatabaseError>;
248    async fn delete(&self, id: i32) -> Result<(), DatabaseError>;
249}
250
251#[async_trait]
252pub trait CatalogZoneStateRepository: Send + Sync {
253    async fn update_serial_for_signature(
254        &self,
255        name: &str,
256        signature: &str,
257        base_serial: i32,
258    ) -> Result<CatalogZoneState, DatabaseError>;
259    async fn update_serial_for_signature_tx(
260        &self,
261        tx: &mut RepositoryTx<'_>,
262        name: &str,
263        signature: &str,
264        base_serial: i32,
265    ) -> Result<CatalogZoneState, DatabaseError>;
266}
267
268// Repository Factory
269pub struct RepositoryFactory;
270
271impl RepositoryFactory {
272    pub fn create_zone_repository(pool: &DatabasePool) -> Box<dyn ZoneRepository> {
273        match pool {
274            DatabasePool::MySQL(mysql_pool) => {
275                Box::new(mysql::MySqlZoneRepository::new(mysql_pool.clone()))
276            }
277            DatabasePool::PostgreSQL(postgres_pool) => {
278                Box::new(postgres::PostgresZoneRepository::new(postgres_pool.clone()))
279            }
280            DatabasePool::SQLite(sqlite_pool) => {
281                Box::new(sqlite::SqliteZoneRepository::new(sqlite_pool.clone()))
282            }
283        }
284    }
285
286    pub fn create_record_repository(pool: &DatabasePool) -> Box<dyn RecordRepository> {
287        match pool {
288            DatabasePool::MySQL(mysql_pool) => {
289                Box::new(mysql::MySqlRecordRepository::new(mysql_pool.clone()))
290            }
291            DatabasePool::PostgreSQL(postgres_pool) => Box::new(
292                postgres::PostgresRecordRepository::new(postgres_pool.clone()),
293            ),
294            DatabasePool::SQLite(sqlite_pool) => {
295                Box::new(sqlite::SqliteRecordRepository::new(sqlite_pool.clone()))
296            }
297        }
298    }
299
300    pub fn create_api_token_repository(pool: &DatabasePool) -> Box<dyn ApiTokenRepository> {
301        match pool {
302            DatabasePool::MySQL(mysql_pool) => {
303                Box::new(mysql::MySqlApiTokenRepository::new(mysql_pool.clone()))
304            }
305            DatabasePool::PostgreSQL(postgres_pool) => Box::new(
306                postgres::PostgresApiTokenRepository::new(postgres_pool.clone()),
307            ),
308            DatabasePool::SQLite(sqlite_pool) => {
309                Box::new(sqlite::SqliteApiTokenRepository::new(sqlite_pool.clone()))
310            }
311        }
312    }
313
314    pub fn create_zone_change_repository(pool: &DatabasePool) -> Box<dyn ZoneChangeRepository> {
315        match pool {
316            DatabasePool::MySQL(mysql_pool) => {
317                Box::new(mysql::MySqlZoneChangeRepository::new(mysql_pool.clone()))
318            }
319            DatabasePool::PostgreSQL(postgres_pool) => Box::new(
320                postgres::PostgresZoneChangeRepository::new(postgres_pool.clone()),
321            ),
322            DatabasePool::SQLite(sqlite_pool) => {
323                Box::new(sqlite::SqliteZoneChangeRepository::new(sqlite_pool.clone()))
324            }
325        }
326    }
327
328    pub fn create_zone_snapshot_repository(pool: &DatabasePool) -> Box<dyn ZoneSnapshotRepository> {
329        match pool {
330            DatabasePool::MySQL(mysql_pool) => {
331                Box::new(mysql::MySqlZoneSnapshotRepository::new(mysql_pool.clone()))
332            }
333            DatabasePool::PostgreSQL(postgres_pool) => Box::new(
334                postgres::PostgresZoneSnapshotRepository::new(postgres_pool.clone()),
335            ),
336            DatabasePool::SQLite(sqlite_pool) => Box::new(
337                sqlite::SqliteZoneSnapshotRepository::new(sqlite_pool.clone()),
338            ),
339        }
340    }
341
342    pub fn create_catalog_zone_state_repository(
343        pool: &DatabasePool,
344    ) -> Box<dyn CatalogZoneStateRepository> {
345        match pool {
346            DatabasePool::MySQL(mysql_pool) => Box::new(
347                mysql::MySqlCatalogZoneStateRepository::new(mysql_pool.clone()),
348            ),
349            DatabasePool::PostgreSQL(postgres_pool) => Box::new(
350                postgres::PostgresCatalogZoneStateRepository::new(postgres_pool.clone()),
351            ),
352            DatabasePool::SQLite(sqlite_pool) => Box::new(
353                sqlite::SqliteCatalogZoneStateRepository::new(sqlite_pool.clone()),
354            ),
355        }
356    }
357}