Skip to content

Commit 672dd8a

Browse files
committed
feat: database tenant update functions
1 parent 1813d79 commit 672dd8a

4 files changed

Lines changed: 217 additions & 31 deletions

File tree

packages/docbox-database/src/models/tenant.rs

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use crate::{DbExecutor, DbResult};
2-
use serde::Serialize;
2+
use serde::{Deserialize, Serialize};
33
use sqlx::prelude::FromRow;
44
use uuid::Uuid;
55

66
pub type TenantId = Uuid;
77

8+
use crate::utils::update_if_some;
9+
810
#[derive(Debug, Clone, FromRow, Serialize, PartialEq, Eq)]
911
pub struct Tenant {
1012
/// Unique ID for the tenant
@@ -30,6 +32,8 @@ pub struct Tenant {
3032
pub event_queue_url: Option<String>,
3133
}
3234

35+
/// Structure for fields required when creating a
36+
/// tenant within the database
3337
pub struct CreateTenant {
3438
pub id: TenantId,
3539
pub name: String,
@@ -42,6 +46,21 @@ pub struct CreateTenant {
4246
pub env: String,
4347
}
4448

49+
/// Bulk update for tenant fields
50+
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
51+
#[serde(default)]
52+
pub struct UpdateTenant {
53+
pub id: Option<TenantId>,
54+
pub name: Option<String>,
55+
pub db_name: Option<String>,
56+
pub db_secret_name: Option<Option<String>>,
57+
pub db_iam_user_name: Option<Option<String>>,
58+
pub s3_name: Option<String>,
59+
pub os_index_name: Option<String>,
60+
pub env: Option<String>,
61+
pub event_queue_url: Option<Option<String>>,
62+
}
63+
4564
impl Tenant {
4665
/// Create a new tenant
4766
pub async fn create(db: impl DbExecutor<'_>, create: CreateTenant) -> DbResult<Tenant> {
@@ -87,35 +106,67 @@ impl Tenant {
87106
}
88107

89108
/// Update the "db_iam_user_name" property of the tenant
90-
pub async fn set_db_iam_user_name(
109+
pub async fn update(
91110
&mut self,
92111
db: impl DbExecutor<'_>,
93-
iam_user_name: Option<String>,
94-
) -> DbResult<Option<Tenant>> {
95-
sqlx::query_as(
96-
r#"UPDATE "docbox_tenants" SET "db_iam_user_name" = $3 WHERE "id" = $1 AND "env" = $2"#,
112+
UpdateTenant {
113+
id,
114+
name,
115+
db_name,
116+
db_secret_name,
117+
db_iam_user_name,
118+
s3_name,
119+
os_index_name,
120+
env,
121+
event_queue_url,
122+
}: UpdateTenant,
123+
) -> DbResult<()> {
124+
sqlx::query(
125+
r#"
126+
UPDATE "docbox_tenants"
127+
SET
128+
"id" = COALESCE($3, "id"),
129+
"name" = COALESCE($4, "name"),
130+
"db_name" = COALESCE($5, "db_name"),
131+
"db_secret_name" = COALESCE($6, "db_secret_name"),
132+
"db_iam_user_name" = COALESCE($7, "db_iam_user_name"),
133+
"s3_name" = COALESCE($8, "s3_name"),
134+
"os_index_name" = COALESCE($9, "os_index_name"),
135+
"env" = COALESCE($10, "env"),
136+
"event_queue_url" = COALESCE($11, "event_queue_url")
137+
WHERE "id" = $1 AND "env" = $2
138+
"#,
97139
)
140+
//
98141
.bind(self.id)
99142
.bind(self.env.clone())
100-
.bind(iam_user_name)
143+
//
144+
.bind(id)
145+
.bind(name.clone())
146+
.bind(db_name.clone())
147+
.bind(db_secret_name.clone())
148+
.bind(db_iam_user_name.clone())
149+
.bind(s3_name.clone())
150+
.bind(os_index_name.clone())
151+
.bind(env.clone())
152+
.bind(event_queue_url.clone())
101153
.fetch_optional(db)
102-
.await
103-
}
154+
.await?;
104155

105-
/// Update the "db_secret_name" property of the tenant
106-
pub async fn set_db_secret_name(
107-
&mut self,
108-
db: impl DbExecutor<'_>,
109-
db_secret_name: Option<String>,
110-
) -> DbResult<Option<Tenant>> {
111-
sqlx::query_as(
112-
r#"UPDATE "docbox_tenants" SET "db_secret_name" = $3 WHERE "id" = $1 AND "env" = $2"#,
113-
)
114-
.bind(self.id)
115-
.bind(self.env.clone())
116-
.bind(db_secret_name)
117-
.fetch_optional(db)
118-
.await
156+
update_if_some!(
157+
self,
158+
id,
159+
name,
160+
db_name,
161+
db_secret_name,
162+
db_iam_user_name,
163+
s3_name,
164+
os_index_name,
165+
env,
166+
event_queue_url,
167+
);
168+
169+
Ok(())
119170
}
120171

121172
/// Find a tenant by `id` within a specific `env`

packages/docbox-database/src/utils.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,15 @@ impl DatabaseErrorExt for DbErr {
7676
.is_some_and(|error| error.is_restrict())
7777
}
7878
}
79+
80+
macro_rules! update_if_some {
81+
($self:expr, $($field:ident),+ $(,)?) => {
82+
$(
83+
if let Some(value) = $field {
84+
$self.$field = value;
85+
}
86+
)+
87+
};
88+
}
89+
90+
pub(crate) use update_if_some;

packages/docbox-database/tests/tenant.rs

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use docbox_database::{
2-
models::tenant::{CreateTenant, Tenant},
2+
models::tenant::{CreateTenant, Tenant, UpdateTenant},
33
utils::DatabaseErrorExt,
44
};
55
use uuid::Uuid;
@@ -418,3 +418,122 @@ async fn test_delete_tenant() {
418418
.unwrap();
419419
assert!(result.is_some());
420420
}
421+
422+
/// Tests that a tenant can be created
423+
#[tokio::test]
424+
async fn test_update_tenant() {
425+
let (db, _db_container) = test_root_db().await;
426+
427+
// Create a secondary tenant to ensure updates aren't applied to it
428+
let secondary_tenant_id = Uuid::new_v4();
429+
let secondary_tenant = Tenant::create(
430+
&db,
431+
CreateTenant {
432+
id: secondary_tenant_id,
433+
name: "dont-match-test".to_string(),
434+
db_name: "dont-match-test".to_string(),
435+
db_secret_name: Some("dont-match-test".to_string()),
436+
db_iam_user_name: None,
437+
s3_name: "dont-match-test".to_string(),
438+
os_index_name: "dont-match-test".to_string(),
439+
event_queue_url: None,
440+
env: "Development".to_string(),
441+
},
442+
)
443+
.await
444+
.unwrap();
445+
446+
let tenant_id = Uuid::new_v4();
447+
448+
let mut tenant = Tenant::create(
449+
&db,
450+
CreateTenant {
451+
id: tenant_id,
452+
name: "test".to_string(),
453+
db_name: "test".to_string(),
454+
db_secret_name: Some("test".to_string()),
455+
db_iam_user_name: None,
456+
s3_name: "test".to_string(),
457+
os_index_name: "test".to_string(),
458+
event_queue_url: None,
459+
env: "Development".to_string(),
460+
},
461+
)
462+
.await
463+
.unwrap();
464+
465+
let original_tenant = tenant.clone();
466+
let new_tenant_id = Uuid::new_v4();
467+
468+
// Update the tenant ID
469+
tenant
470+
.update(
471+
&db,
472+
UpdateTenant {
473+
id: Some(new_tenant_id),
474+
..Default::default()
475+
},
476+
)
477+
.await
478+
.unwrap();
479+
480+
assert_eq!(tenant.id, new_tenant_id);
481+
assert_ne!(tenant, original_tenant);
482+
483+
// Should be able to query the updated tenant and get back the same one we have after the update
484+
let found_tenant = Tenant::find_by_id(&db, tenant.id, &tenant.env)
485+
.await
486+
.unwrap()
487+
.expect("expected to find tenant");
488+
assert_eq!(found_tenant, tenant);
489+
490+
// Update other tenant fields
491+
tenant
492+
.update(
493+
&db,
494+
UpdateTenant {
495+
id: Some(new_tenant_id),
496+
name: Some("test-name-2".to_string()),
497+
db_name: Some("test-db-name-2".to_string()),
498+
db_secret_name: Some(Some("test-secret-name".to_string())),
499+
db_iam_user_name: Some(Some("test-iam-user-name-2".to_string())),
500+
s3_name: Some("test-s3-name-2".to_string()),
501+
os_index_name: Some("test-search-2".to_string()),
502+
event_queue_url: Some(Some("test-event-queue-2".to_string())),
503+
env: Some("Production".to_string()),
504+
},
505+
)
506+
.await
507+
.unwrap();
508+
509+
assert_eq!(tenant.id, new_tenant_id);
510+
assert_eq!(tenant.name, "test-name-2");
511+
assert_eq!(tenant.db_name, "test-db-name-2");
512+
assert_eq!(tenant.db_secret_name, Some("test-secret-name".to_string()));
513+
assert_eq!(
514+
tenant.db_iam_user_name,
515+
Some("test-iam-user-name-2".to_string())
516+
);
517+
assert_eq!(tenant.s3_name, "test-s3-name-2");
518+
assert_eq!(tenant.os_index_name, "test-search-2");
519+
assert_eq!(
520+
tenant.event_queue_url,
521+
Some("test-event-queue-2".to_string())
522+
);
523+
assert_eq!(tenant.env, "Production");
524+
525+
// Should be able to query the updated tenant and get back the same one we have after the update
526+
let found_tenant = Tenant::find_by_id(&db, tenant.id, &tenant.env)
527+
.await
528+
.unwrap()
529+
.expect("expected to find tenant");
530+
assert_eq!(found_tenant, tenant);
531+
532+
// Secondary tenant should not have been changed
533+
let found_secondary_tenant =
534+
Tenant::find_by_id(&db, secondary_tenant.id, &secondary_tenant.env)
535+
.await
536+
.unwrap()
537+
.expect("expected to find tenant");
538+
assert_eq!(found_secondary_tenant, secondary_tenant);
539+
}

packages/docbox-management/src/tenant/migrate_tenant_secret_to_iam.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::database::{DatabaseProvider, close_pool_on_drop};
22
use docbox_core::{
33
database::{
4-
DbErr, DbSecrets, ROOT_DATABASE_NAME, create::make_role_iam_only, models::tenant::Tenant,
4+
DbErr, DbSecrets, ROOT_DATABASE_NAME,
5+
create::make_role_iam_only,
6+
models::tenant::{Tenant, UpdateTenant},
57
},
68
secrets::{SecretManager, SecretManagerError},
79
};
@@ -54,12 +56,14 @@ pub async fn migrate_tenant_secret_to_iam(
5456
.map_err(MigrateIAMError::MakeRoleIAM)?;
5557

5658
tenant
57-
.set_db_iam_user_name(&root_db, Some(secret.username.clone()))
58-
.await
59-
.map_err(MigrateIAMError::UpdateTenant)?;
60-
61-
tenant
62-
.set_db_secret_name(&root_db, None)
59+
.update(
60+
&root_db,
61+
UpdateTenant {
62+
db_iam_user_name: Some(Some(secret.username.clone())),
63+
db_secret_name: Some(None),
64+
..Default::default()
65+
},
66+
)
6367
.await
6468
.map_err(MigrateIAMError::UpdateTenant)?;
6569

0 commit comments

Comments
 (0)