use askama::Template; use axum::{ debug_handler, extract::{Path, State}, response::{Html, IntoResponse}, }; use futures::{lock::Mutex, prelude::*, stream}; use interim_models::{service_cred::ServiceCred, workspace::Workspace}; use interim_pgtypes::pg_role::RoleTree; use redact::Secret; use serde::Deserialize; use url::Url; use uuid::Uuid; use crate::{ app::{App, AppDbConn}, errors::AppError, navigator::Navigator, roles::RoleDisplay, settings::Settings, user::CurrentUser, workspace_nav::WorkspaceNav, workspace_pooler::{RoleAssignment, WorkspacePooler}, }; #[derive(Debug, Deserialize)] pub(super) struct PathParams { workspace_id: Uuid, } /// HTTP GET handler for the page at which a user manages their service /// credentials for a workspace. #[debug_handler(state = App)] pub(super) async fn get( State(settings): State, CurrentUser(user): CurrentUser, AppDbConn(mut app_db): AppDbConn, Path(PathParams { workspace_id }): Path, navigator: Navigator, State(mut pooler): State, ) -> Result { // FIXME: auth let workspace = Workspace::with_id(workspace_id) .fetch_one(&mut app_db) .await?; let cluster = workspace.fetch_cluster(&mut app_db).await?; // Mutex is required to use client in async closures. let workspace_client = Mutex::new( pooler .acquire_for(workspace_id, RoleAssignment::User(user.id)) .await?, ); struct ServiceCredInfo { service_cred: ServiceCred, member_of: Vec, conn_string: Secret, conn_string_redacted: String, } let service_cred_info = stream::iter( ServiceCred::belonging_to_user(user.id) .fetch_all(&mut app_db) .await?, ) .then(async |cred| { let member_of: Vec = stream::iter({ let mut locked_client = workspace_client.lock().await; let tree = RoleTree::granted_to_rolname(&cred.rolname) .fetch_tree(&mut locked_client) .await?; tree.unwrap().flatten_inherited() }) .then(async |role| { tracing::debug!("111"); let mut locked_client = workspace_client.lock().await; RoleDisplay::from_rolname(&role.rolname, &mut locked_client).await }) .collect::>>() .await .into_iter() // [`futures::stream::StreamExt::collect`] can only collect to types // that implement [`Default`], so we must do result handling with the // sync version of `collect()`. .collect::, _>>()? .into_iter() .flatten() .collect(); tracing::debug!("222"); let conn_string = cluster.conn_str_for_db( &workspace.db_name, Some(( cred.rolname.as_str(), Secret::new(cred.password.expose_secret().as_str()), )), )?; Ok(ServiceCredInfo { conn_string, conn_string_redacted: "postgresql://********".to_owned(), member_of, service_cred: cred, }) }) .collect::>>() .await // [`futures::stream::StreamExt::collect`] can only collect to types that // implement [`Default`], so we must do result handling with the sync // version of `collect()`. .into_iter() .collect::, AppError>>()?; #[derive(Template)] #[template(path = "workspaces_single/service_credentials.html")] struct ResponseTemplate { service_cred_info: Vec, settings: Settings, workspace_nav: WorkspaceNav, } Ok(Html( ResponseTemplate { workspace_nav: WorkspaceNav::builder() .navigator(navigator) .workspace(workspace.clone()) .populate_rels(&mut app_db, &mut *workspace_client.lock().await) .await? .build()?, service_cred_info, settings, } .render()?, )) }