2025-09-14 16:19:44 -04:00
|
|
|
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::{
|
2025-09-23 13:08:51 -07:00
|
|
|
app::AppDbConn,
|
|
|
|
|
errors::{AppError, forbidden},
|
2025-09-14 16:19:44 -04:00
|
|
|
navigator::Navigator,
|
|
|
|
|
settings::Settings,
|
|
|
|
|
user::CurrentUser,
|
2025-09-23 13:08:51 -07:00
|
|
|
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
2025-09-14 16:19:44 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[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(),
|
2025-10-01 22:36:19 -07:00
|
|
|
_created_by text default current_user,
|
2025-09-14 16:19:44 -04:00
|
|
|
_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())
|
|
|
|
|
}
|