1
0
Fork 0
forked from 2sys/phonograph
phonograph/interim-server/src/routes/relations_single/update_values_handler.rs

113 lines
3.2 KiB
Rust
Raw Normal View History

2025-09-23 13:15:53 -07:00
use std::collections::HashMap;
use axum::{
Json, debug_handler,
extract::{Path, State},
response::{IntoResponse as _, Response},
};
use interim_models::{
accessors::{Accessor, Actor, portal::PortalAccessor},
datum::Datum,
};
use interim_pgtypes::{
escape_identifier, pg_acl::PgPrivilegeType, pg_attribute::PgAttribute, pg_class::PgClass,
};
2025-09-23 13:15:53 -07:00
use serde::Deserialize;
use serde_json::json;
2025-10-16 06:40:11 +00:00
use sqlx::{Acquire as _, postgres::types::Oid, query};
2025-09-23 13:15:53 -07:00
use uuid::Uuid;
use crate::{
app::{App, AppDbConn},
errors::{AppError, forbidden},
user::CurrentUser,
workspace_pooler::{RoleAssignment, WorkspacePooler},
};
#[derive(Debug, Deserialize)]
pub(super) struct PathParams {
portal_id: Uuid,
rel_oid: u32,
workspace_id: Uuid,
}
#[derive(Debug, Deserialize)]
pub(super) struct FormBody {
2025-10-16 06:40:11 +00:00
cells: Vec<CellInfo>,
}
#[derive(Debug, Deserialize)]
pub(super) struct CellInfo {
2025-09-23 13:15:53 -07:00
column: String,
2025-10-16 06:40:11 +00:00
pkey: HashMap<String, Datum>,
2025-09-23 13:15:53 -07:00
value: Datum,
}
2025-10-16 06:40:11 +00:00
/// HTTP POST handler for updating cell values in a backing Postgres table.
2025-09-23 13:15:53 -07:00
///
/// This handler expects 3 path parameters with the structure described by
/// [`PathParams`].
#[debug_handler(state = App)]
pub(super) async fn post(
State(mut workspace_pooler): State<WorkspacePooler>,
AppDbConn(mut app_db): AppDbConn,
CurrentUser(user): CurrentUser,
Path(PathParams {
portal_id,
rel_oid,
workspace_id,
}): Path<PathParams>,
2025-10-16 06:40:11 +00:00
Json(form): Json<FormBody>,
2025-09-23 13:15:53 -07:00
) -> Result<Response, AppError> {
2025-10-01 22:36:19 -07:00
// Prevent users from modifying Phonograph metadata columns.
2025-10-16 06:40:11 +00:00
if form.cells.iter().any(|cell| cell.column.starts_with('_')) {
2025-10-01 22:36:19 -07:00
return Err(forbidden!("access denied to update system metadata column"));
}
2025-09-23 13:15:53 -07:00
let mut workspace_client = workspace_pooler
.acquire_for(workspace_id, RoleAssignment::User(user.id))
2025-09-23 13:15:53 -07:00
.await?;
let rel = PgClass::with_oid(Oid(rel_oid))
2025-09-23 13:15:53 -07:00
.fetch_one(&mut workspace_client)
.await?;
// For authorization only.
let _portal = PortalAccessor::new()
.id(portal_id)
.as_actor(Actor::User(user.id))
.verify_workspace_id(workspace_id)
.verify_rel_oid(Oid(rel_oid))
.verify_rel_permissions([PgPrivilegeType::Update])
.using_rel(&rel)
.using_app_db(&mut app_db)
.using_workspace_client(&mut workspace_client)
.fetch_one()
.await?;
2025-09-23 13:15:53 -07:00
let pkey_attrs = PgAttribute::pkeys_for_rel(Oid(rel_oid))
.fetch_all(&mut workspace_client)
.await?;
2025-10-16 06:40:11 +00:00
let conn = workspace_client.get_conn();
let mut txn = conn.begin().await?;
for cell in form.cells {
// TODO: simplify pkey management
cell.pkey
.get(&pkey_attrs.first().unwrap().attname)
.unwrap()
.clone()
.bind_onto(cell.value.bind_onto(query(&format!(
"update {ident} set {value_col} = $1 where {pkey_col} = $2",
ident = rel.get_identifier(),
value_col = escape_identifier(&cell.column),
pkey_col = escape_identifier(&pkey_attrs.first().unwrap().attname),
))))
.execute(&mut *txn)
.await?;
}
txn.commit().await?;
2025-09-23 13:15:53 -07:00
Ok(Json(json!({ "ok": true })).into_response())
}