phonograph/interim-server/src/routes/workspaces_single/add_table_handler.rs

110 lines
3.4 KiB
Rust
Raw Normal View History

use axum::{
extract::{Path, State},
response::IntoResponse,
};
use interim_models::workspace_user_perm::{self, WorkspaceUserPerm};
use interim_pgtypes::escape_identifier;
use serde::Deserialize;
use sqlx::query;
use uuid::Uuid;
use crate::{
app::AppDbConn,
errors::{AppError, forbidden},
navigator::Navigator,
settings::Settings,
user::CurrentUser,
workspace_pooler::{RoleAssignment, WorkspacePooler},
};
#[derive(Debug, Deserialize)]
pub(super) struct PathParams {
workspace_id: Uuid,
}
/// HTTP POST handler for creating a managed Postgres table within a workspace
/// database. Upon success, it redirects the client back to the workspace
/// homepage, which is expected to display a list of available tables including
/// the newly created one.
///
/// This handler expects 1 path parameter named `workspace_id` which should
/// deserialize to a UUID.
pub(super) async fn post(
State(settings): State<Settings>,
State(mut pooler): State<WorkspacePooler>,
CurrentUser(user): CurrentUser,
navigator: Navigator,
AppDbConn(mut app_db): AppDbConn,
Path(PathParams { workspace_id }): Path<PathParams>,
) -> Result<impl IntoResponse, AppError> {
// Check workspace authorization.
let workspace_perms = WorkspaceUserPerm::belonging_to_user(user.id)
.fetch_all(&mut app_db)
.await?;
if workspace_perms.iter().all(|p| {
p.workspace_id != workspace_id || p.perm != workspace_user_perm::PermissionValue::Connect
}) {
return Err(forbidden!("access denied to workspace"));
}
let mut workspace_client = pooler
// FIXME: Should this be scoped down to the unprivileged role after
// setting up the table owner?
.acquire_for(workspace_id, RoleAssignment::Root)
.await?;
let table_owner_rolname = format!("table_owner_{0}", Uuid::new_v4().simple());
query(&format!(
"create role {0}",
escape_identifier(&table_owner_rolname),
))
.execute(workspace_client.get_conn())
.await?;
query(&format!(
"grant {0} to {1} with admin option",
escape_identifier(&table_owner_rolname),
escape_identifier(&format!(
"{0}{1}",
settings.db_role_prefix,
user.id.simple()
))
))
.execute(workspace_client.get_conn())
.await?;
query(&format!(
"grant create, usage on schema {0} to {1}",
escape_identifier(&settings.phono_table_namespace),
escape_identifier(&table_owner_rolname),
))
.execute(workspace_client.get_conn())
.await?;
const TABLE_NAME: &str = "untitled";
query(&format!(
r#"
create table {0}.{1} (
_id uuid primary key not null default uuidv7(),
_created_by text not null default current_user,
_created_at timestamptz not null default now(),
_form_session uuid,
_form_backlink_portal uuid,
_form_backlink_row uuid,
notes text
)
"#,
escape_identifier(&settings.phono_table_namespace),
escape_identifier(TABLE_NAME),
))
.execute(workspace_client.get_conn())
.await?;
query(&format!(
"alter table {0}.{1} owner to {2}",
escape_identifier(&settings.phono_table_namespace),
escape_identifier(TABLE_NAME),
escape_identifier(&table_owner_rolname)
))
.execute(workspace_client.get_conn())
.await?;
Ok(navigator.workspace_page(workspace_id).redirect_to())
}