Skip to main content

jax_daemon/database/
bucket_queries.rs

1use time::OffsetDateTime;
2use uuid::Uuid;
3
4use crate::database::{types::DCid, Database};
5use common::prelude::Link;
6
7/// Simple bucket info for UI display (from bucket_log)
8#[derive(Debug, Clone)]
9pub struct BucketInfo {
10    pub id: Uuid,
11    pub name: String,
12    pub link: Link,
13    pub created_at: OffsetDateTime,
14}
15
16/// Bucket log entry for history display
17#[derive(Debug, Clone)]
18pub struct BucketLogEntry {
19    pub bucket_id: Uuid,
20    pub name: String,
21    pub current_link: Link,
22    pub previous_link: Option<Link>,
23    pub height: u64,
24    pub published: bool,
25    pub created_at: OffsetDateTime,
26}
27
28impl Database {
29    /// Get bucket info by ID from the latest bucket_log entry
30    pub async fn get_bucket_info(&self, id: &Uuid) -> Result<Option<BucketInfo>, sqlx::Error> {
31        let id_str = id.to_string();
32        let row = sqlx::query!(
33            r#"
34            SELECT
35                bucket_id as "bucket_id!",
36                name as "name!",
37                current_link as "current_link!: DCid",
38                created_at as "created_at!"
39            FROM bucket_log
40            WHERE bucket_id = $1
41            ORDER BY height DESC
42            LIMIT 1
43            "#,
44            id_str
45        )
46        .fetch_optional(&**self)
47        .await?;
48
49        Ok(row.map(|r| BucketInfo {
50            id: Uuid::parse_str(&r.bucket_id).expect("invalid bucket_id UUID in database"),
51            name: r.name,
52            link: r.current_link.into(),
53            created_at: r.created_at,
54        }))
55    }
56
57    /// List all buckets from the latest bucket_log entries
58    pub async fn list_buckets(
59        &self,
60        prefix: Option<String>,
61        limit: Option<u32>,
62    ) -> Result<Vec<BucketInfo>, sqlx::Error> {
63        let limit_val = limit.unwrap_or(100).min(1000) as i64;
64        let pattern = prefix
65            .map(|p| format!("{}%", p))
66            .unwrap_or_else(|| "%".to_string());
67
68        let rows = sqlx::query!(
69            r#"
70            SELECT
71                bl.bucket_id as "bucket_id!",
72                bl.name as "name!",
73                bl.current_link as "current_link!: DCid",
74                MIN(bl.created_at) as "created_at!"
75            FROM bucket_log bl
76            INNER JOIN (
77                SELECT bucket_id, MAX(height) as max_height
78                FROM bucket_log
79                GROUP BY bucket_id
80            ) latest ON bl.bucket_id = latest.bucket_id AND bl.height = latest.max_height
81            WHERE bl.name LIKE ?1
82            GROUP BY bl.bucket_id, bl.name, bl.current_link
83            ORDER BY created_at DESC
84            LIMIT ?2
85            "#,
86            pattern,
87            limit_val
88        )
89        .fetch_all(&**self)
90        .await?;
91
92        Ok(rows
93            .into_iter()
94            .map(|r| BucketInfo {
95                id: Uuid::parse_str(&r.bucket_id).expect("invalid bucket_id UUID in database"),
96                name: r.name,
97                link: r.current_link.into(),
98                created_at: r.created_at,
99            })
100            .collect())
101    }
102
103    /// Get paginated bucket log entries for a specific bucket
104    pub async fn get_bucket_logs(
105        &self,
106        bucket_id: &Uuid,
107        page: u32,
108        page_size: u32,
109    ) -> Result<Vec<BucketLogEntry>, sqlx::Error> {
110        let bucket_id_str = bucket_id.to_string();
111        let limit = page_size.min(100) as i64;
112        let offset = (page * page_size) as i64;
113
114        let rows = sqlx::query!(
115            r#"
116            SELECT
117                bucket_id as "bucket_id!",
118                name as "name!",
119                current_link as "current_link!: DCid",
120                previous_link as "previous_link: DCid",
121                height as "height!",
122                published as "published!: bool",
123                created_at as "created_at!"
124            FROM bucket_log
125            WHERE bucket_id = ?1
126            ORDER BY height DESC
127            LIMIT ?2 OFFSET ?3
128            "#,
129            bucket_id_str,
130            limit,
131            offset
132        )
133        .fetch_all(&**self)
134        .await?;
135
136        Ok(rows
137            .into_iter()
138            .map(|r| BucketLogEntry {
139                bucket_id: Uuid::parse_str(&r.bucket_id)
140                    .expect("invalid bucket_id UUID in database"),
141                name: r.name,
142                current_link: r.current_link.into(),
143                previous_link: r.previous_link.map(Into::into),
144                height: r.height as u64,
145                published: r.published,
146                created_at: r.created_at,
147            })
148            .collect())
149    }
150
151    /// Get all bucket log entries for a specific bucket (unpaginated, for tree view)
152    pub async fn get_all_bucket_logs(
153        &self,
154        bucket_id: &Uuid,
155    ) -> Result<Vec<BucketLogEntry>, sqlx::Error> {
156        let bucket_id_str = bucket_id.to_string();
157
158        let rows = sqlx::query!(
159            r#"
160            SELECT
161                bucket_id as "bucket_id!",
162                name as "name!",
163                current_link as "current_link!: DCid",
164                previous_link as "previous_link: DCid",
165                height as "height!",
166                published as "published!: bool",
167                created_at as "created_at!"
168            FROM bucket_log
169            WHERE bucket_id = ?1
170            ORDER BY height ASC
171            "#,
172            bucket_id_str
173        )
174        .fetch_all(&**self)
175        .await?;
176
177        Ok(rows
178            .into_iter()
179            .map(|r| BucketLogEntry {
180                bucket_id: Uuid::parse_str(&r.bucket_id)
181                    .expect("invalid bucket_id UUID in database"),
182                name: r.name,
183                current_link: r.current_link.into(),
184                previous_link: r.previous_link.map(Into::into),
185                height: r.height as u64,
186                published: r.published,
187                created_at: r.created_at,
188            })
189            .collect())
190    }
191
192    /// Get total count of log entries for a bucket
193    pub async fn get_bucket_log_count(&self, bucket_id: &Uuid) -> Result<i64, sqlx::Error> {
194        let bucket_id_str = bucket_id.to_string();
195
196        let result = sqlx::query!(
197            r#"
198            SELECT COUNT(*) as "count!"
199            FROM bucket_log
200            WHERE bucket_id = ?1
201            "#,
202            bucket_id_str
203        )
204        .fetch_one(&**self)
205        .await?;
206
207        Ok(result.count)
208    }
209}