use super::{PgPool, Uuid}; /// Insert an uploaded image record. #[tracing::instrument(skip_all)] pub async fn insert_image( pool: &PgPool, uploader_id: Uuid, community_id: Uuid, s3_key: &str, filename: &str, content_type: &str, size_bytes: i64, ) -> Result { sqlx::query_scalar!( "INSERT INTO images (uploader_id, community_id, s3_key, filename, content_type, size_bytes) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", uploader_id, community_id, s3_key, filename, content_type, size_bytes, ) .fetch_one(pool) .await } /// Mark an image as removed by a moderator. #[tracing::instrument(skip_all)] pub async fn remove_image<'e, E: sqlx::PgExecutor<'e>>( executor: E, image_id: Uuid, removed_by: Uuid, ) -> Result<(), sqlx::Error> { sqlx::query!( "UPDATE images SET removed_at = now(), removed_by = $2 WHERE id = $1 AND removed_at IS NULL", image_id, removed_by, ) .execute(executor) .await?; Ok(()) } /// Hard-delete an image row. Used to roll back the row inserted just before a /// failed S3 upload, the object never landed in the bucket, so there is /// nothing to reconcile and the row should not exist. #[tracing::instrument(skip_all)] pub async fn delete_image_row(pool: &PgPool, image_id: Uuid) -> Result<(), sqlx::Error> { sqlx::query!("DELETE FROM images WHERE id = $1", image_id) .execute(pool) .await?; Ok(()) } /// Mark images whose backing S3 object has been deleted, so the reconcile sweep /// never revisits them. Called inline after a successful best-effort delete and /// in batch by the background sweep. #[tracing::instrument(skip_all)] pub async fn mark_images_s3_purged(pool: &PgPool, image_ids: &[Uuid]) -> Result<(), sqlx::Error> { if image_ids.is_empty() { return Ok(()); } sqlx::query!( "UPDATE images SET s3_purged_at = now() WHERE id = ANY($1)", image_ids ) .execute(pool) .await?; Ok(()) }