Skip to main content

max / makenotwork

2.1 KB · 73 lines History Blame Raw
1 //! image rows and the pending-S3-purge queue
2
3 use super::{DateTime, PgPool, Utc, Uuid};
4
5 #[derive(sqlx::FromRow)]
6 pub struct ImageRow {
7 pub id: Uuid,
8 pub community_id: Uuid,
9 pub s3_key: String,
10 pub content_type: String,
11 pub removed_at: Option<DateTime<Utc>>,
12 }
13
14 /// Fetch an image by ID (for serving). Carries `community_id` so the serve
15 /// handler can enforce the owning community's access policy.
16 #[tracing::instrument(skip_all)]
17 pub async fn get_image(pool: &PgPool, image_id: Uuid) -> Result<Option<ImageRow>, sqlx::Error> {
18 sqlx::query_as!(
19 ImageRow,
20 r#"SELECT id, community_id, s3_key, content_type,
21 removed_at AS "removed_at: chrono::DateTime<chrono::Utc>"
22 FROM images WHERE id = $1"#,
23 image_id,
24 )
25 .fetch_optional(pool)
26 .await
27 }
28
29 /// A removed image whose backing S3 object still needs to be purged.
30 #[derive(sqlx::FromRow)]
31 pub struct PendingPurgeImage {
32 pub id: Uuid,
33 pub s3_key: String,
34 }
35
36 /// List removed images whose S3 object has not been purged yet, oldest first.
37 /// Drives the background reconcile sweep; `limit` bounds one batch (S3's
38 /// batch-delete cap is 1000).
39 #[tracing::instrument(skip_all)]
40 pub async fn list_images_pending_s3_purge(
41 pool: &PgPool,
42 limit: i64,
43 ) -> Result<Vec<PendingPurgeImage>, sqlx::Error> {
44 sqlx::query_as!(
45 PendingPurgeImage,
46 "SELECT id, s3_key FROM images
47 WHERE removed_at IS NOT NULL AND s3_purged_at IS NULL
48 ORDER BY removed_at ASC
49 LIMIT $1",
50 limit,
51 )
52 .fetch_all(pool)
53 .await
54 }
55
56 /// Count images uploaded by a user in the last N seconds (rate limiting).
57 #[tracing::instrument(skip_all)]
58 pub async fn count_recent_uploads_by_user(
59 pool: &PgPool,
60 user_id: Uuid,
61 window_secs: i64,
62 ) -> Result<i64, sqlx::Error> {
63 sqlx::query_scalar!(
64 r#"SELECT COUNT(*) AS "count!" FROM images
65 WHERE uploader_id = $1
66 AND created_at > now() - make_interval(secs => $2::float8)"#,
67 user_id,
68 window_secs as f64,
69 )
70 .fetch_one(pool)
71 .await
72 }
73