136 lines
4.1 KiB
Rust
136 lines
4.1 KiB
Rust
|
|
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<Settings>,
|
||
|
|
CurrentUser(user): CurrentUser,
|
||
|
|
AppDbConn(mut app_db): AppDbConn,
|
||
|
|
Path(PathParams { workspace_id }): Path<PathParams>,
|
||
|
|
navigator: Navigator,
|
||
|
|
State(mut pooler): State<WorkspacePooler>,
|
||
|
|
) -> Result<impl IntoResponse, AppError> {
|
||
|
|
// 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<RoleDisplay>,
|
||
|
|
conn_string: Secret<Url>,
|
||
|
|
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<RoleDisplay> = 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::<Vec<Result<_, _>>>()
|
||
|
|
.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::<Result<Vec<_>, _>>()?
|
||
|
|
.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::<Vec<Result<ServiceCredInfo, AppError>>>()
|
||
|
|
.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::<Result<Vec<ServiceCredInfo>, AppError>>()?;
|
||
|
|
|
||
|
|
#[derive(Template)]
|
||
|
|
#[template(path = "workspaces_single/service_credentials.html")]
|
||
|
|
struct ResponseTemplate {
|
||
|
|
service_cred_info: Vec<ServiceCredInfo>,
|
||
|
|
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()?,
|
||
|
|
))
|
||
|
|
}
|