refactor: share the app custom-path scoping rule across its call sites

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-08-07 11:22:21 +02:00
parent c16ff9472d
commit c039e0f6ab
3 changed files with 20 additions and 11 deletions
@@ -6460,10 +6460,14 @@ async fn clone_apps(
if !preserve_identity {
downgrade_cloned_app_policy(&mut app.policy, authed);
}
// Off cloud a custom path addresses an app instance-wide (`create_app` rejects one
// already taken in any workspace), so a clone that kept it would make the parent's
// live public URL resolve to either row. Cloud scopes the lookup by workspace.
let custom_path = if *CLOUD_HOSTED { app.custom_path } else { None };
// An instance-wide custom path addresses one app (`create_app` rejects one already
// taken in any workspace), so a clone that kept it would make the parent's live
// public URL resolve to either row.
let custom_path = if windmill_common::apps::custom_path_is_workspace_scoped() {
app.custom_path
} else {
None
};
let new_app_id = sqlx::query_scalar!(
"INSERT INTO app (workspace_id, path, summary, policy, versions, extra_perms, custom_path)
VALUES ($1, $2, $3, $4, $5, $6, $7)
+5 -7
View File
@@ -56,7 +56,7 @@ use std::str;
use windmill_audit::audit_oss::{audit_log, AuditAuthorable};
use windmill_audit::ActionKind;
use windmill_common::{
apps::{AppScriptId, ListAppQuery, APP_WORKSPACED_ROUTE},
apps::{AppScriptId, ListAppQuery},
auth::TOKEN_PREFIX_LEN,
cache::{self, future::FutureCachedExt},
db::{DbWithOptAuthed, UserDB},
@@ -1114,13 +1114,13 @@ async fn custom_path_exists(
Extension(db): Extension<DB>,
Path((w_id, custom_path)): Path<(String, String)>,
) -> JsonResult<bool> {
let as_workspaced_route = APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed);
let scoped = windmill_common::apps::custom_path_is_workspace_scoped();
let exists =
sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))",
custom_path,
if *CLOUD_HOSTED || as_workspaced_route { Some(&w_id) } else { None }
if scoped { Some(&w_id) } else { None }
)
.fetch_one(&db)
.await?.unwrap_or(false);
@@ -2179,8 +2179,7 @@ async fn create_app_internal<'a>(
}
if let Some(custom_path) = &app.custom_path {
require_admin(authed.is_admin, &authed.username)?;
let scoped =
*CLOUD_HOSTED || APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed);
let scoped = windmill_common::apps::custom_path_is_workspace_scoped();
let conflict = sqlx::query!(
"SELECT workspace_id, path FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) LIMIT 1",
@@ -3055,8 +3054,7 @@ async fn update_app_internal<'a>(
if let Some(ncustom_path) = &ns.custom_path {
require_admin(authed.is_admin, &authed.username)?;
let scoped =
*CLOUD_HOSTED || APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed);
let scoped = windmill_common::apps::custom_path_is_workspace_scoped();
if ncustom_path.is_empty() {
sqlb.set("custom_path", "NULL");
+7
View File
@@ -18,6 +18,13 @@ lazy_static::lazy_static! {
pub static ref APP_WORKSPACED_ROUTE: AtomicBool = AtomicBool::new(false);
}
/// Whether an app's custom path names it within its workspace rather than instance-wide.
/// Every site that stores or resolves one must agree: a path stored under a narrower scope
/// than the resolver's makes two apps answer the same public URL.
pub fn custom_path_is_workspace_scoped() -> bool {
*crate::worker::CLOUD_HOSTED || APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed)
}
/// Traverse FlowValue while invoking provided by caller callback on leafs
// #[async_recursion::async_recursion(?Send)]
pub fn traverse_app_inline_scripts<