1use crate::{
2 auth_n::oidc::utilities::set_user_password, fhir_client::ServerCTX, services::AppState,
3};
4use haste_fhir_client::FHIRClient;
5use haste_fhir_model::r4::generated::{
6 resources::{Project, Resource, ResourceType, User},
7 terminology::IssueType,
8 types::FHIRString,
9};
10use haste_fhir_operation_error::OperationOutcomeError;
11use haste_fhir_search::SearchEngine;
12use haste_fhir_terminology::FHIRTerminology;
13use haste_jwt::{ProjectId, TenantId, claims::SubscriptionTier};
14use haste_repository::{
15 Repository,
16 admin::TenantAuthAdmin,
17 types::{
18 tenant::{CreateTenant, Tenant},
19 user::CreateUser,
20 },
21 utilities::generate_id,
22};
23use std::sync::Arc;
24
25pub async fn create_user<
26 Repo: Repository + Send + Sync + 'static,
27 Search: SearchEngine + Send + Sync + 'static,
28 Terminology: FHIRTerminology + Send + Sync + 'static,
29>(
30 services: &AppState<Repo, Search, Terminology>,
31 tenant: &TenantId,
32 user_resource: User,
33 password: Option<&str>,
34) -> Result<User, OperationOutcomeError> {
35 let ctx = Arc::new(ServerCTX::system(
36 tenant.clone(),
37 ProjectId::System,
38 services.fhir_client.clone(),
39 services.rate_limit.clone(),
40 ));
41
42 let user = services
43 .fhir_client
44 .create(ctx, ResourceType::User, Resource::User(user_resource))
45 .await?;
46
47 let user = match user {
48 Resource::User(user) => user,
49 _ => panic!("Created resource is not a User"),
50 };
51
52 let user_id = user.id.clone().unwrap();
53
54 if let Some(password) = password {
55 set_user_password(
56 &*services.repo,
57 &tenant,
58 &user
59 .email
60 .as_ref()
61 .and_then(|e| e.value.as_ref())
62 .map(|s| s.to_string())
63 .unwrap_or_default(),
64 &user_id,
65 password,
66 )
67 .await?;
68 }
69
70 Ok(user)
71}
72
73pub struct CreateTenantOutput {
74 pub tenant: Tenant,
75 pub owner: haste_repository::types::user::User,
76}
77
78pub async fn create_tenant<
79 Repo: Repository + Send + Sync + 'static,
80 Search: SearchEngine + Send + Sync + 'static,
81 Terminology: FHIRTerminology + Send + Sync + 'static,
82>(
83 services: &AppState<Repo, Search, Terminology>,
84 tenant_id: Option<String>,
85 _name: &str,
86 subscription_tier: &SubscriptionTier,
87 owner: haste_fhir_model::r4::generated::resources::User,
88 owner_password: Option<&str>,
89) -> Result<CreateTenantOutput, OperationOutcomeError> {
90 let services = services.transaction().await?;
91
92 let new_tenant = TenantAuthAdmin::create(
93 &*services.repo,
94 &TenantId::System,
95 CreateTenant {
96 id: Some(TenantId::new(tenant_id.unwrap_or(generate_id(Some(16))))),
97 subscription_tier: Some(subscription_tier.clone().into()),
98 },
99 )
100 .await?;
101
102 services
103 .fhir_client
104 .create(
105 Arc::new(ServerCTX::system(
106 new_tenant.id.clone(),
107 ProjectId::System,
108 services.fhir_client.clone(),
109 services.rate_limit.clone(),
110 )),
111 ResourceType::Project,
112 Resource::Project(Project {
113 id: Some(ProjectId::System.to_string()),
114 name: Box::new(FHIRString {
115 value: Some(ProjectId::System.to_string()),
116 ..Default::default()
117 }),
118 fhirVersion: Box::new(
119 haste_fhir_model::r4::generated::terminology::SupportedFhirVersion::R4(None),
120 ),
121 ..Default::default()
122 }),
123 )
124 .await?;
125
126 let user = create_user(&services, &new_tenant.id, owner, owner_password).await?;
127
128 let Some(user_id) = user.id else {
129 return Err(OperationOutcomeError::fatal(
130 IssueType::Invalid(None),
131 "The user ID is required to complete the tenant creation process.".to_string(),
132 ));
133 };
134
135 let Some(user) = TenantAuthAdmin::<CreateUser, _, _, _, _>::read(
136 services.repo.as_ref(),
137 &new_tenant.id,
138 &user_id,
139 )
140 .await?
141 else {
142 return Err(OperationOutcomeError::fatal(
143 IssueType::Invalid(None),
144 "The user does not exist after creation.".to_string(),
145 ));
146 };
147
148 services.commit().await?;
149
150 Ok(CreateTenantOutput {
151 tenant: new_tenant,
152 owner: user,
153 })
154}