Rust AppView - highly experimental!

chore: rearranging parakeet-db

+8 -8
consumer/src/database_writer/bulk_processor.rs
··· 295 295 if let RecordTypes::AppBskyFeedPost(ref post) = record { 296 296 // Resolve parent author 297 297 let parent_author = if let Some(ref reply) = post.reply { 298 - let parent_did = parakeet_db::at_uri_util::extract_did(&reply.parent.uri); 298 + let parent_did = parakeet_db::utils::at_uri::extract_did(&reply.parent.uri); 299 299 if let Some(did) = parent_did { 300 300 let (aid, _, _) = crate::db::operations::feed::get_actor_id(conn, did).await?; 301 301 Some(aid) ··· 309 309 // Resolve root author (if different from parent) 310 310 let root_author = if let Some(ref reply) = post.reply { 311 311 if reply.root.uri != reply.parent.uri { 312 - let root_did = parakeet_db::at_uri_util::extract_did(&reply.root.uri); 312 + let root_did = parakeet_db::utils::at_uri::extract_did(&reply.root.uri); 313 313 if let Some(did) = root_did { 314 314 let (aid, _, _) = crate::db::operations::feed::get_actor_id(conn, did).await?; 315 315 Some(aid) ··· 333 333 }; 334 334 335 335 if let Some(uri) = quote_uri { 336 - let quoted_did = parakeet_db::at_uri_util::extract_did(uri); 336 + let quoted_did = parakeet_db::utils::at_uri::extract_did(uri); 337 337 if let Some(did) = quoted_did { 338 338 let (aid, _, _) = crate::db::operations::feed::get_actor_id(conn, did).await?; 339 339 Some(aid) ··· 372 372 // Resolve via_repost natural key if present (for likes and reposts that came via a repost) 373 373 let via_repost_key = if let (Some(via_uri), Some(via_cid)) = (&refs.via_uri, &refs.via_cid) { 374 374 // Extract the DID and rkey from the via URI 375 - if let Some((via_did, via_rkey, _collection)) = parakeet_db::at_uri_util::parse_at_uri(via_uri) { 375 + if let Some((via_did, via_rkey, _collection)) = parakeet_db::utils::at_uri::parse_at_uri(via_uri) { 376 376 // Ensure the via repost actor exists 377 377 let (via_actor_id, _, _) = crate::db::operations::feed::get_actor_id(conn, via_did).await?; 378 378 ··· 488 488 let labeler_dids: Vec<&str> = labeler_likes.iter() 489 489 .filter_map(|&i| { 490 490 let uri = &likes[i].record.subject.uri; 491 - parakeet_db::at_uri_util::extract_did(uri) 491 + parakeet_db::utils::at_uri::extract_did(uri) 492 492 }) 493 493 .collect(); 494 494 let resolved_labelers = if !labeler_dids.is_empty() { ··· 568 568 } else if subject_uri.contains("/app.bsky.labeler.service/") { 569 569 // Labeler like - only include if labeler exists (no auto-stubbing) 570 570 // Extract DID from labeler AT URI 571 - if let Some(labeler_did) = parakeet_db::at_uri_util::extract_did(subject_uri) { 571 + if let Some(labeler_did) = parakeet_db::utils::at_uri::extract_did(subject_uri) { 572 572 if let Some(&labeler_actor_id) = resolved_labelers.get(labeler_did) { 573 573 labeler_like_data.push(LabelerLikeCopyData { 574 574 actor_id, ··· 697 697 698 698 // Get CID digest (real CID for reposts) 699 699 let cid_bytes = repost.cid.to_bytes(); 700 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 700 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 701 701 .ok_or_else(|| eyre::eyre!("Invalid CID for repost"))?; 702 702 703 703 // Resolve via_repost natural keys if present ··· 857 857 858 858 // Extract CID digest 859 859 let cid_bytes = cid.to_bytes(); 860 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 860 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 861 861 .expect("Valid CID should have digest"); 862 862 863 863 // Resolve parent and root post natural keys
+14 -14
consumer/src/database_writer/bulk_types.rs
··· 194 194 pub tokens: Vec<String>, // Search tokens 195 195 196 196 // Composite type columns (stored directly in posts table) 197 - pub ext_embed: Option<parakeet_db::composite_types::ExtEmbed>, 198 - pub video_embed: Option<parakeet_db::composite_types::VideoEmbed>, 199 - pub image_1: Option<parakeet_db::composite_types::ImageEmbed>, 200 - pub image_2: Option<parakeet_db::composite_types::ImageEmbed>, 201 - pub image_3: Option<parakeet_db::composite_types::ImageEmbed>, 202 - pub image_4: Option<parakeet_db::composite_types::ImageEmbed>, 197 + pub ext_embed: Option<parakeet_db::composite::ExtEmbed>, 198 + pub video_embed: Option<parakeet_db::composite::VideoEmbed>, 199 + pub image_1: Option<parakeet_db::composite::ImageEmbed>, 200 + pub image_2: Option<parakeet_db::composite::ImageEmbed>, 201 + pub image_3: Option<parakeet_db::composite::ImageEmbed>, 202 + pub image_4: Option<parakeet_db::composite::ImageEmbed>, 203 203 pub embedded_post_actor_id: Option<i32>, // For quote posts (natural key part 1) 204 204 pub embedded_post_rkey: Option<i64>, // For quote posts (natural key part 2) 205 205 pub record_detached: Option<bool>, // For quote posts 206 - pub facet_1: Option<parakeet_db::composite_types::FacetEmbed>, 207 - pub facet_2: Option<parakeet_db::composite_types::FacetEmbed>, 208 - pub facet_3: Option<parakeet_db::composite_types::FacetEmbed>, 209 - pub facet_4: Option<parakeet_db::composite_types::FacetEmbed>, 210 - pub facet_5: Option<parakeet_db::composite_types::FacetEmbed>, 211 - pub facet_6: Option<parakeet_db::composite_types::FacetEmbed>, 212 - pub facet_7: Option<parakeet_db::composite_types::FacetEmbed>, 213 - pub facet_8: Option<parakeet_db::composite_types::FacetEmbed>, 206 + pub facet_1: Option<parakeet_db::composite::FacetEmbed>, 207 + pub facet_2: Option<parakeet_db::composite::FacetEmbed>, 208 + pub facet_3: Option<parakeet_db::composite::FacetEmbed>, 209 + pub facet_4: Option<parakeet_db::composite::FacetEmbed>, 210 + pub facet_5: Option<parakeet_db::composite::FacetEmbed>, 211 + pub facet_6: Option<parakeet_db::composite::FacetEmbed>, 212 + pub facet_7: Option<parakeet_db::composite::FacetEmbed>, 213 + pub facet_8: Option<parakeet_db::composite::FacetEmbed>, 214 214 pub mentions: Option<Vec<Option<i32>>>, // Mention actor IDs array 215 215 } 216 216
+5 -5
consumer/src/database_writer/operations/executor.rs
··· 425 425 // Parse reason_subject URI early for thread mute check and notification 426 426 // Format: at://did:plc:xyz/app.bsky.feed.post/rkey 427 427 let subject_rkey_str = reason_subject.split('/').next_back().unwrap_or(""); 428 - let subject_rkey_i64 = match parakeet_db::tid_util::decode_tid(subject_rkey_str) { 428 + let subject_rkey_i64 = match parakeet_db::utils::tid::decode_tid(subject_rkey_str) { 429 429 Ok(rkey) => rkey, 430 430 Err(e) => { 431 431 tracing::warn!("Invalid subject TID in reason_subject: {} - {}", subject_rkey_str, e); ··· 486 486 // Create PostgreSQL notification for this ancestor 487 487 // Extract rkey from reply_uri (at://did/app.bsky.feed.post/rkey) 488 488 let reply_rkey_str = reply_uri.split('/').next_back().unwrap_or(""); 489 - let reply_rkey_i64 = match parakeet_db::tid_util::decode_tid(reply_rkey_str) { 489 + let reply_rkey_i64 = match parakeet_db::utils::tid::decode_tid(reply_rkey_str) { 490 490 Ok(rkey) => rkey, 491 491 Err(e) => { 492 492 tracing::warn!("Invalid reply TID: {} - {}", reply_rkey_str, e); ··· 504 504 let cid_digest = match Cid::try_from(cid.as_str()) { 505 505 Ok(cid_obj) => { 506 506 let cid_bytes = cid_obj.to_bytes(); 507 - match parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes) { 507 + match parakeet_db::utils::cid::cid_to_digest_owned(&cid_bytes) { 508 508 Some(digest) => digest, 509 509 None => { 510 510 tracing::warn!("Invalid CID digest for CID: {}", cid); ··· 940 940 } 941 941 CollectionType::BskyFeedGen => { 942 942 // Feedgens use arbitrary string rkeys - extract from at_uri 943 - let rkey_str = parakeet_db::at_uri_util::extract_rkey(&at_uri) 943 + let rkey_str = parakeet_db::utils::at_uri::extract_rkey(&at_uri) 944 944 .ok_or_else(|| eyre::eyre!("Invalid at_uri: missing rkey"))?; 945 945 let rows = db::feedgen_delete(conn, actor_id, rkey_str).await?; 946 946 if rows > 0 { ··· 955 955 } 956 956 CollectionType::BskyList => { 957 957 // Lists use arbitrary string rkeys - extract from at_uri 958 - let rkey_str = parakeet_db::at_uri_util::extract_rkey(&at_uri) 958 + let rkey_str = parakeet_db::utils::at_uri::extract_rkey(&at_uri) 959 959 .ok_or_else(|| eyre::eyre!("Invalid at_uri: missing rkey"))?; 960 960 961 961 let rows = db::list_delete(conn, actor_id, rkey_str).await?;
+1 -1
consumer/src/database_writer/operations/handlers/follow.rs
··· 39 39 let target_actor_id = ctx.subject_actor_id.expect("subject_actor_id required for notification"); 40 40 41 41 // Extract CID digest (32 bytes) 42 - let cid_digest = parakeet_db::cid_util::cid_to_digest_owned(&ctx.cid.to_bytes()) 42 + let cid_digest = parakeet_db::utils::cid::cid_to_digest_owned(&ctx.cid.to_bytes()) 43 43 .expect("Valid CID should have digest"); 44 44 45 45 operations.push(crate::database_writer::operations::create_follow_notification(
+2 -2
consumer/src/database_writer/operations/handlers/like.rs
··· 39 39 // Create notification for the post author (if post author actor is known) 40 40 if let Some(post_author_actor_id) = ctx.subject_actor_id { 41 41 // Extract rkey from the post URI and convert to i64 42 - if let Some(post_rkey_str) = parakeet_db::at_uri_util::extract_rkey(&subject_uri) { 43 - if let Ok(post_rkey_i64) = parakeet_db::tid_util::decode_tid(post_rkey_str) { 42 + if let Some(post_rkey_str) = parakeet_db::utils::at_uri::extract_rkey(&subject_uri) { 43 + if let Ok(post_rkey_i64) = parakeet_db::utils::tid::decode_tid(post_rkey_str) { 44 44 // Generate synthetic CID digest 45 45 operations.push(crate::database_writer::operations::create_like_notification( 46 46 rkey_i64, // Already converted above
+7 -7
consumer/src/database_writer/operations/handlers/post.rs
··· 73 73 .expect("TID validation passed, conversion should succeed"); 74 74 75 75 // Extract CID digest (32 bytes) - will be used for notifications 76 - let cid_digest = parakeet_db::cid_util::cid_to_digest_owned(&cid.to_bytes()) 76 + let cid_digest = parakeet_db::utils::cid::cid_to_digest_owned(&cid.to_bytes()) 77 77 .expect("Valid CID should have digest"); 78 78 79 79 // Create notification for the quoted post author ··· 82 82 // Don't notify if quoting self 83 83 if quoted_author_id != actor_id { 84 84 // Extract rkey from quoted post URI and convert to i64 85 - if let Some(quoted_rkey_str) = parakeet_db::at_uri_util::extract_rkey(embed_uri) { 86 - if let Ok(quoted_rkey_i64) = parakeet_db::tid_util::decode_tid(quoted_rkey_str) { 85 + if let Some(quoted_rkey_str) = parakeet_db::utils::at_uri::extract_rkey(embed_uri) { 86 + if let Ok(quoted_rkey_i64) = parakeet_db::utils::tid::decode_tid(quoted_rkey_str) { 87 87 operations.push(crate::database_writer::operations::create_quote_notification( 88 88 rkey_i64, // Already converted above 89 89 actor_id, ··· 126 126 let mut notified_actor_ids = std::collections::HashSet::new(); 127 127 128 128 // Extract rkeys from parent and root URIs and convert to i64 129 - let parent_rkey_str = parakeet_db::at_uri_util::extract_rkey(parent_uri); 130 - let root_rkey_str = maybe_root.as_ref().and_then(|uri| parakeet_db::at_uri_util::extract_rkey(uri)); 129 + let parent_rkey_str = parakeet_db::utils::at_uri::extract_rkey(parent_uri); 130 + let root_rkey_str = maybe_root.as_ref().and_then(|uri| parakeet_db::utils::at_uri::extract_rkey(uri)); 131 131 132 132 if let Some(parent_rkey_str) = parent_rkey_str { 133 - if let Ok(parent_rkey_i64) = parakeet_db::tid_util::decode_tid(parent_rkey_str) { 133 + if let Ok(parent_rkey_i64) = parakeet_db::utils::tid::decode_tid(parent_rkey_str) { 134 134 // Convert root rkey to i64 if present 135 - let root_rkey_i64 = root_rkey_str.and_then(|s| parakeet_db::tid_util::decode_tid(s).ok()); 135 + let root_rkey_i64 = root_rkey_str.and_then(|s| parakeet_db::utils::tid::decode_tid(s).ok()); 136 136 137 137 // Always notify direct parent author 138 138 if let Some(parent_author_id) = post_ctx.parent_author_actor_id {
+3 -3
consumer/src/database_writer/operations/handlers/repost.rs
··· 39 39 // Create notification for the post author (if post author actor is known) 40 40 if let Some(post_author_actor_id) = ctx.subject_actor_id { 41 41 // Extract rkey from the post URI and convert to i64 42 - if let Some(post_rkey_str) = parakeet_db::at_uri_util::extract_rkey(&subject_uri) { 43 - if let Ok(post_rkey_i64) = parakeet_db::tid_util::decode_tid(post_rkey_str) { 42 + if let Some(post_rkey_str) = parakeet_db::utils::at_uri::extract_rkey(&subject_uri) { 43 + if let Ok(post_rkey_i64) = parakeet_db::utils::tid::decode_tid(post_rkey_str) { 44 44 // Extract CID digest (32 bytes) 45 - let cid_digest = parakeet_db::cid_util::cid_to_digest_owned(&ctx.cid.to_bytes()) 45 + let cid_digest = parakeet_db::utils::cid::cid_to_digest_owned(&ctx.cid.to_bytes()) 46 46 .expect("Valid CID should have digest"); 47 47 48 48 operations.push(crate::database_writer::operations::create_repost_notification(
+2 -2
consumer/src/database_writer/operations/notifications.rs
··· 9 9 10 10 use super::DatabaseOperation; 11 11 use chrono::{DateTime, Utc}; 12 - use parakeet_db::notifications::reasons; 12 + use parakeet_db::domain::notification::reasons; 13 13 14 14 /// Create a notification for a like 15 15 /// ··· 35 35 created_at: DateTime<Utc>, 36 36 ) -> DatabaseOperation { 37 37 // Generate synthetic CID digest for the like 38 - let cid_digest = parakeet_db::cid_util::generate_like_cid_digest(liker_actor_id, like_rkey); 38 + let cid_digest = parakeet_db::utils::cid::generate_like_cid_digest(liker_actor_id, like_rkey); 39 39 40 40 DatabaseOperation::InsertNotification { 41 41 recipient_actor_id: post_author_actor_id,
+8 -8
consumer/src/database_writer/reference_extraction.rs
··· 126 126 // Like: extract author DID from liked post URI and via repost URI 127 127 // NOTE: Likes don't have subject_actor_id FK in DB, but we need it for notifications 128 128 RecordTypes::AppBskyFeedLike(rec) => { 129 - let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) { 129 + let mut refs = if let Some(subject_did) = parakeet_db::utils::at_uri::extract_did(rec.subject.uri.as_str()) { 130 130 RecordReferences::with_subject(subject_did.to_string()) 131 131 } else { 132 132 RecordReferences::empty() ··· 144 144 // Repost: extract author DID from reposted post URI and via repost URI 145 145 // NOTE: Reposts don't have subject_actor_id FK in DB, but we need it for notifications 146 146 RecordTypes::AppBskyFeedRepost(rec) => { 147 - let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) { 147 + let mut refs = if let Some(subject_did) = parakeet_db::utils::at_uri::extract_did(rec.subject.uri.as_str()) { 148 148 RecordReferences::with_subject(subject_did.to_string()) 149 149 } else { 150 150 RecordReferences::empty() ··· 165 165 166 166 // Extract reply parent and root authors (for notifications) 167 167 if let Some(reply) = &rec.reply { 168 - refs.parent_author_did = parakeet_db::at_uri_util::extract_did(&reply.parent.uri) 168 + refs.parent_author_did = parakeet_db::utils::at_uri::extract_did(&reply.parent.uri) 169 169 .map(|s| s.to_string()); 170 - refs.root_author_did = parakeet_db::at_uri_util::extract_did(&reply.root.uri) 170 + refs.root_author_did = parakeet_db::utils::at_uri::extract_did(&reply.root.uri) 171 171 .map(|s| s.to_string()); 172 172 } 173 173 ··· 177 177 match bsky_embed { 178 178 AppBskyEmbed::Record(record_embed) => { 179 179 refs.quoted_author_did = 180 - parakeet_db::at_uri_util::extract_did(&record_embed.record.uri) 180 + parakeet_db::utils::at_uri::extract_did(&record_embed.record.uri) 181 181 .map(|s| s.to_string()); 182 182 } 183 183 AppBskyEmbed::RecordWithMedia(rwm) => { 184 184 refs.quoted_author_did = 185 - parakeet_db::at_uri_util::extract_did(&rwm.record.uri) 185 + parakeet_db::utils::at_uri::extract_did(&rwm.record.uri) 186 186 .map(|s| s.to_string()); 187 187 } 188 188 _ => {} ··· 201 201 // ListBlock: extract list owner DID 202 202 RecordTypes::AppBskyGraphListBlock(rec) => { 203 203 let mut dids = Vec::new(); 204 - if let Some(list_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) { 204 + if let Some(list_did) = parakeet_db::utils::at_uri::extract_did(rec.subject.uri.as_str()) { 205 205 dids.push(list_did.to_string()); 206 206 } 207 207 RecordReferences::with_additional(dids) ··· 213 213 if let Some(rules) = &rec.allow { 214 214 for rule in rules { 215 215 if let ThreadgateRule::List { list } = rule { 216 - if let Some(list_did) = parakeet_db::at_uri_util::extract_did(list) { 216 + if let Some(list_did) = parakeet_db::utils::at_uri::extract_did(list) { 217 217 dids.push(list_did.to_string()); 218 218 } 219 219 }
+5 -7
consumer/src/database_writer/workers_tap.rs
··· 339 339 // For posts, resolve parent/root/quoted authors 340 340 let (parent_author_actor_id, root_author_actor_id, quoted_author_actor_id, mentioned_actor_ids) = 341 341 if let crate::relay::types::RecordTypes::AppBskyFeedPost(ref post) = *record { 342 - use parakeet_db::at_uri_util; 343 - 344 342 // Resolve parent author 345 343 let parent_author = if let Some(ref reply) = post.reply { 346 - if let Some(did) = at_uri_util::extract_did(&reply.parent.uri) { 344 + if let Some(did) = utils::at_uri::extract_did(&reply.parent.uri) { 347 345 let (aid, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; 348 346 Some(aid) 349 347 } else { ··· 356 354 // Resolve root author 357 355 let root_author = if let Some(ref reply) = post.reply { 358 356 if reply.root.uri != reply.parent.uri { 359 - if let Some(did) = at_uri_util::extract_did(&reply.root.uri) { 357 + if let Some(did) = utils::at_uri::extract_did(&reply.root.uri) { 360 358 let (aid, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; 361 359 Some(aid) 362 360 } else { ··· 375 373 match bsky_embed { 376 374 AppBskyEmbed::Record(record_embed) => { 377 375 // Extract DID from the quoted post URI 378 - if let Some(did) = parakeet_db::at_uri_util::extract_did(&record_embed.record.uri) { 376 + if let Some(did) = parakeet_db::utils::at_uri::extract_did(&record_embed.record.uri) { 379 377 let (actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; 380 378 Some(actor_id) 381 379 } else { ··· 384 382 }, 385 383 AppBskyEmbed::RecordWithMedia(record_with_media) => { 386 384 // Extract DID from the quoted post URI 387 - if let Some(did) = parakeet_db::at_uri_util::extract_did(&record_with_media.record.uri) { 385 + if let Some(did) = parakeet_db::utils::at_uri::extract_did(&record_with_media.record.uri) { 388 386 let (actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; 389 387 Some(actor_id) 390 388 } else { ··· 422 420 423 421 // Resolve via_repost if present 424 422 let via_repost_key = if let (Some(via_uri), Some(via_cid)) = (&refs.via_uri, &refs.via_cid) { 425 - if let Some((via_did, via_rkey, _)) = parakeet_db::at_uri_util::parse_at_uri(via_uri) { 423 + if let Some((via_did, via_rkey, _)) = parakeet_db::utils::at_uri::parse_at_uri(via_uri) { 426 424 let (via_actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, via_did).await?; 427 425 let (key, _) = crate::db::operations::feed::get_repost_id( 428 426 &mut conn,
+1 -1
consumer/src/db/actor.rs
··· 102 102 cid: Cid, 103 103 ) -> Result<u64> { 104 104 let cid_bytes = cid.to_bytes(); 105 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 105 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 106 106 .expect("CID must be valid AT Protocol CID"); 107 107 108 108 // Use consolidated ActorUpdate API for repo state update
+8 -8
consumer/src/db/bulk_resolve/mod.rs
··· 90 90 let mut dids_set: std::collections::HashSet<String> = std::collections::HashSet::new(); 91 91 92 92 for uri in at_uris { 93 - let did = parakeet_db::at_uri_util::extract_did(uri) 93 + let did = parakeet_db::utils::at_uri::extract_did(uri) 94 94 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; 95 - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) 95 + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) 96 96 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; 97 97 let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) 98 98 .map_err(|e| eyre::eyre!("Invalid TID in AT URI {}: {}", uri, e))?; ··· 253 253 let mut dids_set: std::collections::HashSet<String> = std::collections::HashSet::new(); 254 254 255 255 for uri in at_uris { 256 - let did = parakeet_db::at_uri_util::extract_did(uri) 256 + let did = parakeet_db::utils::at_uri::extract_did(uri) 257 257 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; 258 - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) 258 + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) 259 259 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; 260 260 261 261 uri_to_did_rkey.insert(uri.to_string(), (did.to_string(), rkey.to_string())); ··· 380 380 let mut dids_set: std::collections::HashSet<String> = std::collections::HashSet::new(); 381 381 382 382 for uri in at_uris { 383 - let did = parakeet_db::at_uri_util::extract_did(uri) 383 + let did = parakeet_db::utils::at_uri::extract_did(uri) 384 384 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; 385 - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) 385 + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) 386 386 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; 387 387 let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) 388 388 .map_err(|e| eyre::eyre!("Invalid TID in AT URI {}: {}", uri, e))?; ··· 526 526 let mut dids_set: std::collections::HashSet<String> = std::collections::HashSet::new(); 527 527 528 528 for uri in at_uris { 529 - let did = parakeet_db::at_uri_util::extract_did(uri) 529 + let did = parakeet_db::utils::at_uri::extract_did(uri) 530 530 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; 531 - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) 531 + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) 532 532 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; 533 533 534 534 uri_to_did_rkey.insert(uri.to_string(), (did.to_string(), rkey.to_string()));
+6 -6
consumer/src/db/composite_builders.rs
··· 3 3 //! These functions transform AT Protocol records into the denormalized composite type 4 4 //! structures used in the posts table. 5 5 6 - use parakeet_db::composite_types::{ExtEmbed, VideoEmbed, ImageEmbed, FacetEmbed}; 6 + use parakeet_db::composite::{ExtEmbed, VideoEmbed, ImageEmbed, FacetEmbed}; 7 7 use parakeet_db::types::{ImageMimeType, VideoMimeType, FacetType}; 8 8 use crate::types::records::{AppBskyEmbedImages, AppBskyEmbedVideo, AppBskyEmbedExternal}; 9 9 use jacquard_api::app_bsky::richtext::facet::{Facet as FacetMain, FacetFeaturesItem}; ··· 19 19 let cid_str = thumb.cid().as_str(); 20 20 let cid_parsed = cid_str.parse::<cid::Cid>().ok(); 21 21 let cid_bytes = cid_parsed.map(|c| c.to_bytes()); 22 - let cid = cid_bytes.and_then(|b| parakeet_db::cid_util::cid_to_digest_owned(&b)); 22 + let cid = cid_bytes.and_then(|b| parakeet_db::utils::cid::cid_to_digest_owned(&b)); 23 23 (mime, cid) 24 24 } else { 25 25 (None, None) ··· 36 36 37 37 /// Build video embed composite from AppBskyEmbedVideo 38 38 pub fn build_video_embed(embed: &AppBskyEmbedVideo) -> Option<VideoEmbed> { 39 - use parakeet_db::composite_types::VideoCaption; 39 + use parakeet_db::composite::VideoCaption; 40 40 use parakeet_db::types::{CaptionMimeType, LanguageCode}; 41 41 42 42 let video = &embed.video; ··· 46 46 let cid_str = video.cid().as_str(); 47 47 let cid_parsed = cid_str.parse::<cid::Cid>().ok()?; 48 48 let cid_bytes = cid_parsed.to_bytes(); 49 - let cid = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes)?; 49 + let cid = parakeet_db::utils::cid::cid_to_digest_owned(&cid_bytes)?; 50 50 51 51 // Build caption fields (max 3) 52 52 let mut captions = [None, None, None]; ··· 57 57 let caption_cid_str = caption_data.file.cid().as_str(); 58 58 let caption_cid_parsed = caption_cid_str.parse::<cid::Cid>().ok()?; 59 59 let caption_cid_bytes = caption_cid_parsed.to_bytes(); 60 - let caption_cid = parakeet_db::cid_util::cid_to_digest_owned(&caption_cid_bytes)?; 60 + let caption_cid = parakeet_db::utils::cid::cid_to_digest_owned(&caption_cid_bytes)?; 61 61 62 62 captions[idx] = Some(VideoCaption { 63 63 lang, ··· 93 93 let cid_str = image.image.cid().as_str(); 94 94 if let Ok(cid_parsed) = cid_str.parse::<cid::Cid>() { 95 95 let cid_bytes = cid_parsed.to_bytes(); 96 - if let Some(cid) = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes) { 96 + if let Some(cid) = parakeet_db::utils::cid::cid_to_digest_owned(&cid_bytes) { 97 97 images[idx] = Some(ImageEmbed { 98 98 mime_type, 99 99 cid,
+4 -4
consumer/src/db/gates/queries.rs
··· 160 160 161 161 // OPTIMIZATION 2: Resolve target post natural key directly from database 162 162 let (target_post_actor_id, target_post_rkey) = { 163 - let did = parakeet_db::at_uri_util::extract_did(post_uri) 163 + let did = parakeet_db::utils::at_uri::extract_did(post_uri) 164 164 .ok_or_else(|| eyre::eyre!("Invalid post URI: missing DID in {}", post_uri))?; 165 - let rkey = parakeet_db::at_uri_util::extract_rkey(post_uri) 165 + let rkey = parakeet_db::utils::at_uri::extract_rkey(post_uri) 166 166 .ok_or_else(|| eyre::eyre!("Invalid post URI: missing rkey in {}", post_uri))?; 167 167 let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) 168 168 .wrap_err_with(|| format!("Invalid TID encoding in rkey: {}", rkey))?; ··· 183 183 // OPTIMIZATION 3: Resolve detached post natural keys from database (batch) 184 184 let mut detached_post_keys = Vec::new(); 185 185 for uri in detached_uris { 186 - let did = parakeet_db::at_uri_util::extract_did(uri) 186 + let did = parakeet_db::utils::at_uri::extract_did(uri) 187 187 .ok_or_else(|| eyre::eyre!("Invalid detached URI: missing DID in {}", uri))?; 188 - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) 188 + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) 189 189 .ok_or_else(|| eyre::eyre!("Invalid detached URI: missing rkey in {}", uri))?; 190 190 let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) 191 191 .wrap_err_with(|| format!("Invalid TID encoding in rkey: {}", rkey))?;
+2 -2
consumer/src/db/id_resolution.rs
··· 82 82 at_uri: &str, 83 83 ) -> Result<(i32, i64)> { 84 84 // Parse AT URI: at://did/collection/rkey 85 - let did = parakeet_db::at_uri_util::extract_did(at_uri) 85 + let did = parakeet_db::utils::at_uri::extract_did(at_uri) 86 86 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", at_uri))?; 87 - let rkey = parakeet_db::at_uri_util::extract_rkey(at_uri) 87 + let rkey = parakeet_db::utils::at_uri::extract_rkey(at_uri) 88 88 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", at_uri))?; 89 89 let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) 90 90 .map_err(|e| eyre::eyre!("Invalid TID in AT URI {}: {}", at_uri, e))?;
+2 -2
consumer/src/db/labels.rs
··· 103 103 // AT URI format: at://{did}/collection/rkey 104 104 // Actor profiles: at://{did}/app.bsky.actor.profile/self 105 105 // Posts: at://{did}/app.bsky.feed.post/{rkey} 106 - let (did, collection, rkey) = parakeet_db::at_uri_util::parse_at_uri(at_uri) 106 + let (did, collection, rkey) = parakeet_db::utils::at_uri::parse_at_uri(at_uri) 107 107 .ok_or_else(|| eyre::eyre!("Invalid AT URI: {}", at_uri))?; 108 108 109 109 // Build labels array values ··· 148 148 } else if collection == "app.bsky.feed.post" { 149 149 // Update post labels array 150 150 // Convert TID string to INT8 for rkey lookup 151 - let rkey_i64 = parakeet_db::tid_util::decode_tid(rkey)?; 151 + let rkey_i64 = parakeet_db::utils::tid::decode_tid(rkey)?; 152 152 153 153 conn.execute( 154 154 "UPDATE posts p
+8 -8
consumer/src/db/operations/actor.rs
··· 19 19 // SCHEMA CHANGE: profiles table dropped, now UPDATE actors.profile_* columns 20 20 // No advisory lock needed - simple UPDATE with PostgreSQL row-level locking 21 21 let cid_bytes = cid.to_bytes(); 22 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 22 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 23 23 .expect("CID must be valid AT Protocol CID"); 24 24 let avatar = blob_to_cid_bytes(rec.avatar.as_ref()); 25 25 let banner = blob_to_cid_bytes(rec.banner.as_ref()); ··· 35 35 36 36 if collection == Some("app.bsky.feed.post") { 37 37 // Extract and parse rkey 38 - parakeet_db::at_uri_util::extract_rkey(uri) 38 + parakeet_db::utils::at_uri::extract_rkey(uri) 39 39 .and_then(|rkey_str| parakeet_db::models::tid_to_i64(rkey_str).ok()) 40 40 } else { 41 41 None // Not a post URI, skip ··· 53 53 54 54 if collection == Some("app.bsky.graph.starterpack") { 55 55 // Parse URI components 56 - let sp_did = parakeet_db::at_uri_util::extract_did(uri); 57 - let sp_rkey = parakeet_db::at_uri_util::extract_rkey(uri) 56 + let sp_did = parakeet_db::utils::at_uri::extract_did(uri); 57 + let sp_rkey = parakeet_db::utils::at_uri::extract_rkey(uri) 58 58 .and_then(|rkey_str| parakeet_db::models::tid_to_i64(rkey_str).ok()); 59 59 60 60 if let (Some(sp_did), Some(sp_rkey)) = (sp_did, sp_rkey) { ··· 146 146 // SCHEMA CHANGE: statuses table dropped, now UPDATE actors.status_* columns 147 147 // No advisory lock needed - simple UPDATE with PostgreSQL row-level locking 148 148 let cid_bytes = cid.to_bytes(); 149 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 149 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 150 150 .expect("CID must be valid AT Protocol CID"); 151 151 152 152 let thumb = rec.embed.as_ref().and_then(|v| v.external.thumb.clone()); ··· 155 155 let cid_str = v.cid(); 156 156 let cid_parsed = cid_str.as_str().parse::<cid::Cid>().expect("Valid CID"); 157 157 let cid_bytes = cid_parsed.to_bytes(); 158 - parakeet_db::cid_util::cid_to_digest(&cid_bytes) 158 + parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 159 159 .expect("CID must be valid AT Protocol CID") 160 160 .to_vec() 161 161 }); ··· 171 171 172 172 if collection == Some("app.bsky.feed.post") { 173 173 // Parse URI components 174 - let post_did = parakeet_db::at_uri_util::extract_did(uri); 175 - let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(uri); 174 + let post_did = parakeet_db::utils::at_uri::extract_did(uri); 175 + let post_rkey_str = parakeet_db::utils::at_uri::extract_rkey(uri); 176 176 177 177 // Validate TID length before parsing 178 178 if let (Some(post_did), Some(rkey_str)) = (post_did, post_rkey_str) {
+1 -1
consumer/src/db/operations/community.rs
··· 17 17 ) -> Result<u64> { 18 18 // Parse the subject URI to extract (did, collection, rkey) 19 19 let (post_did, collection, post_rkey_str) = 20 - parakeet_db::at_uri_util::parse_at_uri(&rec.subject) 20 + parakeet_db::utils::at_uri::parse_at_uri(&rec.subject) 21 21 .ok_or_else(|| eyre::eyre!("Invalid AT URI: {}", rec.subject))?; 22 22 23 23 // Validate it's a post URI
+1 -1
consumer/src/db/operations/feed/feedgen.rs
··· 52 52 .await?; 53 53 54 54 let cid_bytes = cid.to_bytes(); 55 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 55 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 56 56 .expect("CID must be valid AT Protocol CID"); 57 57 let description_facets = rec 58 58 .description_facets
+5 -5
consumer/src/db/operations/feed/helpers.rs
··· 99 99 let cid = ipld_core::cid::Cid::try_from(cid_str) 100 100 .wrap_err_with(|| format!("Invalid CID format: {}", cid_str))?; 101 101 let cid_bytes = cid.to_bytes(); 102 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 102 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 103 103 .ok_or_eyre("CID must be valid AT Protocol CID")?; 104 104 105 105 // Extract owner DID and rkey from AT URI 106 - let did = parakeet_db::at_uri_util::extract_did(at_uri) 106 + let did = parakeet_db::utils::at_uri::extract_did(at_uri) 107 107 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", at_uri))?; 108 - let rkey = parakeet_db::at_uri_util::extract_rkey(at_uri) 108 + let rkey = parakeet_db::utils::at_uri::extract_rkey(at_uri) 109 109 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", at_uri))?; 110 110 111 111 // Get/create actor_id for owner ··· 155 155 crate::database_writer::locking::acquire_lock(conn, table_id, key_id).await?; 156 156 157 157 // Extract owner DID and rkey from AT URI 158 - let did = parakeet_db::at_uri_util::extract_did(at_uri) 158 + let did = parakeet_db::utils::at_uri::extract_did(at_uri) 159 159 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", at_uri))?; 160 - let rkey = parakeet_db::at_uri_util::extract_rkey(at_uri) 160 + let rkey = parakeet_db::utils::at_uri::extract_rkey(at_uri) 161 161 .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", at_uri))?; 162 162 163 163 // Get/create actor_id for owner
+5 -5
consumer/src/db/operations/feed/like.rs
··· 87 87 let (subject_post_key, subject_feedgen_key, subject_labeler_actor_id, _subject_was_created): (Option<(i32, i64)>, Option<(i32, String)>, Option<i32>, bool) = match subject_collection { 88 88 "app.bsky.feed.post" => { 89 89 let subject_did = 90 - parakeet_db::at_uri_util::extract_did(subject_uri).ok_or_else(|| { 90 + parakeet_db::utils::at_uri::extract_did(subject_uri).ok_or_else(|| { 91 91 eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri) 92 92 })?; 93 93 let subject_rkey = 94 - parakeet_db::at_uri_util::extract_rkey(subject_uri).ok_or_else(|| { 94 + parakeet_db::utils::at_uri::extract_rkey(subject_uri).ok_or_else(|| { 95 95 eyre::eyre!("Invalid subject URI: missing rkey in {}", subject_uri) 96 96 })?; 97 97 let (subject_actor_id, _, _) = get_actor_id(conn, subject_did).await?; ··· 104 104 } 105 105 "app.bsky.labeler.service" => { 106 106 let subject_did = 107 - parakeet_db::at_uri_util::extract_did(subject_uri).ok_or_else(|| { 107 + parakeet_db::utils::at_uri::extract_did(subject_uri).ok_or_else(|| { 108 108 eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri) 109 109 })?; 110 110 // Note: ensure_labeler_stub doesn't return was_created, so always enqueue labelers ··· 114 114 _ => { 115 115 // Fallback: treat as post 116 116 let subject_did = 117 - parakeet_db::at_uri_util::extract_did(subject_uri).ok_or_else(|| { 117 + parakeet_db::utils::at_uri::extract_did(subject_uri).ok_or_else(|| { 118 118 eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri) 119 119 })?; 120 120 let subject_rkey = 121 - parakeet_db::at_uri_util::extract_rkey(subject_uri).ok_or_else(|| { 121 + parakeet_db::utils::at_uri::extract_rkey(subject_uri).ok_or_else(|| { 122 122 eyre::eyre!("Invalid subject URI: missing rkey in {}", subject_uri) 123 123 })?; 124 124 let (subject_actor_id, _, _) = get_actor_id(conn, subject_did).await?;
+9 -9
consumer/src/db/operations/feed/post.rs
··· 37 37 source: crate::database_writer::EventSource, 38 38 ) -> Result<u64> { 39 39 let cid_bytes = cid.to_bytes(); 40 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 40 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 41 41 .expect("CID must be valid AT Protocol CID"); 42 42 let (_mentions, tags) = rec 43 43 .facets ··· 83 83 // Track whether stubs were created so we only enqueue newly created stubs 84 84 let (parent_post_key, _parent_was_created) = 85 85 if let (Some(parent_uri), Some(parent_cid)) = (&parent_uri, &parent_cid) { 86 - let parent_did = parakeet_db::at_uri_util::extract_did(parent_uri) 86 + let parent_did = parakeet_db::utils::at_uri::extract_did(parent_uri) 87 87 .ok_or_else(|| eyre::eyre!("Invalid parent URI: missing DID in {}", parent_uri))?; 88 - let parent_rkey = parakeet_db::at_uri_util::extract_rkey(parent_uri) 88 + let parent_rkey = parakeet_db::utils::at_uri::extract_rkey(parent_uri) 89 89 .ok_or_else(|| eyre::eyre!("Invalid parent URI: missing rkey in {}", parent_uri))?; 90 90 let (parent_actor_id, _, _) = get_actor_id(conn, parent_did).await?; 91 91 let (post_key, was_created) = ··· 99 99 if let (Some(root_uri), Some(root_cid)) = (&root_uri, &root_cid) { 100 100 // Only get root post natural key if it's different from parent 101 101 if root_uri != parent_uri.as_ref().unwrap_or(&String::new()) { 102 - let root_did = parakeet_db::at_uri_util::extract_did(root_uri) 102 + let root_did = parakeet_db::utils::at_uri::extract_did(root_uri) 103 103 .ok_or_else(|| eyre::eyre!("Invalid root URI: missing DID in {}", root_uri))?; 104 - let root_rkey = parakeet_db::at_uri_util::extract_rkey(root_uri) 104 + let root_rkey = parakeet_db::utils::at_uri::extract_rkey(root_uri) 105 105 .ok_or_else(|| eyre::eyre!("Invalid root URI: missing rkey in {}", root_uri))?; 106 106 let (root_actor_id, _, _) = get_actor_id(conn, root_did).await?; 107 107 let (post_key, was_created) = ··· 148 148 // Get embedded post natural key 149 149 let embed_uri = record.record.uri.as_str(); 150 150 let embed_cid_str = record.record.cid.to_string(); 151 - let embed_did = parakeet_db::at_uri_util::extract_did(embed_uri) 151 + let embed_did = parakeet_db::utils::at_uri::extract_did(embed_uri) 152 152 .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing DID in {}", embed_uri))?; 153 - let embed_rkey = parakeet_db::at_uri_util::extract_rkey(embed_uri) 153 + let embed_rkey = parakeet_db::utils::at_uri::extract_rkey(embed_uri) 154 154 .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing rkey in {}", embed_uri))?; 155 155 let (embed_actor_id, _, _) = get_actor_id(conn, embed_did).await?; 156 156 let (embed_post_key, _) = get_post_id(conn, embed_actor_id, embed_rkey, &embed_cid_str).await?; ··· 173 173 // Process record part 174 174 let embed_uri = rwm.record.uri.as_str(); 175 175 let embed_cid_str = rwm.record.cid.to_string(); 176 - let embed_did = parakeet_db::at_uri_util::extract_did(embed_uri) 176 + let embed_did = parakeet_db::utils::at_uri::extract_did(embed_uri) 177 177 .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing DID in {}", embed_uri))?; 178 - let embed_rkey = parakeet_db::at_uri_util::extract_rkey(embed_uri) 178 + let embed_rkey = parakeet_db::utils::at_uri::extract_rkey(embed_uri) 179 179 .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing rkey in {}", embed_uri))?; 180 180 let (embed_actor_id, _, _) = get_actor_id(conn, embed_did).await?; 181 181 let (embed_post_key, _) = get_post_id(conn, embed_actor_id, embed_rkey, &embed_cid_str).await?;
+2 -2
consumer/src/db/operations/feed/postgate.rs
··· 35 35 let rules: Vec<String> = vec![]; // TODO: Extract rules from DisableRule when needed 36 36 37 37 // Parse post URI to get post actor_id and rkey 38 - let post_did = parakeet_db::at_uri_util::extract_did(rec.post.as_str()) 38 + let post_did = parakeet_db::utils::at_uri::extract_did(rec.post.as_str()) 39 39 .ok_or_else(|| eyre::eyre!("Invalid post URI in postgate: missing DID in {}", rec.post))?; 40 - let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(rec.post.as_str()) 40 + let post_rkey_str = parakeet_db::utils::at_uri::extract_rkey(rec.post.as_str()) 41 41 .ok_or_else(|| eyre::eyre!("Invalid post URI in postgate: {}", rec.post))?; 42 42 let post_rkey = parakeet_db::models::tid_to_i64(post_rkey_str) 43 43 .wrap_err_with(|| format!("Invalid TID in postgate post URI: {}", post_rkey_str))?;
+3 -3
consumer/src/db/operations/feed/repost.rs
··· 36 36 _source: crate::database_writer::EventSource, 37 37 ) -> Result<u64> { 38 38 let cid_bytes = cid.to_bytes(); 39 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 39 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 40 40 .expect("CID must be valid AT Protocol CID"); 41 41 42 42 let subject_uri = &rec.subject.uri; ··· 50 50 // This ensures the FK constraint is satisfied before we reach this point 51 51 52 52 // Resolve post natural key (no stub creation with natural keys) 53 - let subject_did = parakeet_db::at_uri_util::extract_did(subject_uri) 53 + let subject_did = parakeet_db::utils::at_uri::extract_did(subject_uri) 54 54 .ok_or_else(|| eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri))?; 55 - let subject_rkey = parakeet_db::at_uri_util::extract_rkey(subject_uri) 55 + let subject_rkey = parakeet_db::utils::at_uri::extract_rkey(subject_uri) 56 56 .ok_or_else(|| eyre::eyre!("Invalid subject URI: missing rkey in {}", subject_uri))?; 57 57 let (subject_actor_id, _, _) = get_actor_id(conn, subject_did).await?; 58 58 let ((post_actor_id, post_rkey), _subject_was_created) =
+2 -2
consumer/src/db/operations/feed/threadgate.rs
··· 101 101 // but we extract it from the URI to be safe 102 102 let post_uri = rec.post.as_ref() 103 103 .ok_or_else(|| eyre::eyre!("Missing post URI in threadgate"))?; 104 - let post_did = parakeet_db::at_uri_util::extract_did(post_uri.uri.as_str()) 104 + let post_did = parakeet_db::utils::at_uri::extract_did(post_uri.uri.as_str()) 105 105 .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: missing DID in {}", post_uri.uri))?; 106 - let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(post_uri.uri.as_str()) 106 + let post_rkey_str = parakeet_db::utils::at_uri::extract_rkey(post_uri.uri.as_str()) 107 107 .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: {}", post_uri.uri))?; 108 108 let post_rkey = parakeet_db::models::tid_to_i64(post_rkey_str) 109 109 .wrap_err_with(|| format!("Invalid TID in threadgate post URI: {}", post_rkey_str))?;
+4 -4
consumer/src/db/operations/graph.rs
··· 185 185 rec: AppBskyGraphList, 186 186 ) -> Result<bool> { 187 187 let cid_bytes = cid.to_bytes(); 188 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 188 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 189 189 .ok_or_eyre("CID must be valid AT Protocol CID")?; 190 190 let description_facets = rec 191 191 .description_facets ··· 244 244 rec: AppBskyGraphListBlock, 245 245 ) -> Result<u64> { 246 246 let cid_bytes = cid.to_bytes(); 247 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 247 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 248 248 .ok_or_eyre("CID must be valid AT Protocol CID")?; 249 249 250 250 // Parse list URI to get natural keys (list_actor_id, list_rkey) ··· 323 323 rec: AppBskyGraphListItem, 324 324 ) -> Result<u64> { 325 325 let cid_bytes = cid.to_bytes(); 326 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 326 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 327 327 .ok_or_eyre("CID must be valid AT Protocol CID")?; 328 328 329 329 // Acquire advisory lock on record to prevent deadlocks ··· 375 375 rec: AppBskyGraphVerification, 376 376 ) -> Result<u64> { 377 377 let cid_bytes = cid.to_bytes(); 378 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 378 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 379 379 .ok_or_eyre("CID must be valid AT Protocol CID")?; 380 380 381 381 // Acquire advisory lock on record to prevent deadlocks
+2 -2
consumer/src/db/operations/labeler.rs
··· 29 29 let cid = ipld_core::cid::Cid::try_from(cid_str) 30 30 .wrap_err_with(|| format!("Invalid CID format: {}", cid_str))?; 31 31 let cid_bytes = cid.to_bytes(); 32 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 32 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 33 33 .ok_or_eyre("CID must be valid AT Protocol CID")?; 34 34 35 35 // Get/create actor_id (discard allowlist status and was_created, not needed for labelers) ··· 75 75 crate::database_writer::locking::acquire_lock(conn, table_id, key_id).await?; 76 76 77 77 let cid_bytes = cid.to_bytes(); 78 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 78 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 79 79 .ok_or_eyre("CID must be valid AT Protocol CID")?; 80 80 // Note: reason_types, subject_types, and subject_collections not available in jacquard types 81 81 let reasons: Option<Vec<String>> = None;
+2 -2
consumer/src/db/operations/starter_pack.rs
··· 12 12 rec: AppBskyGraphStarterPack, 13 13 ) -> Result<bool> { 14 14 let cid_bytes = cid.to_bytes(); 15 - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) 15 + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) 16 16 .ok_or_eyre("CID must be valid AT Protocol CID")?; 17 17 let record = serde_json::to_value(&rec).unwrap(); 18 18 let description_facets = rec ··· 35 35 for feed_ref in feeds { 36 36 // Parse feed URI to extract (did, rkey) 37 37 let (feed_did, collection, feed_rkey) = 38 - parakeet_db::at_uri_util::parse_at_uri(feed_ref.uri.as_str()) 38 + parakeet_db::utils::at_uri::parse_at_uri(feed_ref.uri.as_str()) 39 39 .ok_or_else(|| eyre::eyre!("Invalid feed URI: {}", feed_ref.uri))?; 40 40 41 41 // Validate it's a feedgen URI
+1 -1
consumer/src/db/record_exists/mod.rs
··· 5 5 6 6 use deadpool_postgres::Object as PgObject; 7 7 use eyre::Result; 8 - use parakeet_db::tid_util::decode_tid; 8 + use parakeet_db::utils::tid::decode_tid; 9 9 10 10 pub mod queries; 11 11
+2 -2
consumer/src/label_indexer/mod.rs
··· 87 87 88 88 for label in labels { 89 89 // Parse AT URI to determine target 90 - let Some((did, collection, rkey)) = parakeet_db::at_uri_util::parse_at_uri(&label.uri) else { 90 + let Some((did, collection, rkey)) = parakeet_db::utils::at_uri::parse_at_uri(&label.uri) else { 91 91 tracing::warn!("Invalid AT URI in label: {}", label.uri); 92 92 continue; 93 93 }; ··· 96 96 actor_labels.entry(did.to_string()).or_default().push(label); 97 97 } else if collection == "app.bsky.feed.post" { 98 98 // Convert TID string to INT8 99 - let Some(rkey_i64) = parakeet_db::tid_util::decode_tid(rkey) else { 99 + let Some(rkey_i64) = parakeet_db::utils::tid::decode_tid(rkey) else { 100 100 tracing::warn!("Invalid TID in label URI: {}", label.uri); 101 101 continue; 102 102 };
+3 -3
consumer/src/utils.rs
··· 28 28 /// Convert a Blob to CID bytes (32-byte digest) for database storage 29 29 pub fn blob_to_cid_bytes(blob: Option<&Blob>) -> Option<Vec<u8>> { 30 30 blob.and_then(|blob| { 31 - parakeet_db::cid_util::blob_to_cid_bytes(Some(blob)) 31 + parakeet_db::utils::cid::blob_to_cid_bytes(Some(blob)) 32 32 }) 33 33 } 34 34 35 35 pub fn strongref_to_parts(strongref: Option<&StrongRef>) -> (Option<String>, Option<String>) { 36 - parakeet_db::cid_util::strongref_to_parts(strongref) 36 + parakeet_db::utils::cid::strongref_to_parts(strongref) 37 37 } 38 38 39 39 /// Convert a StrongRef to (URI, CID digest bytes) for pending linkage 40 40 pub fn strongref_to_parts_with_digest( 41 41 strongref: Option<&StrongRef>, 42 42 ) -> (Option<String>, Option<Vec<u8>>) { 43 - parakeet_db::cid_util::strongref_to_parts_with_digest(strongref) 43 + parakeet_db::utils::cid::strongref_to_parts_with_digest(strongref) 44 44 } 45 45 46 46 pub fn at_uri_is_by(uri: &str, did: &str) -> bool {
+10 -10
consumer/tests/bulk_copy_queries_test.rs
··· 481 481 ).await?; 482 482 483 483 // Create VideoEmbed with all 3 captions to test serialization 484 - let video_embed = parakeet_db::composite_types::VideoEmbed { 484 + let video_embed = parakeet_db::composite::VideoEmbed { 485 485 mime_type: parakeet_db::types::VideoMimeType::Mp4, 486 486 cid: vec![1u8, 2, 3, 4, 5, 6, 7, 8], 487 487 alt: Some("Test video".to_string()), 488 488 width: Some(1920), 489 489 height: Some(1080), 490 - caption_1: Some(parakeet_db::composite_types::VideoCaption { 490 + caption_1: Some(parakeet_db::composite::VideoCaption { 491 491 lang: parakeet_db::types::LanguageCode::En, 492 492 mime_type: parakeet_db::types::CaptionMimeType::Vtt, 493 493 cid: vec![11u8, 12, 13, 14], 494 494 }), 495 - caption_2: Some(parakeet_db::composite_types::VideoCaption { 495 + caption_2: Some(parakeet_db::composite::VideoCaption { 496 496 lang: parakeet_db::types::LanguageCode::Es, 497 497 mime_type: parakeet_db::types::CaptionMimeType::Vtt, 498 498 cid: vec![21u8, 22, 23, 24], 499 499 }), 500 - caption_3: Some(parakeet_db::composite_types::VideoCaption { 500 + caption_3: Some(parakeet_db::composite::VideoCaption { 501 501 lang: parakeet_db::types::LanguageCode::Fr, 502 502 mime_type: parakeet_db::types::CaptionMimeType::Vtt, 503 503 cid: vec![31u8, 32, 33, 34], ··· 582 582 tokens: vec![], 583 583 ext_embed: None, 584 584 video_embed: None, 585 - image_1: Some(parakeet_db::composite_types::ImageEmbed { 585 + image_1: Some(parakeet_db::composite::ImageEmbed { 586 586 mime_type: parakeet_db::types::ImageMimeType::Jpeg, 587 587 cid: vec![11u8, 12, 13, 14], 588 588 alt: Some("Test image".to_string()), 589 589 width: Some(1920), 590 590 height: Some(1080), 591 591 }), 592 - image_2: Some(parakeet_db::composite_types::ImageEmbed { 592 + image_2: Some(parakeet_db::composite::ImageEmbed { 593 593 mime_type: parakeet_db::types::ImageMimeType::Png, 594 594 cid: vec![21u8, 22, 23, 24], 595 595 alt: None, ··· 643 643 embed_subtype: None, 644 644 violates_threadgate: false, 645 645 tokens: vec![], 646 - ext_embed: Some(parakeet_db::composite_types::ExtEmbed { 646 + ext_embed: Some(parakeet_db::composite::ExtEmbed { 647 647 uri: "https://example.com".to_string(), 648 648 title: Some("Example Site".to_string()), 649 649 description: Some("Test description".to_string()), ··· 710 710 embedded_post_actor_id: None, 711 711 embedded_post_rkey: None, 712 712 record_detached: None, 713 - facet_1: Some(parakeet_db::composite_types::FacetEmbed { 713 + facet_1: Some(parakeet_db::composite::FacetEmbed { 714 714 facet_type: parakeet_db::types::FacetType::Link, 715 715 index_start: 0, 716 716 index_end: 10, ··· 718 718 mention_actor_id: None, 719 719 tag: None, 720 720 }), 721 - facet_2: Some(parakeet_db::composite_types::FacetEmbed { 721 + facet_2: Some(parakeet_db::composite::FacetEmbed { 722 722 facet_type: parakeet_db::types::FacetType::Mention, 723 723 index_start: 11, 724 724 index_end: 20, ··· 726 726 mention_actor_id: Some(mention_actor_id), 727 727 tag: None, 728 728 }), 729 - facet_3: Some(parakeet_db::composite_types::FacetEmbed { 729 + facet_3: Some(parakeet_db::composite::FacetEmbed { 730 730 facet_type: parakeet_db::types::FacetType::Tag, 731 731 index_start: 21, 732 732 index_end: 30,
+1 -1
consumer/tests/record_exists_queries_test.rs
··· 6 6 mod common; 7 7 use common::*; 8 8 use consumer::db::record_exists::queries; 9 - use parakeet_db::tid_util::decode_tid; 9 + use parakeet_db::utils::tid::decode_tid; 10 10 use eyre::WrapErr; 11 11 12 12 // Valid test TID that can be decoded to i64
parakeet-db/src/at_uri_util.rs parakeet-db/src/utils/at_uri.rs
parakeet-db/src/cid_util.rs parakeet-db/src/utils/cid.rs
parakeet-db/src/composite_types.rs parakeet-db/src/composite/mod.rs
parakeet-db/src/compression.rs parakeet-db/src/utils/compression.rs
-766
parakeet-db/src/models.rs
··· 1 - // ============================================================================= 2 - // MODELS.RS - Self-Contained Collection Models for Parakeet 3 - // ============================================================================= 4 - // 5 - // This file contains Diesel model definitions that align with the 6 - // self-contained collections schema where each table stores its own record metadata. 7 - // 8 - // KEY PRINCIPLES: 9 - // 10 - // 1. **Self-Contained Tables**: Each collection has actor_id, rkey, cid, created_at 11 - // - No more record_id FK - data is in the collection table itself 12 - // - Single JOIN to actors for DID/handle, no records table JOIN 13 - // - 2-3x fewer JOINs in every query 14 - // 15 - // 2. **Type Safety via ENUMs**: 16 - // - All protocol-defined enums use custom types (not String) 17 - // - Diesel custom_types for type safety at query time 18 - // - Compile-time validation of enum values 19 - // 20 - // 3. **Direct Foreign Keys**: 21 - // - post_id: i64 (FK to posts, not records) 22 - // - list_id: i64 (FK to lists, not records) 23 - // - All FKs reference actual tables, not a central registry 24 - // 25 - // 4. **Discriminated Unions for Polymorphism**: 26 - // - Likes use subject_type enum (post | feedgen | labeler) 27 - // - Notifications use record_type enum (post | like | repost | follow | block) 28 - // - 4-byte enum instead of table JOIN for type discrimination 29 - // 30 - // 5. **Optimized Binary Data**: 31 - // - CIDs stored as Vec<u8> (32-byte digest, header stripped) 32 - // - Signatures as Vec<u8> 33 - // - No string overhead for binary data 34 - // 35 - // ============================================================================= 36 - 37 - use crate::composite_types::{Block, Bookmark, Follow, LabelerDef, Mute}; 38 - use crate::tid_util::{decode_tid, encode_tid, TidError}; 39 - use crate::types::*; 40 - use chrono::prelude::*; 41 - use diesel::prelude::*; 42 - use serde::{Deserialize, Serialize}; 43 - 44 - // ============================================================================= 45 - // HELPER TRAITS FOR TID RKEYS 46 - // ============================================================================= 47 - 48 - /// Trait for models that have a TID-based rkey (stored as i64) 49 - pub trait HasTidRkey { 50 - /// Get the rkey as a TID string 51 - fn rkey_str(&self) -> String; 52 - 53 - /// Get the rkey as i64 54 - fn rkey_i64(&self) -> i64; 55 - } 56 - 57 - /// Helper to decode a TID string to i64 for use in queries/inserts 58 - pub fn tid_to_i64(tid: &str) -> Result<i64, TidError> { 59 - decode_tid(tid) 60 - } 61 - 62 - /// Helper to encode an i64 rkey to TID string for use in AT URIs 63 - pub fn i64_to_tid(value: i64) -> String { 64 - encode_tid(value) 65 - } 66 - 67 - // Macro to implement HasTidRkey trait for models 68 - macro_rules! impl_tid_rkey { 69 - ($model:ty) => { 70 - impl HasTidRkey for $model { 71 - fn rkey_str(&self) -> String { 72 - encode_tid(self.rkey) 73 - } 74 - 75 - fn rkey_i64(&self) -> i64 { 76 - self.rkey 77 - } 78 - } 79 - }; 80 - } 81 - 82 - // Macro to implement created_at() method for TID-based models 83 - // This derives the timestamp from the TID rkey, eliminating need for separate created_at column 84 - macro_rules! impl_tid_created_at { 85 - ($model:ty) => { 86 - impl $model { 87 - /// Get created_at timestamp derived from TID rkey 88 - /// 89 - /// TIDs encode a 53-bit microsecond timestamp in their upper bits. 90 - /// This method extracts that timestamp, providing the original creation time 91 - /// from the AT Protocol record without needing a separate database column. 92 - pub fn created_at(&self) -> DateTime<Utc> { 93 - crate::tid_util::tid_to_datetime(self.rkey) 94 - } 95 - } 96 - }; 97 - } 98 - 99 - // ============================================================================= 100 - // CORE IDENTITY 101 - // ============================================================================= 102 - 103 - #[derive(Debug, Clone, Queryable, Selectable, Identifiable)] 104 - #[diesel(table_name = crate::schema::actors)] 105 - #[diesel(primary_key(id))] 106 - #[diesel(check_for_backend(diesel::pg::Pg))] 107 - pub struct Actor { 108 - pub id: i32, // PK: Internal actor ID 109 - pub did: String, // Reverse DID lookup 110 - pub handle: Option<String>, // Handle (can change) 111 - pub status: ActorStatus, // ENUM: active | takendown | suspended | deleted | deactivated 112 - pub sync_state: ActorSyncState, // ENUM: synced | dirty | partial | processing 113 - pub repo_rev: Option<String>, // Repo revision 114 - pub repo_cid: Option<Vec<u8>>, // Repo root CID (32 bytes) 115 - pub last_indexed: Option<DateTime<Utc>>, 116 - pub account_created_at: Option<DateTime<Utc>>, 117 - // Profile fields (from app.bsky.actor.profile) 118 - pub profile_cid: Option<Vec<u8>>, 119 - pub profile_created_at: Option<DateTime<Utc>>, 120 - pub profile_avatar_cid: Option<Vec<u8>>, 121 - pub profile_banner_cid: Option<Vec<u8>>, 122 - pub profile_display_name: Option<String>, 123 - pub profile_description: Option<String>, 124 - pub profile_pinned_post_rkey: Option<i64>, 125 - pub profile_joined_sp_id: Option<i64>, 126 - pub profile_pronouns: Option<String>, 127 - pub profile_website: Option<String>, 128 - // Note: profile_search_vector omitted from model (PostgreSQL tsvector, not loaded in Rust) 129 - // Status fields (from app.bsky.actor.status) 130 - pub status_cid: Option<Vec<u8>>, 131 - pub status_created_at: Option<DateTime<Utc>>, 132 - pub status_type: Option<StatusType>, 133 - pub status_duration: Option<i32>, 134 - pub status_embed_post_actor_id: Option<i32>, 135 - pub status_embed_post_rkey: Option<i64>, 136 - pub status_thumb_mime_type: Option<ImageMimeType>, 137 - pub status_thumb_cid: Option<Vec<u8>>, 138 - // Chat declaration fields (from app.bsky.chat.declaration) 139 - pub chat_allow_incoming: Option<ChatAllowIncoming>, 140 - pub chat_created_at: Option<DateTime<Utc>>, 141 - // Notification declaration fields (from app.bsky.notification.declaration) 142 - pub notif_decl_allow_subscriptions: Option<NotifAllowSubscriptions>, 143 - pub notif_decl_created_at: Option<DateTime<Utc>>, 144 - // Notification state fields (from notification_state table) 145 - pub notif_seen_at: Option<DateTime<Utc>>, 146 - pub notif_unread_count: Option<i32>, 147 - // Denormalized aggregate stats (from actor_aggregate_stats table) 148 - // NULL = 0 for better compression (Gorilla stores NULLs as 1 bit in bitmap) 149 - pub followers_count: Option<i32>, 150 - pub following_count: Option<i32>, 151 - pub posts_count: Option<i32>, 152 - pub lists_count: Option<i16>, 153 - pub feeds_count: Option<i16>, 154 - pub starterpacks_count: Option<i16>, 155 - // Labeler fields (from labelers table, denormalized - NULL for non-labelers) 156 - pub labeler_cid: Option<Vec<u8>>, 157 - pub labeler_created_at: Option<DateTime<Utc>>, 158 - pub labeler_reasons: Option<Vec<Option<ReasonType>>>, 159 - pub labeler_subject_types: Option<Vec<Option<SubjectType>>>, 160 - pub labeler_subject_collections: Option<Vec<Option<String>>>, 161 - pub labeler_status: Option<LabelerStatus>, 162 - pub labeler_like_count: Option<i32>, 163 - pub labeler_defs: Option<Vec<Option<LabelerDef>>>, 164 - // Social graph arrays (from follows table, denormalized - bidirectional) 165 - pub following: Option<Vec<Option<Follow>>>, // Who this actor follows 166 - pub followers: Option<Vec<Option<Follow>>>, // Who follows this actor 167 - // User preference arrays (from mutes, blocks, bookmarks tables, denormalized) 168 - pub mutes: Option<Vec<Option<Mute>>>, // Muted actors 169 - pub blocks: Option<Vec<Option<Block>>>, // Blocked actors 170 - pub bookmarks: Option<Vec<Option<Bookmark>>>, // Bookmarked posts 171 - 172 - // List moderation arrays (from thread_mutes, list_mutes, list_blocks tables, denormalized) 173 - pub thread_mutes: Option<Vec<Option<crate::composite_types::ThreadMute>>>, // Muted threads 174 - pub list_mutes: Option<Vec<Option<crate::composite_types::ListMute>>>, // Muted lists 175 - pub list_blocks: Option<Vec<Option<crate::composite_types::ListBlock>>>, // List blocks 176 - // Phase 6: RKey arrays for quick lookups 177 - pub post_rkeys: Option<Vec<Option<i64>>>, // All post rkeys owned by this actor 178 - pub repost_rkeys: Option<Vec<Option<i64>>>, // All repost rkeys owned by this actor 179 - // Phase 7: Labels array (from labels table, denormalized) 180 - pub labels: Option<Vec<Option<crate::composite_types::ActorLabelRecord>>>, // Labels applied to this actor 181 - } 182 - 183 - // AllowlistEntry model removed - allowlist table dropped in favor of actors.sync_state 184 - // 185 - // Allowlist status is now determined by actors.sync_state: 186 - // - sync_state IN ('synced', 'dirty', 'processing') = fully allowed 187 - // - sync_state = 'partial' = not allowed (interacts with allowlisted users) 188 - // 189 - // The AllowlistEntry struct is kept in parakeet-db/src/allowlist.rs for API compatibility 190 - 191 - // ============================================================================= 192 - // POSTS & CONTENT 193 - // ============================================================================= 194 - 195 - #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] 196 - #[diesel(table_name = crate::schema::posts)] 197 - #[diesel(primary_key(actor_id, rkey))] 198 - #[diesel(check_for_backend(diesel::pg::Pg))] 199 - pub struct Post { 200 - pub actor_id: i32, // PK part 1: FK to actors 201 - pub rkey: i64, // PK part 2: TID as INT8 202 - pub cid: Vec<u8>, // 32-byte CID digest 203 - pub content: Option<Vec<u8>>, // Zstd-compressed BYTEA (NULL for stubs) 204 - pub langs: array_helpers::LanguageCodeArray, // ENUM array: ISO 639-1 codes (up to 3 per AT Protocol) 205 - pub tags: array_helpers::TextArray, // Hashtags (GIN indexed) 206 - pub embed_type: Option<EmbedType>, // ENUM: images | video | external | record | record_with_media 207 - pub embed_subtype: Option<EmbedType>, // ENUM: For nested embeds (quote posts) 208 - pub violates_threadgate: bool, 209 - pub status: PostStatus, // ENUM: complete | stub | deleted | forbidden 210 - pub tokens: Option<array_helpers::TextArray>, // Token array for search (GIN indexed, nullable) 211 - // Denormalized embed fields (composite types) 212 - pub ext_embed: Option<crate::composite_types::ExtEmbed>, 213 - pub video_embed: Option<crate::composite_types::VideoEmbed>, 214 - // Natural key references to other posts (hypertable → hypertable, NO FK constraints) 215 - pub parent_post_actor_id: Option<i32>, // Parent post reference (for replies) 216 - pub parent_post_rkey: Option<i64>, 217 - pub root_post_actor_id: Option<i32>, // Root post reference (for threads) 218 - pub root_post_rkey: Option<i64>, 219 - pub embedded_post_actor_id: Option<i32>, // Quote post reference 220 - pub embedded_post_rkey: Option<i64>, 221 - pub record_detached: Option<bool>, 222 - pub image_1: Option<crate::composite_types::ImageEmbed>, 223 - pub image_2: Option<crate::composite_types::ImageEmbed>, 224 - pub image_3: Option<crate::composite_types::ImageEmbed>, 225 - pub image_4: Option<crate::composite_types::ImageEmbed>, 226 - pub facet_1: Option<crate::composite_types::FacetEmbed>, 227 - pub facet_2: Option<crate::composite_types::FacetEmbed>, 228 - pub facet_3: Option<crate::composite_types::FacetEmbed>, 229 - pub facet_4: Option<crate::composite_types::FacetEmbed>, 230 - pub facet_5: Option<crate::composite_types::FacetEmbed>, 231 - pub facet_6: Option<crate::composite_types::FacetEmbed>, 232 - pub facet_7: Option<crate::composite_types::FacetEmbed>, 233 - pub facet_8: Option<crate::composite_types::FacetEmbed>, 234 - pub mentions: Option<Vec<Option<i32>>>, // Array of actor_ids 235 - // Embedded engagement arrays (array-only tracking, compute counts via array_length()) 236 - // Likes: like_actor_ids[i] pairs with like_rkeys[i] 237 - pub like_actor_ids: Option<Vec<Option<i32>>>, // [actor1, actor2, ...] (who liked) 238 - pub like_rkeys: Option<Vec<Option<i64>>>, // [rkey1, rkey2, ...] (when they liked) 239 - // Via repost tracking as JSONB: {"30": {"actor_id": 15, "rkey": 999}, ...} 240 - // Key: liker's actor_id (as string), Value: {actor_id, rkey} of repost they came via 241 - pub like_via_repost_data: Option<serde_json::Value>, 242 - // Replies: reply_actor_ids[i] pairs with reply_rkeys[i] 243 - pub reply_actor_ids: Option<Vec<Option<i32>>>, // [actor1, actor2, ...] (who replied) 244 - pub reply_rkeys: Option<Vec<Option<i64>>>, // [rkey1, rkey2, ...] (when they replied) 245 - // Quotes: quote_actor_ids[i] pairs with quote_rkeys[i] 246 - pub quote_actor_ids: Option<Vec<Option<i32>>>, // [actor1, actor2, ...] (who quoted) 247 - pub quote_rkeys: Option<Vec<Option<i64>>>, // [rkey1, rkey2, ...] (when they quoted) 248 - // Reposts: repost_actor_ids[i] pairs with repost_rkeys[i] 249 - pub repost_actor_ids: Option<Vec<Option<i32>>>, // [actor1, actor2, ...] (who reposted) 250 - pub repost_rkeys: Option<Vec<Option<i64>>>, // [rkey1, rkey2, ...] (when they reposted) 251 - // Denormalized threadgate data (from threadgates table + threadgate_hidden_replies junction) 252 - pub threadgate_allow: Option<array_helpers::ThreadgateRuleArray>, // Rules (maxLength: 5) 253 - pub threadgate_hidden_actor_ids: Option<Vec<Option<i32>>>, // Hidden reply authors (maxLength: 300) 254 - pub threadgate_hidden_rkeys: Option<Vec<Option<i64>>>, // Hidden reply rkeys (parallel array) 255 - // Denormalized postgate data (from postgates table + postgate_detached junction) 256 - pub postgate_rules: Option<array_helpers::PostgateRuleArray>, // Rules (maxLength: 5) 257 - pub postgate_detached_actor_ids: Option<Vec<Option<i32>>>, // Detached embed authors (maxLength: 50) 258 - pub postgate_detached_rkeys: Option<Vec<Option<i64>>>, // Detached embed rkeys (parallel array) 259 - // Phase 7: Labels array (from labels table, denormalized) 260 - pub labels: Option<Vec<Option<crate::composite_types::PostLabelRecord>>>, // Labels applied to this post 261 - // Phase 8: Engagement counts (maintained automatically by triggers) 262 - pub like_count: i32, // Count of likes (maintained by update_post_counts trigger) 263 - pub repost_count: i32, // Count of reposts (maintained by update_post_counts trigger) 264 - pub reply_count: i32, // Count of replies (maintained by update_post_counts trigger) 265 - pub quote_count: i32, // Count of quote posts (maintained by update_post_counts trigger) 266 - // Note: created_at derived from TID rkey via created_at() method 267 - } 268 - 269 - impl Post { 270 - /// Get a specific like by index position 271 - /// 272 - /// Returns None if index is out of bounds or arrays are NULL 273 - pub fn get_like(&self, idx: usize) -> Option<PostLikeInfo> { 274 - let actor_ids = self.like_actor_ids.as_ref()?; 275 - let rkeys = self.like_rkeys.as_ref()?; 276 - 277 - let actor_id = *actor_ids.get(idx)?.as_ref()?; 278 - let rkey = *rkeys.get(idx)?.as_ref()?; 279 - 280 - // Extract via_repost from JSONB if it exists for this liker 281 - let (via_repost_actor_id, via_repost_rkey) = self 282 - .like_via_repost_data 283 - .as_ref() 284 - .and_then(|json| json.get(actor_id.to_string())) 285 - .and_then(|data| { 286 - let actor = data.get("actor_id")?.as_i64()? as i32; 287 - let rkey = data.get("rkey")?.as_i64()?; 288 - Some((Some(actor), Some(rkey))) 289 - }) 290 - .unwrap_or((None, None)); 291 - 292 - Some(PostLikeInfo { 293 - actor_id, 294 - rkey, 295 - via_repost_actor_id, 296 - via_repost_rkey, 297 - }) 298 - } 299 - 300 - /// Check if an actor has liked this post 301 - /// 302 - /// Returns Some(like_rkey) if the actor has liked, None otherwise 303 - pub fn actor_liked(&self, actor_id: i32) -> Option<i64> { 304 - let actor_ids = self.like_actor_ids.as_ref()?; 305 - let rkeys = self.like_rkeys.as_ref()?; 306 - 307 - actor_ids 308 - .iter() 309 - .position(|id| id.as_ref() == Some(&actor_id)) 310 - .and_then(|idx| rkeys.get(idx).and_then(|r| r.as_ref()).copied()) 311 - } 312 - 313 - /// Get iterator over all likes 314 - /// 315 - /// Returns empty iterator if post has no likes 316 - pub fn likes(&self) -> impl Iterator<Item = PostLikeInfo> + '_ { 317 - let count = self 318 - .like_actor_ids 319 - .as_ref() 320 - .map(|v| v.len()) 321 - .unwrap_or(0); 322 - 323 - (0..count).filter_map(move |i| self.get_like(i)) 324 - } 325 - 326 - /// Get number of likes from array length (should match like_count) 327 - pub fn likes_len(&self) -> usize { 328 - self.like_actor_ids 329 - .as_ref() 330 - .map(|v| v.len()) 331 - .unwrap_or(0) 332 - } 333 - } 334 - 335 - /// Represents a single like extracted from post arrays 336 - #[derive(Debug, Clone)] 337 - pub struct PostLikeInfo { 338 - pub actor_id: i32, 339 - pub rkey: i64, 340 - pub via_repost_actor_id: Option<i32>, 341 - pub via_repost_rkey: Option<i64>, 342 - } 343 - 344 - impl PostLikeInfo { 345 - /// Get created_at timestamp from the like's TID rkey 346 - pub fn created_at(&self) -> DateTime<Utc> { 347 - crate::tid_util::tid_to_datetime(self.rkey) 348 - } 349 - } 350 - 351 - // Stats struct for API responses (replaces parakeet_index::PostStats) 352 - #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 353 - pub struct PostStats { 354 - pub likes: i32, // Keep as i32 for API compatibility 355 - pub replies: i32, 356 - pub reposts: i32, 357 - pub quotes: i32, 358 - } 359 - 360 - impl PostStats { 361 - pub fn from_post(post: &Post) -> Self { 362 - Self { 363 - likes: post.like_count, 364 - replies: post.reply_count, 365 - reposts: post.repost_count, 366 - quotes: post.quote_count, 367 - } 368 - } 369 - 370 - pub fn zero() -> Self { 371 - Self { likes: 0, replies: 0, reposts: 0, quotes: 0 } 372 - } 373 - } 374 - 375 - // Profile stats struct for API responses (replaces parakeet_index::ProfileStats) 376 - #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 377 - pub struct ProfileStats { 378 - pub followers: i32, 379 - pub following: i32, 380 - pub posts: i32, 381 - pub lists: i32, 382 - pub feeds: i32, 383 - pub starterpacks: i32, 384 - } 385 - 386 - impl ProfileStats { 387 - pub fn zero() -> Self { 388 - Self { 389 - followers: 0, 390 - following: 0, 391 - posts: 0, 392 - lists: 0, 393 - feeds: 0, 394 - starterpacks: 0, 395 - } 396 - } 397 - } 398 - 399 - // Post embed tables - reference posts directly 400 - 401 - #[derive(Debug, Queryable, Selectable, Identifiable)] 402 - #[diesel(table_name = crate::schema::uris)] 403 - #[diesel(primary_key(id))] 404 - #[diesel(check_for_backend(diesel::pg::Pg))] 405 - pub struct Uri { 406 - pub id: i32, // PK: Deduplication ID 407 - pub uri: String, // Unique URI 408 - pub created_at: DateTime<Utc>, 409 - } 410 - 411 - // Postgates 412 - 413 - // ============================================================================= 414 - // SOCIAL INTERACTIONS 415 - // ============================================================================= 416 - 417 - // Note: Feed Generator Likes table dropped - likes stored as arrays on feedgens table 418 - // The like_actor_ids[] and like_rkeys[] arrays are maintained on feedgens 419 - // This follows the same denormalization pattern as posts 420 - 421 - // Note: Labeler Likes table dropped - labeler data moved to actors table 422 - // The labeler_like_count is maintained on actors.labeler_like_count 423 - // Individual like records (labeler_likes table) were dropped for simplicity 424 - 425 - #[derive(Clone, Debug, Queryable, Selectable, Identifiable)] 426 - #[diesel(table_name = crate::schema::reposts)] 427 - #[diesel(primary_key(actor_id, rkey))] 428 - #[diesel(check_for_backend(diesel::pg::Pg))] 429 - pub struct Repost { 430 - pub actor_id: i32, // PK part 1: FK to actors 431 - pub rkey: i64, // PK part 2: TID as INT8 432 - pub cid: Vec<u8>, // Real CID - needed for like.via_repost references 433 - pub post_actor_id: i32, // Natural key reference to posts (regular → hypertable FK allowed) - NOT NULL 434 - pub post_rkey: i64, // Natural key reference to posts - NOT NULL 435 - pub via_repost_actor_id: Option<i32>, // Natural key self-reference (quote-repost-of-repost) 436 - pub via_repost_rkey: Option<i64>, 437 - pub status: RepostStatus, // ENUM: complete | stub 438 - // Note: created_at derived from TID rkey via created_at() method 439 - } 440 - 441 - // Follow model removed - follow relationships now stored as follow_record[] arrays on actors table 442 - // Follow composite type is defined in composite_types.rs 443 - 444 - // Block, Mute, Bookmark models removed - user preferences now stored as arrays on actors table 445 - // Composite types are defined in composite_types.rs 446 - 447 - // Profile, NotifDecl, ChatDecl, and Status structs removed - data now consolidated into Actor struct 448 - // See actors table columns: profile_*, status_*, chat_*, notif_decl_*, notif_seen_at, notif_unread_count 449 - 450 - // ============================================================================= 451 - // FEED GENERATORS 452 - // ============================================================================= 453 - 454 - #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] 455 - #[diesel(table_name = crate::schema::feedgens)] 456 - #[diesel(primary_key(actor_id, rkey))] 457 - #[diesel(check_for_backend(diesel::pg::Pg))] 458 - pub struct FeedGen { 459 - pub actor_id: i32, // PK1: FK to actors 460 - pub rkey: String, // PK2: Arbitrary user-chosen string (NOT TID, e.g. "music", "discover") 461 - pub cid: Vec<u8>, // 32-byte CID digest 462 - pub created_at: DateTime<Utc>, // From AT Protocol record 463 - pub owner_actor_id: i32, // FK to actors (denormalized for filtering) 464 - pub service_actor_id: i32, // FK to actors (denormalized for resolution) 465 - pub content_mode: Option<ContentMode>, // ENUM: contentModeUnspecified | contentModeVideo 466 - pub name: Option<String>, // NULL for stubs or deleted feedgens 467 - pub description: Option<String>, 468 - pub description_facets: Option<serde_json::Value>, 469 - pub avatar_cid: Option<Vec<u8>>, // 32-byte CID 470 - pub accepts_interactions: bool, 471 - pub status: FeedgenStatus, // ENUM: complete | stub | deleted 472 - pub like_count: i32, // Aggregated count of likes (maintained by database_writer) 473 - } 474 - 475 - // ============================================================================= 476 - // LISTS 477 - // ============================================================================= 478 - 479 - #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] 480 - #[diesel(table_name = crate::schema::lists)] 481 - #[diesel(primary_key(actor_id, rkey))] 482 - #[diesel(check_for_backend(diesel::pg::Pg))] 483 - pub struct List { 484 - pub actor_id: i32, // PK1: FK to actors 485 - pub rkey: String, // PK2: Can be TID or arbitrary string like "nfb", "bblock" 486 - pub cid: Vec<u8>, // 32-byte CID digest 487 - pub owner_actor_id: i32, // FK to actors (denormalized for filtering) 488 - pub list_type: Option<ListType>, // ENUM: curatelist | modlist | referencelist (NULL for stubs) 489 - pub name: Option<String>, // NULL for stubs 490 - pub description: Option<String>, 491 - pub description_facets: Option<serde_json::Value>, 492 - pub avatar_cid: Option<Vec<u8>>, // 32-byte CID 493 - pub status: RecordStatus, // ENUM: complete | stub | deleted | forbidden 494 - // Note: created_at derived from TID rkey if present, or epoch for non-TID rkeys 495 - } 496 - 497 - #[derive(Debug, Queryable, Selectable, Identifiable)] 498 - #[diesel(table_name = crate::schema::list_items)] 499 - #[diesel(primary_key(actor_id, rkey))] 500 - #[diesel(check_for_backend(diesel::pg::Pg))] 501 - pub struct ListItem { 502 - pub actor_id: i32, // PK: FK to actors 503 - pub rkey: i64, // PK: TID as INT8 504 - pub cid: Vec<u8>, // 32-byte CID digest 505 - pub subject_actor_id: i32, // FK to actors 506 - pub list_owner_actor_id: i32, // FK to lists (natural key 1/2) 507 - pub list_rkey: String, // FK to lists (natural key 2/2) 508 - // Note: created_at derived from TID rkey via created_at() method 509 - } 510 - 511 - // Note: ListBlock, ListMute, ThreadMute models removed 512 - // List moderation data is now stored as arrays on actors table: 513 - // - thread_mutes: thread_mute_record[] 514 - // - list_mutes: list_mute_record[] 515 - // - list_blocks: list_block_record[] 516 - // Composite types are defined in composite_types.rs 517 - 518 - // ============================================================================= 519 - // STARTER PACKS 520 - // ============================================================================= 521 - 522 - #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] 523 - #[diesel(table_name = crate::schema::starterpacks)] 524 - #[diesel(primary_key(actor_id, rkey))] 525 - #[diesel(check_for_backend(diesel::pg::Pg))] 526 - pub struct StarterPack { 527 - pub actor_id: i32, // PK1: FK to actors 528 - pub rkey: i64, // PK2: TID as INT8 529 - pub cid: Vec<u8>, // 32-byte CID digest 530 - pub owner_actor_id: i32, // FK to actors (denormalized for filtering) 531 - pub name: Option<String>, // NULL for stubs 532 - pub description: Option<String>, 533 - pub description_facets: Option<serde_json::Value>, 534 - pub list_actor_id: Option<i32>, // FK to lists (natural key 1/2, nullable) 535 - pub list_rkey: Option<String>, // FK to lists (natural key 2/2, nullable) 536 - pub status: RecordStatus, // ENUM: complete | stub | deleted | forbidden 537 - // Note: search_vector omitted from model 538 - // Note: created_at derived from TID rkey via created_at() method 539 - } 540 - 541 - #[derive(Clone, Debug, Queryable, Selectable)] 542 - #[diesel(table_name = crate::schema::starterpack_feeds)] 543 - #[diesel(primary_key(starterpack_id, position))] 544 - #[diesel(check_for_backend(diesel::pg::Pg))] 545 - pub struct StarterPackFeed { 546 - pub starterpack_id: i64, // FK to starterpacks 547 - pub position: i16, 548 - pub feed_actor_id: i32, // FK to feedgens (natural key 1/2) 549 - pub feed_rkey: String, // FK to feedgens (natural key 2/2) 550 - } 551 - 552 - // ============================================================================= 553 - // THREAD CONTROL 554 - // ============================================================================= 555 - 556 - // Threadgate struct for denormalized use (no longer has a separate table) 557 - // Used only for compatibility with EnrichedThreadgate in parakeet loader 558 - #[derive(Clone, Debug, Serialize, Deserialize)] 559 - pub struct Threadgate { 560 - pub actor_id: i32, // Post's actor_id (threadgate typically owned by post author) 561 - pub rkey: i64, // Post's rkey (synthetic - threadgate has own rkey in reality) 562 - pub cid: Vec<u8>, // Synthetic CID (deterministic based on post key) 563 - pub allow: Option<array_helpers::ThreadgateRuleArray>, // ENUM array: mention | following | list 564 - pub post_actor_id: i32, // Same as actor_id (denormalized in post) 565 - pub post_rkey: i64, // Same as rkey (denormalized in post) 566 - } 567 - 568 - // ============================================================================= 569 - // MODERATION & LABELS 570 - // ============================================================================= 571 - 572 - // Note: Labeler and LabelerDef structs removed 573 - // Labeler data is now stored directly on actors table with labeler_* columns 574 - // LabelerDef is now a composite type in composite_types.rs, stored as labeler_defs array on actors 575 - 576 - #[derive(Clone, Debug)] 577 - pub struct Label { 578 - pub labeler_actor_id: i32, // PK: FK to actors 579 - pub label: String, // PK: Label identifier 580 - pub uri: String, // PK: Labeled URI 581 - pub self_label: bool, 582 - pub cid: Option<Vec<u8>>, // 32-byte CID 583 - pub negated: bool, 584 - pub expires: Option<DateTime<Utc>>, 585 - pub sig: Option<Vec<u8>>, // Signature 586 - pub created_at: DateTime<Utc>, 587 - pub labeler: String, // COMPUTED: Labeler DID (fetched via JOIN in loaders) 588 - } 589 - 590 - #[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] 591 - #[diesel(table_name = crate::schema::verification)] 592 - #[diesel(primary_key(id))] 593 - #[diesel(check_for_backend(diesel::pg::Pg))] 594 - pub struct Verification { 595 - pub id: i64, // PK: Surrogate key 596 - pub actor_id: i32, // FK to actors 597 - pub rkey: i64, // TID as INT8 598 - pub cid: Vec<u8>, // 32-byte CID digest 599 - pub verifier_actor_id: i32, // FK to actors 600 - pub subject_actor_id: i32, // FK to actors 601 - pub handle: String, 602 - pub display_name: String, 603 - // Note: created_at derived from TID rkey via created_at() method 604 - } 605 - 606 - // ChatDecl and Status structs removed - data now consolidated into Actor struct 607 - // See actors table columns: chat_*, status_* 608 - 609 - // ============================================================================= 610 - // HELPER TYPES FOR ARRAYS 611 - // ============================================================================= 612 - 613 - // For diesel arrays with nullable elements that we want as Vec<T> 614 - pub mod array_helpers { 615 - use diesel::deserialize::FromSql; 616 - use diesel::pg::Pg; 617 - use diesel::sql_types::{Array, Nullable, Text}; 618 - use diesel::{deserialize, FromSqlRow}; 619 - use serde::{Deserialize, Serialize}; 620 - use std::ops::{Deref, DerefMut}; 621 - 622 - #[derive(Clone, Debug, Default, Serialize, Deserialize, FromSqlRow)] 623 - #[diesel(sql_type = Array<Nullable<Text>>)] 624 - pub struct TextArray(pub Vec<String>); 625 - 626 - impl FromSql<Array<Nullable<Text>>, Pg> for TextArray { 627 - fn from_sql(bytes: diesel::pg::PgValue<'_>) -> deserialize::Result<Self> { 628 - let vec_with_nulls = 629 - <Vec<Option<String>> as FromSql<Array<Nullable<Text>>, Pg>>::from_sql(bytes)?; 630 - Ok(TextArray(vec_with_nulls.into_iter().flatten().collect())) 631 - } 632 - } 633 - 634 - impl Deref for TextArray { 635 - type Target = Vec<String>; 636 - 637 - fn deref(&self) -> &Self::Target { 638 - &self.0 639 - } 640 - } 641 - 642 - impl DerefMut for TextArray { 643 - fn deref_mut(&mut self) -> &mut Self::Target { 644 - &mut self.0 645 - } 646 - } 647 - 648 - impl From<TextArray> for Vec<String> { 649 - fn from(v: TextArray) -> Vec<String> { 650 - v.0 651 - } 652 - } 653 - 654 - // Macro to generate array helper types for ENUMs 655 - macro_rules! enum_array { 656 - ($name:ident, $enum_type:ty, $sql_type:ty) => { 657 - #[derive(Clone, Debug, Default, Serialize, Deserialize, FromSqlRow)] 658 - #[diesel(sql_type = Array<Nullable<$sql_type>>)] 659 - pub struct $name(pub Vec<$enum_type>); 660 - 661 - impl FromSql<Array<Nullable<$sql_type>>, Pg> for $name { 662 - fn from_sql(bytes: diesel::pg::PgValue<'_>) -> deserialize::Result<Self> { 663 - let vec_with_nulls = <Vec<Option<$enum_type>> as FromSql< 664 - Array<Nullable<$sql_type>>, 665 - Pg, 666 - >>::from_sql(bytes)?; 667 - Ok($name(vec_with_nulls.into_iter().flatten().collect())) 668 - } 669 - } 670 - 671 - impl Deref for $name { 672 - type Target = Vec<$enum_type>; 673 - 674 - fn deref(&self) -> &Self::Target { 675 - &self.0 676 - } 677 - } 678 - 679 - impl DerefMut for $name { 680 - fn deref_mut(&mut self) -> &mut Self::Target { 681 - &mut self.0 682 - } 683 - } 684 - 685 - impl From<$name> for Vec<$enum_type> { 686 - fn from(v: $name) -> Vec<$enum_type> { 687 - v.0 688 - } 689 - } 690 - }; 691 - } 692 - 693 - // ENUM array types 694 - enum_array!( 695 - PostgateRuleArray, 696 - crate::types::PostgateRule, 697 - crate::schema::sql_types::PostgateRule 698 - ); 699 - enum_array!( 700 - ThreadgateRuleArray, 701 - crate::types::ThreadgateRule, 702 - crate::schema::sql_types::ThreadgateRule 703 - ); 704 - enum_array!( 705 - ReasonTypeArray, 706 - crate::types::ReasonType, 707 - crate::schema::sql_types::ReasonType 708 - ); 709 - enum_array!( 710 - SubjectTypeArray, 711 - crate::types::SubjectType, 712 - crate::schema::sql_types::SubjectType 713 - ); 714 - enum_array!( 715 - LanguageCodeArray, 716 - crate::types::LanguageCode, 717 - crate::schema::sql_types::LanguageCode 718 - ); 719 - } 720 - 721 - // ============================================================================= 722 - // TID RKEY IMPLEMENTATIONS 723 - // ============================================================================= 724 - // Implement HasTidRkey for all models with TID-based rkeys 725 - 726 - impl_tid_rkey!(Post); 727 - // impl_tid_rkey!(FeedgenLike); // Removed - feedgen_likes table dropped, now like_actor_ids[]/like_rkeys[] arrays 728 - // impl_tid_rkey!(LabelerLike); // Removed - labeler_likes table dropped 729 - impl_tid_rkey!(Repost); 730 - // impl_tid_rkey!(Follow); // Removed - follows table dropped, now follow_record[] arrays 731 - impl_tid_rkey!(Block); 732 - impl_tid_rkey!(Bookmark); 733 - // NOTE: FeedGen and List intentionally excluded - use arbitrary String rkeys, not TIDs 734 - impl_tid_rkey!(ListItem); 735 - // impl_tid_rkey!(ListBlock); // Removed - list_blocks table dropped, now list_block_record[] arrays 736 - impl_tid_rkey!(StarterPack); 737 - impl_tid_rkey!(Verification); 738 - 739 - // ============================================================================= 740 - // TID TIMESTAMP IMPLEMENTATIONS 741 - // ============================================================================= 742 - // Implement created_at() method for models where we derive timestamp from TID 743 - // This eliminates the need for a separate created_at column in the database 744 - 745 - impl_tid_created_at!(Post); 746 - // impl_tid_created_at!(FeedgenLike); // Removed - feedgen_likes table dropped, now like_actor_ids[]/like_rkeys[] arrays 747 - // impl_tid_created_at!(LabelerLike); // Removed - labeler_likes table dropped 748 - impl_tid_created_at!(Repost); 749 - // impl_tid_created_at!(Follow); // Removed - follows table dropped, now follow_record[] arrays 750 - impl_tid_created_at!(Block); 751 - impl_tid_created_at!(Bookmark); 752 - impl_tid_created_at!(ListItem); 753 - // impl_tid_created_at!(ListBlock); // Removed - list_blocks table dropped, now list_block_record[] arrays 754 - // NOTE: List uses String rkey and has custom created_at() implementation 755 - impl_tid_created_at!(StarterPack); 756 - impl_tid_created_at!(Verification); 757 - 758 - // Custom created_at() implementation for List (String rkey can be TID or arbitrary) 759 - impl List { 760 - /// Get created_at timestamp derived from rkey if it's a TID, or epoch for non-TID rkeys 761 - pub fn created_at(&self) -> DateTime<Utc> { 762 - crate::models::tid_to_i64(&self.rkey) 763 - .map(crate::tid_util::tid_to_datetime) 764 - .unwrap_or_else(|_| DateTime::<Utc>::UNIX_EPOCH) 765 - } 766 - }
parakeet-db/src/schema.rs parakeet-db/src/infrastructure/schema.rs
parakeet-db/src/tid_util.rs parakeet-db/src/utils/tid.rs
parakeet-db/src/types.rs parakeet-db/src/infrastructure/types/mod.rs
+1 -1
parakeet/src/common/cache_listener.rs
··· 171 171 if let Some((actor_id_str, rkey_str)) = rest.split_once(':') { 172 172 if let Ok(actor_id) = actor_id_str.parse::<i32>() { 173 173 // Parse TID rkey 174 - if let Ok(rkey) = parakeet_db::tid_util::decode_tid(rkey_str) { 174 + if let Ok(rkey) = parakeet_db::utils::tid::decode_tid(rkey_str) { 175 175 // Invalidate StarterpackEntity cache 176 176 use crate::entities::core::starterpack::StarterpackKey; 177 177 state.starterpack_entity.invalidate(vec![StarterpackKey {
+1 -1
parakeet/src/common/helpers.rs
··· 156 156 /// This matches the official Bluesky API which uses base32-encoded TIDs 157 157 /// like "3m47vaoniuy2d" for pagination cursors. 158 158 pub fn tid_cursor(cursor: Option<&String>) -> Option<i64> { 159 - cursor.and_then(|v| parakeet_db::tid_util::decode_tid(v).ok()) 159 + cursor.and_then(|v| parakeet_db::utils::tid::decode_tid(v).ok()) 160 160 } 161 161 162 162 /// Parses a datetime cursor string (ISO 8601 format) into a DateTime
+3 -3
parakeet/src/entities/converters/post.rs
··· 26 26 .quote_count(Some(post_data.post.quote_count as i64)) 27 27 .bookmark_count(Some(0)) 28 28 .indexed_at(Datetime::new( 29 - &parakeet_db::tid_util::tid_to_datetime(post_data.post.rkey) 29 + &parakeet_db::utils::tid::tid_to_datetime(post_data.post.rkey) 30 30 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true) 31 31 ).unwrap()) 32 32 .build() ··· 45 45 // Build the post URI and CID 46 46 let uri = format!("at://{}/app.bsky.feed.post/{}", 47 47 author_profile.did.as_str(), 48 - parakeet_db::tid_util::encode_tid(post_data.post.rkey)); 49 - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&post_data.post.cid).unwrap_or_default(); 48 + parakeet_db::utils::tid::encode_tid(post_data.post.rkey)); 49 + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&post_data.post.cid).unwrap_or_default(); 50 50 51 51 Some(self.post_to_post_view(&post_data, author_profile, &uri, &cid)) 52 52 }
+10 -10
parakeet/src/entities/converters/profile.rs
··· 3 3 /// This replaces the complex hydration layer with simple, direct conversions 4 4 5 5 use crate::entities::core::{ProfileEntity, PostEntity, StarterpackEntity}; 6 - use parakeet_db::models::Actor; 6 + use parakeet_db::domain::Actor; 7 7 use jacquard_api::app_bsky::actor::{ 8 8 ProfileView, ProfileViewDetailed, ProfileViewBasic, 9 9 ProfileAssociated, ProfileAssociatedChat, ··· 22 22 23 23 // Keep avatar_url alive for the entire function scope 24 24 let avatar_url = actor.profile_avatar_cid.as_ref() 25 - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) 25 + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) 26 26 .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); 27 27 28 28 let mut builder = ProfileView::new() ··· 72 72 .and_then(|follows| { 73 73 follows.iter().flatten().find(|f| f.subject_actor_id == actor.id) 74 74 .map(|f| format!("at://{}/app.bsky.graph.follow/{}", viewer_did, 75 - parakeet_db::tid_util::encode_tid(f.rkey))) 75 + parakeet_db::utils::tid::encode_tid(f.rkey))) 76 76 }); 77 77 78 78 // Check if this actor follows viewer ··· 81 81 .and_then(|follows| { 82 82 follows.iter().flatten().find(|f| f.subject_actor_id == viewer_actor_id) 83 83 .map(|f| format!("at://{}/app.bsky.graph.follow/{}", actor.did, 84 - parakeet_db::tid_util::encode_tid(f.rkey))) 84 + parakeet_db::utils::tid::encode_tid(f.rkey))) 85 85 }); 86 86 87 87 // Check if viewer has muted this actor ··· 106 106 .and_then(|blocks| { 107 107 blocks.iter().flatten().find(|b| b.subject_actor_id == actor.id) 108 108 .map(|b| format!("at://{}/app.bsky.graph.block/{}", viewer_did, 109 - parakeet_db::tid_util::encode_tid(b.rkey))) 109 + parakeet_db::utils::tid::encode_tid(b.rkey))) 110 110 }); 111 111 112 112 Some(ViewerState { ··· 130 130 131 131 // Keep URLs alive for the entire function scope 132 132 let avatar_url = actor.profile_avatar_cid.as_ref() 133 - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) 133 + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) 134 134 .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); 135 135 136 136 let banner_url = actor.profile_banner_cid.as_ref() 137 - .and_then(|banner_cid| parakeet_db::cid_util::digest_to_blob_cid_string(banner_cid)) 137 + .and_then(|banner_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(banner_cid)) 138 138 .map(|cid_str| format!("https://cdn.bsky.social/img/banner/plain/{}/{}@jpeg", actor.did, cid_str)); 139 139 140 140 let mut builder = ProfileViewDetailed::new() ··· 221 221 222 222 // Keep URLs alive for the entire function scope 223 223 let avatar_url = actor.profile_avatar_cid.as_ref() 224 - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) 224 + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) 225 225 .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); 226 226 227 227 let banner_url = actor.profile_banner_cid.as_ref() 228 - .and_then(|banner_cid| parakeet_db::cid_util::digest_to_blob_cid_string(banner_cid)) 228 + .and_then(|banner_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(banner_cid)) 229 229 .map(|cid_str| format!("https://cdn.bsky.social/img/banner/plain/{}/{}@jpeg", actor.did, cid_str)); 230 230 231 231 let mut builder = ProfileViewDetailed::new() ··· 334 334 335 335 // Keep avatar_url alive for the entire function scope 336 336 let avatar_url = actor.profile_avatar_cid.as_ref() 337 - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) 337 + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) 338 338 .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); 339 339 340 340 let mut builder = ProfileViewBasic::new()
+2 -2
parakeet/src/entities/core/feedgen.rs
··· 282 282 }; 283 283 284 284 // Convert CID 285 - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&data.cid); 285 + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&data.cid); 286 286 287 287 // Build avatar URL if present 288 288 let avatar = data.avatar_cid.as_ref().and_then(|cid_bytes| { 289 - parakeet_db::cid_util::digest_to_blob_cid_string(cid_bytes) 289 + parakeet_db::utils::cid::digest_to_blob_cid_string(cid_bytes) 290 290 .map(|avatar_cid| format!("{}/avatar/{}", self.cdn_base, avatar_cid)) 291 291 }); 292 292
+5 -5
parakeet/src/entities/core/list.rs
··· 282 282 let creator_view = crate::entities::converters::profile::actor_to_profile_view(&creator); 283 283 284 284 // Convert CID 285 - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(data.cid()); 285 + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(data.cid()); 286 286 287 287 // Build avatar URL if present 288 288 let avatar = data.avatar_cid().and_then(|cid_bytes| { 289 - parakeet_db::cid_util::digest_to_blob_cid_string(cid_bytes) 289 + parakeet_db::utils::cid::digest_to_blob_cid_string(cid_bytes) 290 290 .map(|avatar_cid| format!("{}/avatar/{}", self.cdn_base, avatar_cid)) 291 291 }); 292 292 ··· 404 404 let mut processed = Vec::new(); 405 405 for row in results { 406 406 // Decode TID from string to get timestamp 407 - if let Ok(tid) = parakeet_db::tid_util::decode_tid(&row.rkey) { 408 - let created_at = parakeet_db::tid_util::tid_to_datetime(tid); 407 + if let Ok(tid) = parakeet_db::utils::tid::decode_tid(&row.rkey) { 408 + let created_at = parakeet_db::utils::tid::tid_to_datetime(tid); 409 409 410 410 // Apply cursor filter in Rust 411 411 if let Some(cursor_ts) = cursor { ··· 476 476 let mut processed = Vec::new(); 477 477 for row in results { 478 478 // Convert TID to timestamp 479 - let created_at = parakeet_db::tid_util::tid_to_datetime(row.rkey); 479 + let created_at = parakeet_db::utils::tid::tid_to_datetime(row.rkey); 480 480 481 481 // Apply cursor filter in Rust 482 482 if let Some(cursor_ts) = cursor {
+2 -2
parakeet/src/entities/core/notification.rs
··· 284 284 285 285 let mut map = std::collections::HashMap::new(); 286 286 for r in results { 287 - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.subject_rkey); 287 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.subject_rkey); 288 288 let subject_uri = format!("at://{}/app.bsky.feed.post/{}", r.subject_did, encoded_rkey); 289 289 map.insert((r.actor_id, r.rkey), (subject_uri, r.created_at)); 290 290 } ··· 340 340 341 341 let mut map = std::collections::HashMap::new(); 342 342 for r in results { 343 - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.post_rkey); 343 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.post_rkey); 344 344 let post_uri = format!("at://{}/app.bsky.feed.post/{}", r.post_did, encoded_rkey); 345 345 map.insert((r.actor_id, r.rkey), (post_uri, r.created_at)); 346 346 }
+20 -20
parakeet/src/entities/core/post.rs
··· 10 10 use jacquard_common::types::string::{AtUri, Cid, Datetime}; 11 11 use jacquard_common::types::value::Data; 12 12 use jacquard_common::IntoStatic; 13 - use parakeet_db::models::{Post, Actor}; 13 + use parakeet_db::domain::{Post, Actor}; 14 14 use std::sync::Arc; 15 15 use std::collections::HashMap; 16 16 use std::time::Duration; ··· 69 69 /// Convert this post data to a StrongRef (AT URI + CID) 70 70 pub fn to_strong_ref(&self) -> Option<StrongRef<'static>> { 71 71 // Convert the CID bytes to the proper string format 72 - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&self.post.cid)?; 72 + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&self.post.cid)?; 73 73 74 74 // Build the AT URI 75 75 let uri = format!( 76 76 "at://{}/app.bsky.feed.post/{}", 77 77 self.author.did, 78 - parakeet_db::tid_util::encode_tid(self.post.rkey) 78 + parakeet_db::utils::tid::encode_tid(self.post.rkey) 79 79 ); 80 80 81 81 StrongRef::new_from_str(uri, &cid_str).ok().map(|sr| sr.into_static()) ··· 121 121 let uri = format!( 122 122 "at://{}/app.bsky.feed.post/{}", 123 123 post_data.author.did, 124 - parakeet_db::tid_util::encode_tid(post_data.post.rkey) 124 + parakeet_db::utils::tid::encode_tid(post_data.post.rkey) 125 125 ); 126 126 self.uri_to_key.invalidate(&uri).await; 127 127 } ··· 152 152 let tid_str = parts[2]; 153 153 154 154 // Decode the TID to get rkey 155 - let rkey = parakeet_db::tid_util::decode_tid(tid_str) 155 + let rkey = parakeet_db::utils::tid::decode_tid(tid_str) 156 156 .map_err(|_| eyre::eyre!("Invalid TID in URI"))?; 157 157 158 158 // Get actor_id from ProfileEntity (uses its cache) ··· 194 194 let uri = format!( 195 195 "at://{}/app.bsky.feed.post/{}", 196 196 post_data.author.did, 197 - parakeet_db::tid_util::encode_tid(post_data.post.rkey) 197 + parakeet_db::utils::tid::encode_tid(post_data.post.rkey) 198 198 ); 199 199 self.uri_to_key.insert(uri, key).await; 200 200 self.post_cache.insert(key, post_data.clone()).await; ··· 252 252 let uri = format!( 253 253 "at://{}/app.bsky.feed.post/{}", 254 254 post_data.author.did, 255 - parakeet_db::tid_util::encode_tid(post_data.post.rkey) 255 + parakeet_db::utils::tid::encode_tid(post_data.post.rkey) 256 256 ); 257 257 self.uri_to_key.insert(uri, key).await; 258 258 self.post_cache.insert(key, post_data.clone()).await; ··· 513 513 let uri = format!( 514 514 "at://{}/app.bsky.feed.post/{}", 515 515 data.author.did, 516 - parakeet_db::tid_util::encode_tid(data.post.rkey) 516 + parakeet_db::utils::tid::encode_tid(data.post.rkey) 517 517 ); 518 518 519 - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&data.post.cid) 519 + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&data.post.cid) 520 520 .unwrap_or_else(|| "bafyreiunknown".to_string()); 521 521 522 522 // Extract text from content if available ··· 530 530 }; 531 531 532 532 // Get created_at from TID rkey 533 - let created_at = parakeet_db::tid_util::tid_to_datetime(data.post.rkey); 533 + let created_at = parakeet_db::utils::tid::tid_to_datetime(data.post.rkey); 534 534 535 535 // Compute viewer state if viewer is provided 536 536 let viewer = if let Some(viewer_id) = viewer_actor_id { ··· 575 575 // Build viewer state with like/repost URIs if applicable 576 576 let like_uri = if like { 577 577 // Generate proper like URI with TID 578 - let like_tid = parakeet_db::tid_util::timestamp_to_tid(chrono::Utc::now()); 578 + let like_tid = parakeet_db::utils::tid::timestamp_to_tid(chrono::Utc::now()); 579 579 let viewer_did = viewer_actor.as_ref().map(|a| a.did.clone()).unwrap_or_else(|| "unknown".to_string()); 580 580 Some(format!("at://{}/app.bsky.feed.like/{}", viewer_did, like_tid)) 581 581 } else { ··· 584 584 585 585 let repost_uri = if repost { 586 586 // Generate proper repost URI with TID 587 - let repost_tid = parakeet_db::tid_util::timestamp_to_tid(chrono::Utc::now()); 587 + let repost_tid = parakeet_db::utils::tid::timestamp_to_tid(chrono::Utc::now()); 588 588 let viewer_did = viewer_actor.as_ref().map(|a| a.did.clone()).unwrap_or_else(|| "unknown".to_string()); 589 589 Some(format!("at://{}/app.bsky.feed.repost/{}", viewer_did, repost_tid)) 590 590 } else { ··· 659 659 // Check each image field 660 660 for img_opt in [&data.post.image_1, &data.post.image_2, &data.post.image_3, &data.post.image_4] { 661 661 if let Some(img) = img_opt { 662 - if let Some(cid_str) = parakeet_db::cid_util::digest_to_blob_cid_string(&img.cid) { 662 + if let Some(cid_str) = parakeet_db::utils::cid::digest_to_blob_cid_string(&img.cid) { 663 663 images.push(ImageView { 664 664 thumb: format!("https://cdn.bsky.social/img/feed_thumbnail/plain/{}/{}@jpeg", 665 665 data.author.did, cid_str), ··· 694 694 title: ext.title.clone().unwrap_or_default(), 695 695 description: ext.description.clone().unwrap_or_default(), 696 696 thumb: ext.thumb_cid.as_ref() 697 - .and_then(|cid| parakeet_db::cid_util::digest_to_blob_cid_string(cid)) 697 + .and_then(|cid| parakeet_db::utils::cid::digest_to_blob_cid_string(cid)) 698 698 .map(|cid_str| format!("https://cdn.bsky.social/img/feed_thumbnail/plain/{}/{}@jpeg", 699 699 data.author.did, cid_str)), 700 700 } ··· 751 751 let parent_uri = format!( 752 752 "at://{}/app.bsky.feed.post/{}", 753 753 parent_did, 754 - parakeet_db::tid_util::encode_tid(parent_rkey) 754 + parakeet_db::utils::tid::encode_tid(parent_rkey) 755 755 ); 756 756 ReplyRefPost::NotFound { 757 757 uri: parent_uri, ··· 793 793 let root_uri = format!( 794 794 "at://{}/app.bsky.feed.post/{}", 795 795 root_did, 796 - parakeet_db::tid_util::encode_tid(root_rkey) 796 + parakeet_db::utils::tid::encode_tid(root_rkey) 797 797 ); 798 798 ReplyRefPost::NotFound { 799 799 uri: root_uri, ··· 803 803 // If we can't build root URI, create NotFound with placeholder 804 804 ReplyRefPost::NotFound { 805 805 uri: format!("at://unknown/app.bsky.feed.post/{}", 806 - parakeet_db::tid_util::encode_tid(root_rkey)), 806 + parakeet_db::utils::tid::encode_tid(root_rkey)), 807 807 not_found: true, 808 808 } 809 809 } ··· 864 864 for (like_actor_id, like_rkey) in actor_ids.into_iter().zip(rkeys.into_iter()) { 865 865 // Skip None values 866 866 if let (Some(actor_id), Some(rkey)) = (like_actor_id, like_rkey) { 867 - let timestamp = parakeet_db::tid_util::tid_to_datetime(rkey); 867 + let timestamp = parakeet_db::utils::tid::tid_to_datetime(rkey); 868 868 869 869 // Apply cursor filter 870 870 if let Some(cursor_ts) = cursor { ··· 1082 1082 .optional()?; 1083 1083 1084 1084 Ok(result.map(|(post_actor_id, post_rkey)| { 1085 - let timestamp = parakeet_db::tid_util::tid_to_datetime(rkey); 1085 + let timestamp = parakeet_db::utils::tid::tid_to_datetime(rkey); 1086 1086 (post_actor_id, post_rkey, timestamp) 1087 1087 })) 1088 1088 } ··· 1367 1367 Ok(results 1368 1368 .into_iter() 1369 1369 .map(|r| { 1370 - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.rkey); 1370 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.rkey); 1371 1371 let uri = format!("at://{}/app.bsky.feed.post/{}", r.did, encoded_rkey); 1372 1372 ((r.actor_id, r.rkey), uri) 1373 1373 })
+11 -11
parakeet/src/entities/core/profile.rs
··· 429 429 actor_id: i32, 430 430 cursor_ts: Option<&chrono::DateTime<chrono::Utc>>, 431 431 limit: u8, 432 - ) -> Result<Vec<parakeet_db::composite_types::Block>> { 432 + ) -> Result<Vec<parakeet_db::composite::Block>> { 433 433 // If the actor doesn't exist, they have no blocks 434 434 let actor = match self.get_profile_by_id(actor_id).await { 435 435 Ok(a) => a, 436 436 Err(_) => return Ok(Vec::new()), 437 437 }; 438 438 439 - let blocks: Vec<parakeet_db::composite_types::Block> = actor.blocks 439 + let blocks: Vec<parakeet_db::composite::Block> = actor.blocks 440 440 .as_ref() 441 441 .map(|blocks| blocks.iter().flatten().cloned().collect()) 442 442 .unwrap_or_default(); ··· 445 445 let filtered: Vec<_> = if let Some(cursor) = cursor_ts { 446 446 blocks.into_iter() 447 447 .filter(|b| { 448 - let dt = parakeet_db::tid_util::tid_to_datetime(b.rkey); 448 + let dt = parakeet_db::utils::tid::tid_to_datetime(b.rkey); 449 449 dt < *cursor 450 450 }) 451 451 .take(limit as usize + 1) ··· 484 484 actor_id: i32, 485 485 cursor_ts: Option<&chrono::DateTime<chrono::Utc>>, 486 486 limit: u8, 487 - ) -> Result<Vec<parakeet_db::composite_types::Mute>> { 487 + ) -> Result<Vec<parakeet_db::composite::Mute>> { 488 488 // If the actor doesn't exist, they have no mutes 489 489 let actor = match self.get_profile_by_id(actor_id).await { 490 490 Ok(a) => a, 491 491 Err(_) => return Ok(Vec::new()), 492 492 }; 493 493 494 - let mutes: Vec<parakeet_db::composite_types::Mute> = actor.mutes 494 + let mutes: Vec<parakeet_db::composite::Mute> = actor.mutes 495 495 .as_ref() 496 496 .map(|mutes| mutes.iter().flatten().cloned().collect()) 497 497 .unwrap_or_default(); ··· 515 515 actor_id: i32, 516 516 cursor_ts: Option<&chrono::DateTime<chrono::Utc>>, 517 517 limit: u8, 518 - ) -> Result<Vec<parakeet_db::composite_types::ListMute>> { 518 + ) -> Result<Vec<parakeet_db::composite::ListMute>> { 519 519 // If the actor doesn't exist, they have no muted lists 520 520 let actor = match self.get_profile_by_id(actor_id).await { 521 521 Ok(a) => a, 522 522 Err(_) => return Ok(Vec::new()), 523 523 }; 524 524 525 - let list_mutes: Vec<parakeet_db::composite_types::ListMute> = actor.list_mutes 525 + let list_mutes: Vec<parakeet_db::composite::ListMute> = actor.list_mutes 526 526 .as_ref() 527 527 .map(|mutes| mutes.iter().flatten().cloned().collect()) 528 528 .unwrap_or_default(); ··· 580 580 actor_id: i32, 581 581 cursor_ts: Option<&chrono::DateTime<chrono::Utc>>, 582 582 limit: u8, 583 - ) -> Result<Vec<parakeet_db::composite_types::Follow>> { 583 + ) -> Result<Vec<parakeet_db::composite::Follow>> { 584 584 // If the actor doesn't exist, they follow nobody 585 585 let actor = match self.get_profile_by_id(actor_id).await { 586 586 Ok(a) => a, 587 587 Err(_) => return Ok(Vec::new()), 588 588 }; 589 589 590 - let follows: Vec<parakeet_db::composite_types::Follow> = actor.following 590 + let follows: Vec<parakeet_db::composite::Follow> = actor.following 591 591 .as_ref() 592 592 .map(|follows| follows.iter().flatten().cloned().collect()) 593 593 .unwrap_or_default(); ··· 596 596 let filtered: Vec<_> = if let Some(cursor) = cursor_ts { 597 597 follows.into_iter() 598 598 .filter(|f| { 599 - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); 599 + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); 600 600 dt < *cursor 601 601 }) 602 602 .take(limit as usize + 1) ··· 849 849 // If we have a cursor, filter out posts created before it 850 850 if let Some(cursor_ts) = cursor { 851 851 posts_liked.retain(|(_, rkey)| { 852 - let timestamp = parakeet_db::tid_util::tid_to_datetime(*rkey); 852 + let timestamp = parakeet_db::utils::tid::tid_to_datetime(*rkey); 853 853 timestamp > *cursor_ts 854 854 }); 855 855 }
+7 -7
parakeet/src/entities/core/starterpack.rs
··· 203 203 let actor_id = self.profile_entity.resolve_identifier(did).await.ok()?; 204 204 205 205 // Parse TID rkey 206 - let rkey = parakeet_db::tid_util::decode_tid(rkey_str).ok()?; 206 + let rkey = parakeet_db::utils::tid::decode_tid(rkey_str).ok()?; 207 207 208 208 Some((actor_id, rkey)) 209 209 } ··· 253 253 .await 254 254 .unwrap_or_else(|_| view.creator.did.to_string()); 255 255 256 - let rkey_str = parakeet_db::tid_util::encode_tid(keys.iter() 256 + let rkey_str = parakeet_db::utils::tid::encode_tid(keys.iter() 257 257 .find(|(_, k)| k.actor_id == actor_id) 258 258 .map(|(_, k)| k.rkey) 259 259 .unwrap_or(0)); ··· 280 280 let creator_view = self.profile_entity.actor_to_profile_view_basic(&creator, None).await; 281 281 282 282 // Convert CID 283 - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&data.cid); 283 + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&data.cid); 284 284 285 285 // Parse description facets 286 286 let description_facets: Option<Vec<jacquard_api::app_bsky::richtext::facet::Facet>> = data.description_facets.and_then(|v| { ··· 288 288 }); 289 289 290 290 // Build the URI 291 - let rkey_str = parakeet_db::tid_util::encode_tid(data.rkey); 291 + let rkey_str = parakeet_db::utils::tid::encode_tid(data.rkey); 292 292 let uri = format!("at://{}/app.bsky.graph.starterpack/{}", creator.did, rkey_str); 293 293 294 294 // Get indexed_at - use current time for now ··· 314 314 let (list_item_count, joined_week_count, joined_all_time_count) = 315 315 if let (Some(list_actor_id), Some(list_rkey_str)) = (data.list_actor_id, &data.list_rkey) { 316 316 // Decode the base32 rkey to i64 317 - let list_rkey = parakeet_db::tid_util::decode_tid(list_rkey_str).unwrap_or(0); 317 + let list_rkey = parakeet_db::utils::tid::decode_tid(list_rkey_str).unwrap_or(0); 318 318 self.get_list_counts(list_actor_id, list_rkey).await.unwrap_or((0, 0, 0)) 319 319 } else { 320 320 (0, 0, 0) ··· 578 578 579 579 Ok(rows.into_iter() 580 580 .map(|r| { 581 - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.rkey); 581 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.rkey); 582 582 let at_uri = format!("at://{}/app.bsky.graph.starterpack/{}", r.did, encoded_rkey); 583 583 (at_uri, r.owner, r.name, r.description) 584 584 }) ··· 732 732 if let Ok(item_owner_did) = self.profile_entity.get_did_by_id(item_actor_id).await { 733 733 let item_uri = format!("at://{}/app.bsky.graph.listitem/{}", 734 734 item_owner_did, 735 - parakeet_db::tid_util::encode_tid(item_rkey)); 735 + parakeet_db::utils::tid::encode_tid(item_rkey)); 736 736 737 737 let subject = self.profile_entity.actor_to_profile_view(subject_actor); 738 738 use jacquard_common::IntoStatic;
+2 -2
parakeet/src/entities/ext/actor.rs
··· 1 1 use parakeet_db::{ 2 2 models::Actor, 3 3 types::ActorSyncState, 4 - composite_types::Follow, 4 + composite::Follow, 5 5 }; 6 6 7 7 /// Extension trait for Actor to add convenience methods and AT Protocol conversions ··· 193 193 194 194 fn pinned_post_uri(&self) -> Option<String> { 195 195 self.profile_pinned_post_rkey.map(|rkey| { 196 - let tid = parakeet_db::tid_util::encode_tid(rkey); 196 + let tid = parakeet_db::utils::tid::encode_tid(rkey); 197 197 format!("at://{}/app.bsky.feed.post/{}", self.did, tid) 198 198 }) 199 199 }
+2 -2
parakeet/src/entities/ext/post.rs
··· 1 - use parakeet_db::models::Post; 1 + use parakeet_db::domain::Post; 2 2 3 3 /// Extension trait for Post to add convenience methods and AT Protocol conversions 4 4 pub trait PostExt { ··· 50 50 format!( 51 51 "at://{}/app.bsky.feed.post/{}", 52 52 author_did, 53 - parakeet_db::tid_util::encode_tid(self.rkey) 53 + parakeet_db::utils::tid::encode_tid(self.rkey) 54 54 ) 55 55 } 56 56
+5 -5
parakeet/src/xrpc/app_bsky/bookmark.rs
··· 47 47 .map_err(|_| StatusCode::NOT_FOUND)?; 48 48 49 49 // Decode TID 50 - let rkey = parakeet_db::tid_util::decode_tid(rkey_str) 50 + let rkey = parakeet_db::utils::tid::decode_tid(rkey_str) 51 51 .map_err(|_| StatusCode::BAD_REQUEST)?; 52 52 53 53 // Update bookmarks array on actor ··· 56 56 57 57 // Create new bookmark composite type 58 58 let new_bookmark = format!("({},{},{})", 59 - parakeet_db::tid_util::decode_tid(&parakeet_db::tid_util::timestamp_to_tid(chrono::Utc::now())).unwrap_or(0), // Generate new rkey for the bookmark 59 + parakeet_db::utils::tid::decode_tid(&parakeet_db::utils::tid::timestamp_to_tid(chrono::Utc::now())).unwrap_or(0), // Generate new rkey for the bookmark 60 60 post_actor_id, 61 61 rkey 62 62 ); ··· 119 119 .map_err(|_| StatusCode::NOT_FOUND)?; 120 120 121 121 // Decode TID 122 - let rkey = parakeet_db::tid_util::decode_tid(rkey_str) 122 + let rkey = parakeet_db::utils::tid::decode_tid(rkey_str) 123 123 .map_err(|_| StatusCode::BAD_REQUEST)?; 124 124 125 125 // Remove bookmark from actor's array ··· 194 194 let uri = format!( 195 195 "at://{}/app.bsky.feed.post/{}", 196 196 post_did, 197 - parakeet_db::tid_util::encode_tid(post_rkey) 197 + parakeet_db::utils::tid::encode_tid(post_rkey) 198 198 ); 199 199 200 200 // Create StrongRef for the subject 201 201 // Convert CID bytes to string, then parse to Cid 202 - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&cid) 202 + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&cid) 203 203 .unwrap_or_else(|| "bafyreigrey4aogz7sq5bxfaiwlcieaivxscvgs5ivgqczecmvz6jhmnxq".to_string()); // Default CID 204 204 205 205 use jacquard_common::IntoStatic;
+7 -7
parakeet/src/xrpc/app_bsky/feed/get_timeline.rs
··· 41 41 .map(|c| c.as_ref()) 42 42 .and_then(|c| datetime_cursor(Some(&c.to_string()))) 43 43 .map(|dt| { 44 - let tid_str = parakeet_db::tid_util::timestamp_to_tid(dt); 45 - parakeet_db::tid_util::decode_tid(&tid_str).unwrap_or(0) 44 + let tid_str = parakeet_db::utils::tid::timestamp_to_tid(dt); 45 + parakeet_db::utils::tid::decode_tid(&tid_str).unwrap_or(0) 46 46 }); 47 47 48 48 // Get followed profiles with their post rkeys (all cached) ··· 107 107 // Build cursor from last item (if we have more pages) 108 108 let cursor = if has_next && timeline_items.len() > limit as usize { 109 109 let last_rkey = timeline_items[limit as usize - 1].0; 110 - let timestamp = parakeet_db::tid_util::tid_to_datetime(last_rkey); 110 + let timestamp = parakeet_db::utils::tid::tid_to_datetime(last_rkey); 111 111 Some(timestamp.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)) 112 112 } else { 113 113 None ··· 173 173 uri: Some(jacquard_common::types::aturi::AtUri::new(&format!( 174 174 "at://{}/app.bsky.feed.repost/{}", 175 175 reposter.did, 176 - parakeet_db::tid_util::encode_tid(rkey) 176 + parakeet_db::utils::tid::encode_tid(rkey) 177 177 )).unwrap()), 178 178 cid: None, // TODO: Add CID if needed 179 179 indexed_at: jacquard_common::types::datetime::Datetime::from(indexed_at.fixed_offset()), ··· 236 236 .map(|c| c.as_ref()) 237 237 .and_then(|c| datetime_cursor(Some(&c.to_string()))) 238 238 .map(|dt| { 239 - let tid_str = parakeet_db::tid_util::timestamp_to_tid(dt); 240 - parakeet_db::tid_util::decode_tid(&tid_str).unwrap_or(0) 239 + let tid_str = parakeet_db::utils::tid::timestamp_to_tid(dt); 240 + parakeet_db::utils::tid::decode_tid(&tid_str).unwrap_or(0) 241 241 }); 242 242 243 243 // Use ProfileEntity to get author's posts ··· 262 262 // Build cursor from last post 263 263 let cursor = if has_next && posts_to_return.len() == limit as usize { 264 264 let last_rkey = posts_to_return[posts_to_return.len() - 1].1; 265 - let timestamp = parakeet_db::tid_util::tid_to_datetime(last_rkey); 265 + let timestamp = parakeet_db::utils::tid::tid_to_datetime(last_rkey); 266 266 Some(timestamp.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)) 267 267 } else { 268 268 None
+3 -3
parakeet/src/xrpc/app_bsky/feed/likes.rs
··· 47 47 .last() 48 48 .map(|(_, rkey)| { 49 49 // Convert TID to timestamp for cursor 50 - let dt = parakeet_db::tid_util::tid_to_datetime(*rkey); 50 + let dt = parakeet_db::utils::tid::tid_to_datetime(*rkey); 51 51 dt.to_rfc3339_opts(chrono::SecondsFormat::Millis, true) 52 52 }); 53 53 ··· 56 56 for (post_actor_id, post_rkey) in &results { 57 57 let post_did = state.profile_entity.get_did_by_id(*post_actor_id).await 58 58 .unwrap_or_else(|_| format!("did:plc:unknown{}", post_actor_id)); 59 - let rkey_str = parakeet_db::tid_util::encode_tid(*post_rkey); 59 + let rkey_str = parakeet_db::utils::tid::encode_tid(*post_rkey); 60 60 let uri = format!("at://{}/app.bsky.feed.post/{}", post_did, rkey_str); 61 61 post_uris.push(uri); 62 62 } ··· 124 124 .map_err(|_| Error::not_found())?; 125 125 126 126 // Decode TID 127 - let post_rkey = parakeet_db::tid_util::decode_tid(post_rkey_str) 127 + let post_rkey = parakeet_db::utils::tid::decode_tid(post_rkey_str) 128 128 .map_err(|_| Error::invalid_request(Some("Invalid TID".to_string())))?; 129 129 130 130 // Parse cursor as timestamp
+3 -3
parakeet/src/xrpc/app_bsky/feed/posts/queries.rs
··· 134 134 .map_err(|_| crate::common::errors::Error::not_found())?; 135 135 136 136 // Decode rkey 137 - let embed_rkey = parakeet_db::tid_util::decode_tid(embed_rkey_str) 137 + let embed_rkey = parakeet_db::utils::tid::decode_tid(embed_rkey_str) 138 138 .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; 139 139 140 140 // Parse cursor ··· 166 166 let uri = format!( 167 167 "at://{}/app.bsky.feed.post/{}", 168 168 author_did, 169 - parakeet_db::tid_util::encode_tid(*rkey) 169 + parakeet_db::utils::tid::encode_tid(*rkey) 170 170 ); 171 171 172 172 // Get post view ··· 238 238 .map_err(|_| crate::common::errors::Error::not_found())?; 239 239 240 240 // Decode rkey 241 - let post_rkey = parakeet_db::tid_util::decode_tid(post_rkey_str) 241 + let post_rkey = parakeet_db::utils::tid::decode_tid(post_rkey_str) 242 242 .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; 243 243 244 244 // Parse cursor
+4 -4
parakeet/src/xrpc/app_bsky/feed/posts/threads.rs
··· 79 79 let anchor_actor_id = state.profile_entity.resolve_identifier(anchor_did).await 80 80 .map_err(|_| Error::actor_not_found(anchor_did))?; 81 81 82 - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) 82 + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) 83 83 .map_err(|_| Error::invalid_request(Some("Invalid rkey".to_string())))?; 84 84 85 85 // Get root info for parent query filtering ··· 119 119 let root_actor_id = state.profile_entity.resolve_identifier(root_did).await 120 120 .map_err(|_| Error::actor_not_found(root_did))?; 121 121 122 - let root_rkey = parakeet_db::tid_util::decode_tid(root_rkey_base32).ok(); 122 + let root_rkey = parakeet_db::utils::tid::decode_tid(root_rkey_base32).ok(); 123 123 root_rkey.map(|rkey| (root_actor_id, rkey)) 124 124 } else { 125 125 None ··· 152 152 for item in &parents { 153 153 let did = state.profile_entity.get_did_by_id(item.actor_id).await 154 154 .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); 155 - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); 155 + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); 156 156 all_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); 157 157 } 158 158 ··· 160 160 for item in &children { 161 161 let did = state.profile_entity.get_did_by_id(item.actor_id).await 162 162 .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); 163 - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); 163 + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); 164 164 all_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); 165 165 } 166 166
+2 -2
parakeet/src/xrpc/app_bsky/feed/search.rs
··· 75 75 for (actor_id, rkey) in &results { 76 76 let did = state.profile_entity.get_did_by_id(*actor_id).await 77 77 .unwrap_or_else(|_| format!("did:plc:unknown{}", actor_id)); 78 - let rkey_str = parakeet_db::tid_util::encode_tid(*rkey); 78 + let rkey_str = parakeet_db::utils::tid::encode_tid(*rkey); 79 79 post_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); 80 80 } 81 81 ··· 142 142 for (actor_id, rkey) in results { 143 143 let author_did = state.profile_entity.get_did_by_id(actor_id).await 144 144 .unwrap_or_else(|_| format!("did:plc:unknown{}", actor_id)); 145 - let rkey_str = parakeet_db::tid_util::encode_tid(rkey); 145 + let rkey_str = parakeet_db::utils::tid::encode_tid(rkey); 146 146 let uri = format!("at://{}/app.bsky.feed.post/{}", author_did, rkey_str); 147 147 posts.push(uri); 148 148 }
+2 -2
parakeet/src/xrpc/app_bsky/graph/lists.rs
··· 102 102 .ok_or_else(|| Error::not_found())?; 103 103 104 104 // Parse the list URI to get actor_id and rkey for querying items 105 - let (did, _collection, rkey_str) = parakeet_db::at_uri_util::parse_at_uri(&query.list) 105 + let (did, _collection, rkey_str) = parakeet_db::utils::at_uri::parse_at_uri(&query.list) 106 106 .ok_or_else(|| Error::not_found())?; 107 107 108 108 // Resolve DID to actor_id ··· 172 172 let item_uri = format!( 173 173 "at://{}/app.bsky.graph.listitem/{}", 174 174 item_did, 175 - parakeet_db::tid_util::encode_tid(rkey) 175 + parakeet_db::utils::tid::encode_tid(rkey) 176 176 ); 177 177 178 178 Some(ListItemView {
+4 -4
parakeet/src/xrpc/app_bsky/graph/relations.rs
··· 29 29 30 30 // Build cursor from last result 31 31 let cursor = blocks.last().and_then(|b| { 32 - let dt = parakeet_db::tid_util::tid_to_datetime(b.rkey); 32 + let dt = parakeet_db::utils::tid::tid_to_datetime(b.rkey); 33 33 Some(dt.timestamp_millis().to_string()) 34 34 }); 35 35 ··· 95 95 if let Some(cursor_ts) = cursor_value { 96 96 followers.retain(|f| { 97 97 // f.rkey is a TID that needs conversion 98 - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); 98 + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); 99 99 dt < cursor_ts 100 100 }); 101 101 } ··· 110 110 let cursor = if has_next { 111 111 followers.last().map(|f| { 112 112 // Convert TID to timestamp for cursor 113 - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); 113 + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); 114 114 dt.timestamp_millis().to_string() 115 115 }) 116 116 } else { ··· 153 153 154 154 // Build cursor from last result 155 155 let cursor = results.last().map(|f| { 156 - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); 156 + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); 157 157 dt.timestamp_millis().to_string() 158 158 }); 159 159
+1 -1
parakeet/src/xrpc/app_bsky/graph/starter_packs.rs
··· 67 67 let uris: Vec<String> = results 68 68 .iter() 69 69 .map(|r| { 70 - let rkey_str = parakeet_db::tid_util::encode_tid(r.2); 70 + let rkey_str = parakeet_db::utils::tid::encode_tid(r.2); 71 71 format!("at://{}/app.bsky.graph.starterpack/{}", actor_did, rkey_str) 72 72 }) 73 73 .collect();
+2 -2
parakeet/src/xrpc/app_bsky/graph/thread_mutes.rs
··· 33 33 let (root_did, _collection, rkey_str) = (parts[0], parts[1], parts[2]); 34 34 35 35 // Get root post's actor_id and rkey (natural keys) 36 - let rkey_bigint = parakeet_db::tid_util::decode_tid(rkey_str) 36 + let rkey_bigint = parakeet_db::utils::tid::decode_tid(rkey_str) 37 37 .map_err(|_| Error::invalid_request(Some("Invalid TID in root URI".into())))?; 38 38 39 39 let root_post_actor_id = state.profile_entity.resolve_identifier(root_did).await ··· 87 87 let (root_did, _collection, rkey_str) = (parts[0], parts[1], parts[2]); 88 88 89 89 // Get root post's actor_id and rkey (natural keys) 90 - let rkey_bigint = parakeet_db::tid_util::decode_tid(rkey_str) 90 + let rkey_bigint = parakeet_db::utils::tid::decode_tid(rkey_str) 91 91 .map_err(|_| Error::invalid_request(Some("Invalid TID in root URI".into())))?; 92 92 93 93 let root_post_actor_id = state.profile_entity.resolve_identifier(root_did).await
+6 -6
parakeet/src/xrpc/app_bsky/notification.rs
··· 281 281 uri: jacquard_common::types::string::AtUri::new(&uri).unwrap().into_static(), 282 282 // Use the real CID from database (already stored for all record types) 283 283 cid: jacquard_common::types::string::Cid::new( 284 - parakeet_db::cid_util::digest_to_record_cid_string(&notif.record_cid) 284 + parakeet_db::utils::cid::digest_to_record_cid_string(&notif.record_cid) 285 285 .unwrap_or_else(|| String::from("bafyrei_invalid_cid")) 286 286 .as_bytes() 287 287 ).unwrap().into_static(), ··· 501 501 use parakeet_db::types::NotificationRecordType; 502 502 503 503 // Calculate created_at from record rkey for all record types 504 - let created_at = parakeet_db::tid_util::tid_to_datetime(notif.record_rkey); 504 + let created_at = parakeet_db::utils::tid::tid_to_datetime(notif.record_rkey); 505 505 506 506 match notif.record_type { 507 507 NotificationRecordType::Like => { 508 508 // Construct subject URI from notification's subject fields 509 509 if let (Some(subject_actor_id), Some(subject_rkey)) = (notif.subject_actor_id, notif.subject_rkey) { 510 510 if let Some(subject_did) = actor_did_map.get(&subject_actor_id) { 511 - let encoded_rkey = parakeet_db::tid_util::encode_tid(subject_rkey); 511 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(subject_rkey); 512 512 let subject_uri = format!("at://{}/app.bsky.feed.post/{}", subject_did, encoded_rkey); 513 513 514 514 return serde_json::json!({ ··· 525 525 // Construct subject post URI from notification's subject fields 526 526 if let (Some(subject_actor_id), Some(subject_rkey)) = (notif.subject_actor_id, notif.subject_rkey) { 527 527 if let Some(subject_did) = actor_did_map.get(&subject_actor_id) { 528 - let encoded_rkey = parakeet_db::tid_util::encode_tid(subject_rkey); 528 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(subject_rkey); 529 529 let post_uri = format!("at://{}/app.bsky.feed.post/{}", subject_did, encoded_rkey); 530 530 531 531 return serde_json::json!({ ··· 564 564 let parent_uri = match (parent_actor_id, parent_rkey) { 565 565 (Some(actor_id), Some(rkey)) => { 566 566 actor_did_map.get(actor_id).map(|did| { 567 - let encoded_rkey = parakeet_db::tid_util::encode_tid(*rkey); 567 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(*rkey); 568 568 format!("at://{}/app.bsky.feed.post/{}", did, encoded_rkey) 569 569 }) 570 570 } ··· 573 573 let root_uri = match (root_actor_id, root_rkey) { 574 574 (Some(actor_id), Some(rkey)) => { 575 575 actor_did_map.get(actor_id).map(|did| { 576 - let encoded_rkey = parakeet_db::tid_util::encode_tid(*rkey); 576 + let encoded_rkey = parakeet_db::utils::tid::encode_tid(*rkey); 577 577 format!("at://{}/app.bsky.feed.post/{}", did, encoded_rkey) 578 578 }) 579 579 }
+1 -1
parakeet/src/xrpc/app_bsky/unspecced/handlers.rs
··· 8 8 use jacquard_api::app_bsky::feed::GeneratorView; 9 9 use jacquard_api::app_bsky::graph::StarterPackViewBasic; 10 10 use jacquard_common::IntoStatic; 11 - use parakeet_db::models::ProfileStats; 11 + use parakeet_db::domain::ProfileStats; 12 12 use serde::{Deserialize, Serialize}; 13 13 14 14 // ======== getTrendingTopics ========
+4 -4
parakeet/src/xrpc/app_bsky/unspecced/thread_v2/other_replies.rs
··· 91 91 // Get actor_id from cache (should be cached after hydrate_post) 92 92 let cached_actor = state.id_cache.get_actor_id(anchor_did).await 93 93 .ok_or_else(|| Error::server_error(Some("Actor not in cache")))?; 94 - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) 94 + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) 95 95 .map_err(|_| Error::invalid_request(Some("Invalid rkey".to_string())))?; 96 96 97 97 // Get additional replies that weren't included in the main thread view ··· 108 108 for item in &replies { 109 109 let did = state.profile_entity.get_did_by_id(item.actor_id).await 110 110 .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); 111 - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); 111 + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); 112 112 reply_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); 113 113 } 114 114 let replies_hydrated = std::collections::HashMap::new(); // TODO: Fix hydration ··· 129 129 // Build parent URI 130 130 let parent_did = state.profile_entity.get_did_by_id(parent_actor_id).await 131 131 .unwrap_or_else(|_| format!("did:plc:unknown{}", parent_actor_id)); 132 - let parent_rkey_str = parakeet_db::tid_util::encode_tid(parent_rkey); 132 + let parent_rkey_str = parakeet_db::utils::tid::encode_tid(parent_rkey); 133 133 let parent_uri = format!("at://{}/app.bsky.feed.post/{}", parent_did, parent_rkey_str); 134 134 135 135 // Build this post's URI 136 136 let did = state.profile_entity.get_did_by_id(reply.actor_id).await 137 137 .unwrap_or_else(|_| format!("did:plc:unknown{}", reply.actor_id)); 138 - let rkey_str = parakeet_db::tid_util::encode_tid(reply.rkey); 138 + let rkey_str = parakeet_db::utils::tid::encode_tid(reply.rkey); 139 139 let at_uri = format!("at://{}/app.bsky.feed.post/{}", did, rkey_str); 140 140 141 141 replies_by_parent
+6 -6
parakeet/src/xrpc/app_bsky/unspecced/thread_v2/thread_builder.rs
··· 168 168 let cached_actor = self.id_cache.get_actor_id(anchor_did).await 169 169 .ok_or_else(|| crate::common::errors::Error::server_error(Some("Actor not in cache")))?; 170 170 171 - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) 171 + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) 172 172 .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; 173 173 174 174 // Get root actor_id if we have a root URI ··· 179 179 let root_rkey_base32 = root_parts[2]; 180 180 let root_cached = self.id_cache.get_actor_id(root_did).await 181 181 .ok_or_else(|| crate::common::errors::Error::server_error(Some("Root actor not in cache")))?; 182 - let root_rkey = parakeet_db::tid_util::decode_tid(root_rkey_base32) 182 + let root_rkey = parakeet_db::utils::tid::decode_tid(root_rkey_base32) 183 183 .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid root rkey".to_string())))?; 184 184 (root_cached.actor_id, root_rkey) 185 185 } else { ··· 216 216 for item in &parents { 217 217 let did = self.profile_entity.get_did_by_id(item.actor_id).await 218 218 .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); 219 - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); 219 + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); 220 220 let uri = format!("at://{}/app.bsky.feed.post/{}", did, rkey_str); 221 221 parent_uris.push(uri.clone()); 222 222 parent_uri_by_index.push(uri); ··· 326 326 let cached_actor = self.id_cache.get_actor_id(anchor_did).await 327 327 .ok_or_else(|| crate::common::errors::Error::server_error(Some("Actor not in cache")))?; 328 328 329 - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) 329 + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) 330 330 .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; 331 331 332 332 self.post_entity.get_thread_children_by_arrays( ··· 355 355 for item in &replies { 356 356 let did = self.profile_entity.get_did_by_id(item.actor_id).await 357 357 .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); 358 - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); 358 + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); 359 359 let uri = format!("at://{}/app.bsky.feed.post/{}", did, rkey_str); 360 360 reply_uris.push(uri.clone()); 361 361 reply_uri_map.insert((item.actor_id, item.rkey), uri); ··· 378 378 // Build parent URI 379 379 let parent_did = self.profile_entity.get_did_by_id(parent_actor_id).await 380 380 .unwrap_or_else(|_| format!("did:plc:unknown{}", parent_actor_id)); 381 - let parent_rkey_str = parakeet_db::tid_util::encode_tid(parent_rkey); 381 + let parent_rkey_str = parakeet_db::utils::tid::encode_tid(parent_rkey); 382 382 let parent_uri = format!("at://{}/app.bsky.feed.post/{}", parent_did, parent_rkey_str); 383 383 384 384 let child_uri = reply_uri_map.get(&(reply.actor_id, reply.rkey))
+6 -6
parakeet/src/xrpc/com_atproto/repo.rs
··· 52 52 } 53 53 54 54 // Decode base32 TID to bigint 55 - let rkey_bigint = parakeet_db::tid_util::decode_tid(&query.rkey) 55 + let rkey_bigint = parakeet_db::utils::tid::decode_tid(&query.rkey) 56 56 .map_err(|_| Error::invalid_request(Some("Invalid rkey format".to_string())))?; 57 57 58 58 let result: FeedGenRecord = diesel_async::RunQueryDsl::get_result( ··· 73 73 ) 74 74 .await?; 75 75 76 - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&result.cid) 76 + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&result.cid) 77 77 .unwrap_or_else(|| String::from("bafyrei_invalid_cid")); 78 78 79 79 ( ··· 86 86 } 87 87 "app.bsky.feed.post" => { 88 88 // Decode base32 TID to bigint 89 - let rkey_bigint = parakeet_db::tid_util::decode_tid(&query.rkey) 89 + let rkey_bigint = parakeet_db::utils::tid::decode_tid(&query.rkey) 90 90 .map_err(|_| Error::invalid_request(Some("Invalid rkey format".to_string())))?; 91 91 92 92 // Use raw SQL to get CID and record from posts table ··· 108 108 .await?; 109 109 110 110 // Convert real CID from database to string 111 - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&result.cid) 111 + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&result.cid) 112 112 .unwrap_or_else(|| String::from("bafyrei_invalid_cid")); 113 113 114 114 (cid_str, result.record) ··· 124 124 } 125 125 126 126 // Decode base32 TID to bigint 127 - let rkey_bigint = parakeet_db::tid_util::decode_tid(&query.rkey) 127 + let rkey_bigint = parakeet_db::utils::tid::decode_tid(&query.rkey) 128 128 .map_err(|_| Error::invalid_request(Some("Invalid rkey format".to_string())))?; 129 129 130 130 let result: StarterPackRecord = diesel_async::RunQueryDsl::get_result( ··· 144 144 ) 145 145 .await?; 146 146 147 - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&result.cid) 147 + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&result.cid) 148 148 .unwrap_or_else(|| String::from("bafyrei_invalid_cid")); 149 149 150 150 (cid_str, result.record)
+1 -1
parakeet/src/xrpc/community_lexicon/bookmarks.rs
··· 54 54 55 55 impl BookmarkRecord { 56 56 fn created_at(&self) -> chrono::DateTime<chrono::Utc> { 57 - parakeet_db::tid_util::tid_to_datetime(self.rkey) 57 + parakeet_db::utils::tid::tid_to_datetime(self.rkey) 58 58 } 59 59 } 60 60