| 248 |
248 |
|
if !ok {
|
| 249 |
249 |
|
return Ok(false);
|
| 250 |
250 |
|
}
|
|
251 |
+ |
// Enqueue the OLD object for deletion in the SAME tx as the row UPDATE,
|
|
252 |
+ |
// closing the crash-between-commit-and-enqueue orphan window (ultra-fuzz
|
|
253 |
+ |
// Run #1 Storage LOW). is_s3_key_live guards the worker if a row still
|
|
254 |
+ |
// references it.
|
|
255 |
+ |
if let Some(old_key) = old_key_to_delete.as_deref() {
|
|
256 |
+ |
db::pending_s3_deletions::enqueue_deletions(
|
|
257 |
+ |
&mut *tx,
|
|
258 |
+ |
&[(old_key.to_string(), "main".to_string())],
|
|
259 |
+ |
"project_image_replace",
|
|
260 |
+ |
)
|
|
261 |
+ |
.await?;
|
|
262 |
+ |
}
|
| 251 |
263 |
|
tx.commit().await?;
|
| 252 |
264 |
|
Ok(true)
|
| 253 |
265 |
|
}
|
| 271 |
283 |
|
// Clear the pending upload record now that the upload is committed
|
| 272 |
284 |
|
db::pending_uploads::remove_pending_upload(&state.db, user.id, &req.s3_key, "main").await?;
|
| 273 |
285 |
|
|
| 274 |
|
- |
// Enqueue old S3 object for durable deletion now that the new URL is committed.
|
| 275 |
|
- |
if let Some(old_key) = old_key_to_delete
|
| 276 |
|
- |
&& let Err(e) = db::pending_s3_deletions::enqueue_deletions(
|
| 277 |
|
- |
&state.db,
|
| 278 |
|
- |
&[(old_key.clone(), "main".to_string())],
|
| 279 |
|
- |
"project_image_replace",
|
| 280 |
|
- |
).await
|
| 281 |
|
- |
{
|
| 282 |
|
- |
tracing::warn!(key = %old_key, error = ?e, "failed to enqueue old project image for deletion");
|
| 283 |
|
- |
}
|
|
286 |
+ |
// (The old S3 object was enqueued for deletion inside the commit tx above.)
|
| 284 |
287 |
|
|
| 285 |
288 |
|
// Scan enqueue AFTER the DB write commits (Phase 5 chronic fix — the same
|
| 286 |
289 |
|
// ordering rule that uploads/versions/media follow via `commit_upload`).
|
| 473 |
476 |
|
if !ok {
|
| 474 |
477 |
|
return Ok(false);
|
| 475 |
478 |
|
}
|
|
479 |
+ |
// Enqueue the OLD cover for deletion in the SAME tx as the row UPDATE
|
|
480 |
+ |
// (ultra-fuzz Run #1 Storage LOW; matches the in-tx ordering the delete
|
|
481 |
+ |
// paths use). is_s3_key_live guards the worker against a live reference.
|
|
482 |
+ |
if let Some(old_key) = old_key_to_delete.as_deref() {
|
|
483 |
+ |
db::pending_s3_deletions::enqueue_deletions(
|
|
484 |
+ |
&mut *tx,
|
|
485 |
+ |
&[(old_key.to_string(), "main".to_string())],
|
|
486 |
+ |
"item_image_replace",
|
|
487 |
+ |
)
|
|
488 |
+ |
.await?;
|
|
489 |
+ |
}
|
| 476 |
490 |
|
tx.commit().await?;
|
| 477 |
491 |
|
Ok(true)
|
| 478 |
492 |
|
}
|
| 494 |
508 |
|
|
| 495 |
509 |
|
db::pending_uploads::remove_pending_upload(&state.db, user.id, &req.s3_key, "main").await?;
|
| 496 |
510 |
|
|
| 497 |
|
- |
if let Some(old_key) = old_key_to_delete
|
| 498 |
|
- |
&& let Err(e) = db::pending_s3_deletions::enqueue_deletions(
|
| 499 |
|
- |
&state.db,
|
| 500 |
|
- |
&[(old_key.clone(), "main".to_string())],
|
| 501 |
|
- |
"item_image_replace",
|
| 502 |
|
- |
).await
|
| 503 |
|
- |
{
|
| 504 |
|
- |
tracing::warn!(key = %old_key, error = ?e, "failed to enqueue old item cover for deletion");
|
| 505 |
|
- |
}
|
|
511 |
+ |
// (The old cover was enqueued for deletion inside the commit tx above.)
|
| 506 |
512 |
|
|
| 507 |
513 |
|
// Scan enqueue + scan_status flip AFTER the DB write — same ordering rule
|
| 508 |
514 |
|
// as uploads/versions/media. The Run #6 audit caught this same bug here.
|