mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 00:47:53 +00:00
prevent self-parenting
This commit is contained in:
1 parent
2844314776
commit
3f2086d85b
2 files changed
+39
-13
No files matched your search
+22
-5
@@ -18,29 +18,46 @@ where
|
||||
|
||||
/// Set `maybe_parent_folder_token = new_parent_token` on every owned,
|
||||
/// live folder whose token is in `child_tokens` AND that isn't the parent
|
||||
/// itself. Returns the number of rows whose value actually changed.
|
||||
/// itself (a folder can't be its own parent). Returns the number of rows
|
||||
/// whose value actually changed.
|
||||
///
|
||||
/// Self-referential parenting is prevented at two layers:
|
||||
/// * Rust-side: the parent is filtered out of the candidate list
|
||||
/// before the SQL is built.
|
||||
/// * SQL-side: the WHERE clause includes `AND token != new_parent_token`
|
||||
/// as a defense-in-depth guard.
|
||||
pub async fn bulk_set_parent_folder<'e, 'c: 'e, E>(
|
||||
args: BulkSetParentFolderArgs<'e, 'c, E>,
|
||||
) -> Result<u64, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if args.child_tokens.is_empty() {
|
||||
// Drop the parent from the candidate list. This makes the function
|
||||
// self-protecting — callers don't have to remember to filter — and
|
||||
// pairs with the SQL `token != new_parent_token` guard below.
|
||||
let new_parent_str = args.new_parent_token.as_str();
|
||||
|
||||
let filtered_children: Vec<&FolderToken> = args.child_tokens
|
||||
.iter()
|
||||
.filter(|t| t.as_str() != new_parent_str)
|
||||
.collect();
|
||||
|
||||
if filtered_children.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut builder = QueryBuilder::<MySql>::new(
|
||||
"UPDATE folders SET maybe_parent_folder_token = ",
|
||||
);
|
||||
builder.push_bind(args.new_parent_token.as_str());
|
||||
builder.push_bind(new_parent_str);
|
||||
builder.push(" WHERE owner_user_token = ");
|
||||
builder.push_bind(args.owner_user_token.as_str());
|
||||
builder.push(" AND maybe_deleted_at IS NULL AND token != ");
|
||||
builder.push_bind(args.new_parent_token.as_str());
|
||||
builder.push_bind(new_parent_str);
|
||||
builder.push(" AND token IN (");
|
||||
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.child_tokens {
|
||||
for token in filtered_children {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
+17
-8
@@ -72,13 +72,25 @@ pub async fn bulk_add_subfolders_handler(
|
||||
warn!("Parent folder lookup failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
if parent.is_none() {
|
||||
return Err(CommonWebError::NotFound);
|
||||
}
|
||||
|
||||
let parent = match parent {
|
||||
Some(parent) => parent,
|
||||
None => return Err(CommonWebError::NotFound),
|
||||
};
|
||||
|
||||
// Drop any self-reference up front. A folder can't be its own parent;
|
||||
// reporting it as "accepted" would be a lie since the SQL guard
|
||||
// silently excludes it from the UPDATE.
|
||||
let parent_str = parent.token.as_str();
|
||||
|
||||
let candidates: Vec<FolderToken> = request.subfolder_tokens
|
||||
.iter()
|
||||
.filter(|t| t.as_str() != parent_str)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let accepted = filter_existing_owned_folder_tokens(FilterExistingOwnedFolderTokensArgs {
|
||||
candidate_tokens: &request.subfolder_tokens,
|
||||
candidate_tokens: &candidates,
|
||||
owner_user_token: &user_session.user_token,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
@@ -100,9 +112,6 @@ pub async fn bulk_add_subfolders_handler(
|
||||
})?;
|
||||
}
|
||||
|
||||
// Note: `accepted` may include the parent itself; the SQL guard excludes
|
||||
// it from the UPDATE. We still report it as "accepted" for client
|
||||
// simplicity — no-op tokens are normal in idempotent bulk endpoints.
|
||||
Ok(Json(BulkAddSubfoldersSuccessResponse {
|
||||
success: true,
|
||||
accepted_subfolder_tokens: accepted,
|
||||
|
||||
Reference in new issue
Block a user