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#[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#[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#[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#[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
268pub 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}