docbox_management/tenant/
create_tenant.rs1use docbox_core::tenant::create_tenant::InitTenantError;
2use docbox_database::{
3 DbErr, DbPool, ROOT_DATABASE_NAME,
4 create::{create_database, create_restricted_role},
5 models::tenant::{Tenant, TenantId},
6};
7use docbox_search::SearchIndexFactory;
8use docbox_secrets::AppSecretManager;
9use docbox_storage::StorageLayerFactory;
10use serde::{Deserialize, Serialize};
11use serde_json::json;
12use thiserror::Error;
13
14use crate::{database::DatabaseProvider, password::random_password};
15
16#[derive(Debug, Error)]
17pub enum CreateTenantError {
18 #[error("error connecting to 'postgres' database: {0}")]
19 ConnectPostgres(DbErr),
20
21 #[error("error creating tenant database: {0}")]
22 CreateTenantDatabase(DbErr),
23
24 #[error("error connecting to tenant database: {0}")]
25 ConnectTenantDatabase(DbErr),
26
27 #[error("error connecting to root database: {0}")]
28 ConnectRootDatabase(DbErr),
29
30 #[error("error creating tenant database role: {0}")]
31 CreateTenantRole(DbErr),
32
33 #[error("error serializing tenant secret: {0}")]
34 SerializeSecret(serde_json::Error),
35
36 #[error("failed to create tenant secret: {0}")]
37 CreateTenantSecret(anyhow::Error),
38
39 #[error("failed to init tenant: {0}")]
40 CreateTenant(InitTenantError),
41}
42
43#[derive(Debug, Deserialize, Serialize, Clone)]
45pub struct CreateTenantConfig {
46 pub id: TenantId,
48 pub name: String,
50 pub env: String,
52
53 pub db_name: String,
55 pub db_secret_name: String,
58 pub db_role_name: String,
60
61 pub storage_bucket_name: String,
63 pub storage_cors_origins: Vec<String>,
65 pub storage_s3_queue_arn: Option<String>,
68
69 pub search_index_name: String,
71
72 pub event_queue_url: Option<String>,
74}
75
76pub async fn create_tenant(
77 db_provider: &impl DatabaseProvider,
78 search_factory: &SearchIndexFactory,
79 storage_factory: &StorageLayerFactory,
80 secrets: &AppSecretManager,
81 config: CreateTenantConfig,
82) -> Result<Tenant, CreateTenantError> {
83 let tenant_db = initialize_tenant_database(db_provider, &config.db_name).await?;
85 tracing::info!("created tenant database");
86
87 let db_role_password = random_password(30);
89
90 initialize_tenant_db_role(
91 &tenant_db,
92 &config.db_name,
93 &config.db_role_name,
94 &db_role_password,
95 )
96 .await?;
97 tracing::info!("created tenant user");
98
99 initialize_tenant_db_secret(
100 secrets,
101 &config.db_secret_name,
102 &config.db_role_name,
103 &db_role_password,
104 )
105 .await?;
106 tracing::info!("created tenant database secret");
107
108 let root_db = db_provider
110 .connect(ROOT_DATABASE_NAME)
111 .await
112 .map_err(CreateTenantError::ConnectRootDatabase)?;
113
114 let tenant = docbox_core::tenant::create_tenant::create_tenant(
116 &root_db,
117 &tenant_db,
118 search_factory,
119 storage_factory,
120 docbox_core::tenant::create_tenant::CreateTenant {
121 id: config.id,
122 name: config.name,
123 db_name: config.db_name,
124 db_secret_name: config.db_secret_name,
125 s3_name: config.storage_bucket_name,
126 os_index_name: config.search_index_name,
127 event_queue_url: config.event_queue_url,
128 origins: config.storage_cors_origins,
129 s3_queue_arn: config.storage_s3_queue_arn,
130 env: config.env,
131 },
132 )
133 .await
134 .map_err(CreateTenantError::CreateTenant)?;
135
136 Ok(tenant)
137}
138
139pub async fn initialize_tenant_database(
140 db_provider: &impl DatabaseProvider,
141 db_name: &str,
142) -> Result<DbPool, CreateTenantError> {
143 let db_postgres = db_provider
145 .connect("postgres")
146 .await
147 .map_err(CreateTenantError::ConnectPostgres)?;
148
149 if let Err(error) = create_database(&db_postgres, db_name).await {
151 if !error
152 .as_database_error()
153 .is_some_and(|err| err.code().is_some_and(|code| code.to_string().eq("42P04")))
154 {
155 return Err(CreateTenantError::CreateTenantDatabase(error));
156 }
157 }
158
159 let tenant_db = db_provider
161 .connect(db_name)
162 .await
163 .map_err(CreateTenantError::ConnectTenantDatabase)?;
164
165 Ok(tenant_db)
166}
167
168pub async fn initialize_tenant_db_role(
171 db: &DbPool,
172 db_name: &str,
173 role_name: &str,
174 role_password: &str,
175) -> Result<(), CreateTenantError> {
176 create_restricted_role(db, db_name, role_name, role_password)
178 .await
179 .map_err(CreateTenantError::CreateTenantRole)?;
180
181 Ok(())
182}
183
184pub async fn initialize_tenant_db_secret(
186 secrets: &AppSecretManager,
187 secret_name: &str,
188 role_name: &str,
189 role_password: &str,
190) -> Result<(), CreateTenantError> {
191 let secret_value = serde_json::to_string(&json!({
192 "username": role_name,
193 "password": role_password
194 }))
195 .map_err(CreateTenantError::SerializeSecret)?;
196
197 secrets
198 .set_secret(secret_name, &secret_value)
199 .await
200 .map_err(CreateTenantError::CreateTenantSecret)?;
201
202 Ok(())
203}