phonograph/interim-server/src/routes/relations_single/update_rel_name_handler.rs

87 lines
2.3 KiB
Rust
Raw Normal View History

2025-10-01 22:36:19 -07:00
use std::sync::LazyLock;
use axum::{
debug_handler,
extract::{Path, State},
response::Response,
};
use interim_pgtypes::{escape_identifier, pg_class::PgClass};
use regex::Regex;
use serde::Deserialize;
use sqlx::{postgres::types::Oid, query};
use uuid::Uuid;
use validator::Validate;
use crate::{
app::{App, AppDbConn},
2025-10-22 00:43:53 -07:00
errors::AppError,
2025-10-01 22:36:19 -07:00
extractors::ValidatedForm,
navigator::{Navigator, NavigatorPage as _},
user::CurrentUser,
workspace_pooler::WorkspacePooler,
};
static RE_REL_NAME: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[a-z][a-z0-9_]*$").unwrap());
#[derive(Debug, Deserialize)]
pub(super) struct PathParams {
rel_oid: u32,
workspace_id: Uuid,
}
#[derive(Debug, Deserialize, Validate)]
pub(super) struct FormBody {
#[validate(regex(path = *RE_REL_NAME))]
name: String,
}
/// HTTP POST handler for updating a relation's name.
///
/// Currently, names must begin with a letter and may only contain lowercase
/// alphanumeric characters and underscores.
#[debug_handler(state = App)]
pub(super) async fn post(
AppDbConn(mut app_db): AppDbConn,
State(mut pooler): State<WorkspacePooler>,
CurrentUser(user): CurrentUser,
navigator: Navigator,
Path(PathParams {
rel_oid,
workspace_id,
}): Path<PathParams>,
ValidatedForm(FormBody { name }): ValidatedForm<FormBody>,
) -> Result<Response, AppError> {
2025-10-22 00:43:53 -07:00
// FIXME: Check workspace authorization.
2025-10-01 22:36:19 -07:00
let mut workspace_client = pooler
.acquire_for(
workspace_id,
crate::workspace_pooler::RoleAssignment::User(user.id),
)
.await?;
let rel = PgClass::with_oid(Oid(rel_oid))
.fetch_one(&mut workspace_client)
.await?;
// FIXME ensure that user has ownership of the table.
// TODO: move this to a function in `interim-pgtypes`.
query(&format!(
"alter table {ident} rename to {name_esc}",
ident = rel.get_identifier(),
// `_esc` suffixes to make sure that the macro won't fall back to
// similarly named variable(s) in scope if anything inadvertently
// changes.
name_esc = escape_identifier(&name)
))
.execute(workspace_client.get_conn())
.await?;
Ok(navigator
.rel_settings_page()
.workspace_id(workspace_id)
.rel_oid(Oid(rel_oid))
.build()?
.redirect_to())
}