mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
feat: delete user drafts when their underlying item is deleted
This commit is contained in:
+49
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM draft\n WHERE workspace_id = $1\n AND email = $2\n AND path = $3\n AND typ = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "draft_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"flow",
|
||||
"app",
|
||||
"raw_app",
|
||||
"resource",
|
||||
"variable",
|
||||
"trigger_schedule",
|
||||
"trigger_webhook",
|
||||
"trigger_default_email",
|
||||
"trigger_email",
|
||||
"trigger_http",
|
||||
"trigger_websocket",
|
||||
"trigger_postgres",
|
||||
"trigger_kafka",
|
||||
"trigger_nats",
|
||||
"trigger_mqtt",
|
||||
"trigger_sqs",
|
||||
"trigger_gcp",
|
||||
"trigger_azure",
|
||||
"trigger_poll",
|
||||
"trigger_cli",
|
||||
"trigger_nextcloud",
|
||||
"trigger_google",
|
||||
"trigger_github"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a15ff27845c017aa201210147f6a4fd63a2be3646aac508214cbfab065f3328c"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ IN ('app', 'raw_app')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cbe8fb4935908a7eb9a0b56b1d6f330cd3c8ef1ca692147210a36e56946f7ef6"
|
||||
}
|
||||
@@ -25,7 +25,9 @@ use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{Error, JsonResult, Result},
|
||||
schedule::Schedule,
|
||||
user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery},
|
||||
user_drafts::{
|
||||
delete_user_draft, maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery,
|
||||
},
|
||||
utils::{
|
||||
escape_ilike_pattern, not_found_if_none, paginate, Pagination, ScheduleType, StripPath,
|
||||
},
|
||||
@@ -1119,6 +1121,17 @@ async fn delete_schedule(
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
// Clean up the authed user's per-user draft for this schedule path.
|
||||
// Idempotent on no-draft.
|
||||
delete_user_draft(
|
||||
&db,
|
||||
&w_id,
|
||||
&authed.email,
|
||||
UserDraftItemKind::TriggerSchedule,
|
||||
path,
|
||||
)
|
||||
.await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
|
||||
@@ -1634,8 +1634,10 @@ async fn delete_app(
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
// Cover both `app` and `raw_app` draft kinds — the `app` table backs
|
||||
// both, and the old `typ = 'app'`-only clause leaked raw-app drafts.
|
||||
sqlx::query!(
|
||||
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'",
|
||||
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ IN ('app', 'raw_app')",
|
||||
path,
|
||||
&w_id
|
||||
)
|
||||
|
||||
@@ -161,6 +161,36 @@ fn deep_merge(target: &mut serde_json::Value, source: serde_json::Value) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the authed user's draft for `(workspace, kind, path)`.
|
||||
/// Idempotent — returns Ok even when no row exists. Scoped to a single
|
||||
/// email so other users' drafts at the same path are untouched.
|
||||
///
|
||||
/// Called from item delete handlers (`delete_script_by_path`,
|
||||
/// `delete_flow_by_path`, etc.) so the user can't be left with a stale
|
||||
/// per-user draft after the underlying item is gone.
|
||||
pub async fn delete_user_draft(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
email: &str,
|
||||
kind: UserDraftItemKind,
|
||||
path: &str,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
r#"DELETE FROM draft
|
||||
WHERE workspace_id = $1
|
||||
AND email = $2
|
||||
AND path = $3
|
||||
AND typ = $4"#,
|
||||
w_id,
|
||||
email,
|
||||
path,
|
||||
kind as UserDraftItemKind,
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch the authed user's draft as a standalone payload, used by
|
||||
/// "get by path" routes when no deployed row exists at the path but a
|
||||
/// draft might. Returns the draft JSON wrapped as `WithDraftOverlay`
|
||||
|
||||
@@ -43,7 +43,9 @@ use windmill_common::{
|
||||
db::{DbWithOptAuthed, UserDB},
|
||||
error::{self, Error, JsonResult, Result},
|
||||
get_database_url,
|
||||
user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery},
|
||||
user_drafts::{
|
||||
delete_user_draft, maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery,
|
||||
},
|
||||
utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath},
|
||||
variables,
|
||||
worker::{CLOUD_HOSTED, WINDMILL_DIR},
|
||||
@@ -1104,6 +1106,20 @@ async fn delete_resource(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
// Clean up the authed user's per-user drafts for this resource path
|
||||
// and any linked variables we cascaded into. Idempotent on no-draft.
|
||||
delete_user_draft(&db, &w_id, &authed.email, UserDraftItemKind::Resource, path).await?;
|
||||
for var_path in &deleted_linked_variables {
|
||||
delete_user_draft(
|
||||
&db,
|
||||
&w_id,
|
||||
&authed.email,
|
||||
UserDraftItemKind::Variable,
|
||||
var_path,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
|
||||
@@ -35,7 +35,7 @@ use windmill_common::{
|
||||
db::{DbWithOptAuthed, UserDB},
|
||||
error::{Error, JsonResult, Result},
|
||||
scripts::ScriptHash,
|
||||
user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay},
|
||||
user_drafts::{delete_user_draft, maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay},
|
||||
utils::{not_found_if_none, paginate, Pagination, StripPath, WarnAfterExt},
|
||||
variables::{
|
||||
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
|
||||
@@ -656,6 +656,15 @@ async fn delete_variable(
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
// Clean up the authed user's per-user drafts for this path so they
|
||||
// aren't left dangling after the underlying item is gone. Idempotent
|
||||
// on the no-draft case. Resource is included because variables
|
||||
// cascade-delete linked resource rows at the same path.
|
||||
delete_user_draft(&db, &w_id, &authed.email, UserDraftItemKind::Variable, path).await?;
|
||||
if deleted_linked_resource.is_some() {
|
||||
delete_user_draft(&db, &w_id, &authed.email, UserDraftItemKind::Resource, path).await?;
|
||||
}
|
||||
|
||||
// If variable was a secret, also delete from Vault backend (if configured)
|
||||
if is_secret {
|
||||
delete_secret_from_backend(&db, &w_id, path).await?;
|
||||
|
||||
@@ -16,7 +16,9 @@ use windmill_api_auth::{check_scopes, ApiAuthed};
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{Error, JsonResult, Result},
|
||||
user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery},
|
||||
user_drafts::{
|
||||
delete_user_draft, maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery,
|
||||
},
|
||||
utils::{paginate, Pagination, StripPath},
|
||||
worker::CLOUD_HOSTED,
|
||||
DB,
|
||||
@@ -727,6 +729,7 @@ async fn delete_trigger<T: TriggerCrud>(
|
||||
Extension(handler): Extension<Arc<T>>,
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((workspace_id, path)): Path<(String, StripPath)>,
|
||||
) -> Result<String> {
|
||||
let path = path.to_path();
|
||||
@@ -784,6 +787,19 @@ async fn delete_trigger<T: TriggerCrud>(
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
// Clean up the authed user's per-user draft for this trigger path.
|
||||
// The draft kind is derived from the impl via TriggerCrud, mirroring
|
||||
// the lookup `maybe_overlay_draft` uses on get-by-path. Idempotent on
|
||||
// no-draft.
|
||||
delete_user_draft(
|
||||
&db,
|
||||
&workspace_id,
|
||||
&authed.email,
|
||||
T::user_draft_item_kind(),
|
||||
path,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(format!("Trigger '{}' deleted", path))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user