Skip to main content

haste_server/
tenants.rs

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}