v0.2.13
RustBox CI / rustbox (push) Canceled after 0s

This commit is contained in:
Mateusz Gruszczyński committed 2026-11-02 14:23:13 +01:00
1 parent 83440f20e2
commit 12e0c99850
32 files changed
+729 -109

No files matched your search

+62 -19
View File
@@ -11,7 +11,7 @@ use crate::auth::session::SessionState;
use crate::config::AppConfig;
use crate::db;
use crate::error::AppError;
use crate::storage::{BlobStorage, attachment_key};
use crate::storage::{self, BlobStorage, attachment_key};
#[derive(Debug, Serialize)]
struct UploadResponse {
@@ -73,7 +73,13 @@ pub async fn upload_attachment(
let chunk = match field.chunk().await {
Ok(chunk) => chunk,
Err(e) => {
let _ = writer.abort().await;
if writer.abort().await.is_err()
&& let Err(cleanup_error) =
storage::enqueue_unreferenced_delete(&conn, storage.as_ref(), &key)
.await
{
tracing::error!(error = %cleanup_error, storage_key = %key, "Failed to queue aborted attachment cleanup");
}
return Err(AppError::BadRequest(format!(
"Failed to read field data: {e}"
)));
@@ -82,7 +88,12 @@ pub async fn upload_attachment(
let Some(chunk) = chunk else { break };
size = size.saturating_add(chunk.len());
if size > max_attachment_size {
let _ = writer.abort().await;
if writer.abort().await.is_err()
&& let Err(cleanup_error) =
storage::enqueue_unreferenced_delete(&conn, storage.as_ref(), &key).await
{
tracing::error!(error = %cleanup_error, storage_key = %key, "Failed to queue oversized attachment cleanup");
}
return Err(AppError::BadRequest(format!(
"File '{}' exceeds maximum size of {} MiB",
filename, config.max_attachment_size_mb
@@ -90,18 +101,37 @@ pub async fn upload_attachment(
}
checksum.update(&chunk);
if let Err(e) = writer.write(chunk).await {
let _ = writer.abort().await;
if writer.abort().await.is_err()
&& let Err(cleanup_error) =
storage::enqueue_unreferenced_delete(&conn, storage.as_ref(), &key).await
{
tracing::error!(error = %cleanup_error, storage_key = %key, "Failed to queue interrupted attachment cleanup");
}
return Err(AppError::ServiceUnavailable(format!(
"Failed to upload attachment: {e}"
)));
}
}
let stat = writer.finish().await.map_err(|e| {
AppError::ServiceUnavailable(format!("Failed to finalize attachment upload: {e}"))
})?;
let stat = match writer.finish().await {
Ok(stat) => stat,
Err(e) => {
if let Err(cleanup_error) =
storage::enqueue_unreferenced_delete(&conn, storage.as_ref(), &key).await
{
tracing::error!(error = %cleanup_error, storage_key = %key, "Failed to queue failed-finalize attachment cleanup");
}
return Err(AppError::ServiceUnavailable(format!(
"Failed to finalize attachment upload: {e}"
)));
}
};
if stat.size != size as u64 {
let _ = storage.delete(&key).await;
if let Err(cleanup_error) =
storage::enqueue_unreferenced_delete(&conn, storage.as_ref(), &key).await
{
tracing::error!(error = %cleanup_error, storage_key = %key, "Failed to queue invalid attachment cleanup");
}
return Err(AppError::InternalError(format!(
"Attachment size verification failed for '{filename}'"
)));
@@ -118,7 +148,11 @@ pub async fn upload_attachment(
checksum_sha256: Some(&checksum),
};
if let Err(e) = db::drafts::add_draft_attachment(&conn, &attachment) {
let _ = storage.delete(&key).await;
if let Err(cleanup_error) =
storage::enqueue_unreferenced_delete(&conn, storage.as_ref(), &key).await
{
tracing::error!(error = %cleanup_error, storage_key = %key, "Failed to queue unattached blob cleanup");
}
return Err(AppError::InternalError(e));
}
@@ -180,21 +214,30 @@ pub async fn delete_attachment(
let conn = db::pool::open_user_db(&config.data_dir, &session.user_hash)
.map_err(|e| AppError::InternalError(format!("Failed to open database: {e}")))?;
let attachments =
db::drafts::get_draft_attachments(&conn, &draft_id).map_err(AppError::InternalError)?;
let attachment = attachments
.iter()
.find(|a| a.id == attachment_id)
.ok_or_else(|| AppError::NotFound("Attachment not found".to_string()))?;
let transaction = conn.unchecked_transaction().map_err(|e| {
AppError::InternalError(format!(
"Failed to start attachment delete transaction: {e}"
))
})?;
let attachment =
db::drafts::get_draft_attachment_for_update(&transaction, &draft_id, &attachment_id)
.map_err(AppError::InternalError)?
.ok_or_else(|| AppError::NotFound("Attachment not found".to_string()))?;
let storage_key = attachment.storage_key.clone();
let deleted = db::drafts::delete_draft_attachment(&conn, &attachment_id)
let cleanup_event = db::blob_delete_outbox::enqueue(&transaction, &storage_key)
.map_err(AppError::InternalError)?;
let deleted = db::drafts::delete_draft_attachment(&transaction, &attachment_id)
.map_err(AppError::InternalError)?;
if !deleted {
return Err(AppError::NotFound("Attachment not found".to_string()));
}
if let Err(error) = storage.delete(&storage_key).await {
tracing::warn!(error = %error, storage_key = %storage_key, "Failed to remove deleted attachment blob");
transaction.commit().map_err(|e| {
AppError::InternalError(format!("Failed to commit attachment deletion: {e}"))
})?;
if let Err(error) =
storage::process_queued_delete(&conn, storage.as_ref(), &cleanup_event).await
{
tracing::warn!(error = %error, storage_key = %storage_key, "Failed to process queued attachment blob deletion");
}
Ok(Json(DeleteResponse {