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