1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use super::common::MetabaseId;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
14#[serde(rename_all = "lowercase")]
15pub enum ConnectionSource {
16 #[default]
17 Admin,
18 Setup,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub struct FieldId(pub i64);
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27pub struct TableId(pub i64);
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct Database {
32 pub id: MetabaseId,
34
35 pub name: String,
37
38 pub engine: String,
40
41 pub details: Value,
43
44 #[serde(default)]
46 pub is_full_sync: bool,
47
48 #[serde(default)]
50 pub is_on_demand: bool,
51
52 #[serde(default)]
54 pub is_sample: bool,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub cache_field_values_schedule: Option<String>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub metadata_sync_schedule: Option<String>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub created_at: Option<DateTime<Utc>>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub updated_at: Option<DateTime<Utc>>,
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct DatabaseTable {
76 pub id: TableId,
78
79 pub db_id: MetabaseId,
81
82 pub name: String,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub schema: Option<String>,
88
89 pub display_name: String,
91
92 #[serde(default = "default_true")]
94 pub active: bool,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub description: Option<String>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub entity_type: Option<String>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub visibility_type: Option<String>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub created_at: Option<DateTime<Utc>>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub updated_at: Option<DateTime<Utc>>,
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct DatabaseField {
120 pub id: FieldId,
122
123 pub table_id: TableId,
125
126 pub name: String,
128
129 pub display_name: String,
131
132 pub database_type: String,
134
135 pub base_type: String,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub semantic_type: Option<String>,
141
142 #[serde(default = "default_true")]
144 pub active: bool,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub description: Option<String>,
149
150 #[serde(default)]
152 pub is_pk: bool,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub fk_target_field_id: Option<FieldId>,
157
158 #[serde(default)]
160 pub position: i32,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub visibility_type: Option<String>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub created_at: Option<DateTime<Utc>>,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub updated_at: Option<DateTime<Utc>>,
173}
174
175#[derive(Debug, Clone, Serialize)]
177pub struct CreateDatabaseRequest {
178 pub name: String,
180
181 pub engine: String,
183
184 pub details: Value,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub is_full_sync: Option<bool>,
190
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub is_on_demand: Option<bool>,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub cache_field_values_schedule: Option<String>,
198
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub metadata_sync_schedule: Option<String>,
202}
203
204#[derive(Debug, Clone, Default, Serialize)]
206pub struct UpdateDatabaseRequest {
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub name: Option<String>,
210
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub details: Option<Value>,
214
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub is_full_sync: Option<bool>,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub is_on_demand: Option<bool>,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub cache_field_values_schedule: Option<String>,
226
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub metadata_sync_schedule: Option<String>,
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct DatabaseSyncStatus {
235 pub status: String,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub progress: Option<f32>,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub error: Option<String>,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub started_at: Option<DateTime<Utc>>,
249
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub completed_at: Option<DateTime<Utc>>,
253}
254
255fn default_true() -> bool {
256 true
257}
258
259impl Database {
260 pub fn builder(name: impl Into<String>, engine: impl Into<String>) -> DatabaseBuilder {
262 DatabaseBuilder::new(name, engine)
263 }
264}
265
266pub struct DatabaseBuilder {
268 name: String,
269 engine: String,
270 details: Value,
271 is_full_sync: bool,
272 is_on_demand: bool,
273 cache_field_values_schedule: Option<String>,
274 metadata_sync_schedule: Option<String>,
275}
276
277impl DatabaseBuilder {
278 pub fn new(name: impl Into<String>, engine: impl Into<String>) -> Self {
280 Self {
281 name: name.into(),
282 engine: engine.into(),
283 details: Value::Object(serde_json::Map::new()),
284 is_full_sync: true,
285 is_on_demand: false,
286 cache_field_values_schedule: None,
287 metadata_sync_schedule: None,
288 }
289 }
290
291 pub fn details(mut self, details: Value) -> Self {
293 self.details = details;
294 self
295 }
296
297 pub fn full_sync(mut self, enabled: bool) -> Self {
299 self.is_full_sync = enabled;
300 self
301 }
302
303 pub fn on_demand_sync(mut self, enabled: bool) -> Self {
305 self.is_on_demand = enabled;
306 self
307 }
308
309 pub fn cache_schedule(mut self, schedule: impl Into<String>) -> Self {
311 self.cache_field_values_schedule = Some(schedule.into());
312 self
313 }
314
315 pub fn sync_schedule(mut self, schedule: impl Into<String>) -> Self {
317 self.metadata_sync_schedule = Some(schedule.into());
318 self
319 }
320
321 pub fn build(self) -> Database {
323 Database {
324 id: MetabaseId(0), name: self.name,
326 engine: self.engine,
327 details: self.details,
328 is_full_sync: self.is_full_sync,
329 is_on_demand: self.is_on_demand,
330 is_sample: false,
331 cache_field_values_schedule: self.cache_field_values_schedule,
332 metadata_sync_schedule: self.metadata_sync_schedule,
333 created_at: None,
334 updated_at: None,
335 }
336 }
337
338 pub fn build_request(self) -> CreateDatabaseRequest {
340 CreateDatabaseRequest {
341 name: self.name,
342 engine: self.engine,
343 details: self.details,
344 is_full_sync: Some(self.is_full_sync),
345 is_on_demand: Some(self.is_on_demand),
346 cache_field_values_schedule: self.cache_field_values_schedule,
347 metadata_sync_schedule: self.metadata_sync_schedule,
348 }
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use serde_json::json;
356
357 #[test]
358 fn test_database_creation() {
359 let database = Database::builder("Test DB", "postgres")
360 .details(json!({
361 "host": "localhost",
362 "port": 5432,
363 "dbname": "testdb",
364 "user": "testuser"
365 }))
366 .full_sync(true)
367 .on_demand_sync(false)
368 .build();
369
370 assert_eq!(database.name, "Test DB");
371 assert_eq!(database.engine, "postgres");
372 assert!(database.is_full_sync);
373 assert!(!database.is_on_demand);
374 }
375
376 #[test]
377 fn test_database_table() {
378 let table = DatabaseTable {
379 id: TableId(1),
380 db_id: MetabaseId(1),
381 name: "users".to_string(),
382 schema: Some("public".to_string()),
383 display_name: "Users".to_string(),
384 active: true,
385 description: Some("User accounts".to_string()),
386 entity_type: Some("entity/UserTable".to_string()),
387 visibility_type: None,
388 created_at: None,
389 updated_at: None,
390 };
391
392 assert_eq!(table.name, "users");
393 assert_eq!(table.display_name, "Users");
394 assert!(table.active);
395 }
396
397 #[test]
398 fn test_database_field() {
399 let field = DatabaseField {
400 id: FieldId(1),
401 table_id: TableId(1),
402 name: "email".to_string(),
403 display_name: "Email".to_string(),
404 database_type: "VARCHAR(255)".to_string(),
405 base_type: "type/Text".to_string(),
406 semantic_type: Some("type/Email".to_string()),
407 active: true,
408 description: None,
409 is_pk: false,
410 fk_target_field_id: None,
411 position: 2,
412 visibility_type: None,
413 created_at: None,
414 updated_at: None,
415 };
416
417 assert_eq!(field.name, "email");
418 assert_eq!(field.base_type, "type/Text");
419 assert_eq!(field.semantic_type, Some("type/Email".to_string()));
420 assert!(!field.is_pk);
421 }
422
423 #[test]
424 fn test_create_database_request() {
425 let request = Database::builder("Production DB", "mysql")
426 .details(json!({
427 "host": "db.example.com",
428 "port": 3306,
429 "dbname": "production"
430 }))
431 .cache_schedule("0 0 * * *")
432 .build_request();
433
434 assert_eq!(request.name, "Production DB");
435 assert_eq!(request.engine, "mysql");
436 assert_eq!(request.is_full_sync, Some(true));
437 assert_eq!(
438 request.cache_field_values_schedule,
439 Some("0 0 * * *".to_string())
440 );
441 }
442
443 #[test]
444 fn test_update_database_request() {
445 let request = UpdateDatabaseRequest {
446 name: Some("Updated DB".to_string()),
447 is_full_sync: Some(false),
448 ..Default::default()
449 };
450
451 assert_eq!(request.name, Some("Updated DB".to_string()));
452 assert_eq!(request.is_full_sync, Some(false));
453 assert!(request.details.is_none());
454 }
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
461pub struct DatabaseMetadata {
462 pub id: MetabaseId,
464
465 pub name: String,
467
468 pub engine: String,
470
471 pub tables: Vec<TableMetadata>,
473
474 #[serde(default)]
476 pub features: Vec<String>,
477
478 #[serde(skip_serializing_if = "Option::is_none")]
480 pub native_permissions: Option<String>,
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct TableMetadata {
486 pub id: TableId,
488
489 pub name: String,
491
492 #[serde(skip_serializing_if = "Option::is_none")]
494 pub schema: Option<String>,
495
496 pub display_name: String,
498
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub description: Option<String>,
502
503 #[serde(skip_serializing_if = "Option::is_none")]
505 pub entity_type: Option<String>,
506
507 pub fields: Vec<FieldMetadata>,
509
510 #[serde(default = "default_true")]
512 pub active: bool,
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct FieldMetadata {
518 pub id: FieldId,
520
521 pub name: String,
523
524 pub display_name: String,
526
527 pub database_type: String,
529
530 pub base_type: String,
532
533 #[serde(skip_serializing_if = "Option::is_none")]
535 pub semantic_type: Option<String>,
536
537 #[serde(skip_serializing_if = "Option::is_none")]
539 pub description: Option<String>,
540
541 #[serde(default)]
543 pub is_pk: bool,
544
545 #[serde(skip_serializing_if = "Option::is_none")]
547 pub fk_target_field_id: Option<FieldId>,
548
549 pub position: i32,
551
552 #[serde(default = "default_true")]
554 pub active: bool,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct SyncResult {
560 pub id: String,
562
563 pub status: String,
565
566 #[serde(skip_serializing_if = "Option::is_none")]
568 pub message: Option<String>,
569
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub started_at: Option<DateTime<Utc>>,
573
574 #[serde(skip_serializing_if = "Option::is_none")]
576 pub completed_at: Option<DateTime<Utc>>,
577}